2006年3月20日 星期一

Static 與 Shared 的函式庫撰寫

Standard
簡單來說函式庫分為兩種類型: Static﹝靜態﹞和 Shared﹝共享﹞,前者你可以想像是嵌入式,將函式嵌入到主程式中;後者是主程式能以 Dynamic (動態)方式指定呼叫的外部函式庫。

最近將一些程式模組化,把許多函式分門別類拉到主程式外做成函式庫模組。這工程雖不算浩大,但也挺累人的。因為函式庫的撰寫很有實用價值,我就隨手將它記下來,還有將以前所看過的文件重新翻出來復習一番。有一篇 《Program Library HOWTO》詳細記載了撰寫函式庫的方法和過程,有興趣的人可以參考。

撰寫程式時,程式碼裡的任何工作,扣除程式語法不算,總是由許許多多函式所組成,可別以為這些函式是電腦內建的,CPU可沒有這麼厲害,可以內建這樣龐大的指令集。這些函式都是由前人不斷發展提供的,可能是作業系統研發者,也可能是某應用程式的設計者。

一些公認標準的共用函式庫,讓一般程式設計師可以方便的使用各種功能,例如,當你想要在螢幕上顯示一行文字,你只要呼叫如 printf() 這種函式,而不必再去處理 低階的 BIOS Interrupt,然後控制 CPU 上的 Register 來達成螢幕輸出的工作。共用函式庫最大的好處,就是能讓許多工作不必重覆撰寫,以省下不少時間,更棒的是,你可以使用各種程式語言來呼叫使用這些函式。

文章一開始就提到函式庫分兩種類型:Static Library、Shared Library。

關於 Static Library ,應該大多程式設計者都很熟悉,就是將自定的函式寫再不同的 .c 檔中,在編譯時,透過編譯器去連結各個 .c 產生出來的二進位 .o 檔,將所有的函式都寫入同一執行檔中,使執行檔主程式能夠使用這些外部函式。

Shared Library 可就沒有這麼中央集權,我們可以將函式庫做成一個個的 .so 檔,你可以將它放入系統函式庫資料夾中,讓所有程式都能呼叫他。當你想要將自定的函式編譯成 Shared Library 時,可用以下指令:

摘錄於《Program Library - More Example

# 將自定函式的 .c 檔案編譯成 .o
gcc -fPIC -Wall -g -c libhello.c

# 將 .o 連結成 .so 檔
gcc -g -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0 libhello.o -lc

Shared Library 另一項特點就是可以動態載入,你不必在編譯時就指定使用,可以在程式中判斷是否存在進而動態載入,這樣的好處可用在模組化的程式,在需要使用某些功能時才載入必要的函式庫。如果要在你的程式中動態載入函式庫,要做以下兩步驟:

摘錄於《Program Library - More Example

1. 在你的程式中包含 dlfcn.h 並使用 dlopen() 等函式動態呼叫 Library

/* demo_dynamic.c -- demonstrate dynamic loading and
use of the "hello" routine */


/* Need dlfcn.h for the routines to
dynamically load libraries */
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

/* Note that we don't have to include "libhello.h".
However, we do need to specify something related;
we need to specify a type that will hold the value
we're going to get from dlsym(). */

/* The type "simple_demo_function" describes a function that
takes no arguments, and returns no value: */

typedef void (*simple_demo_function)(void);


int main(void) {
const char *error;
void *module;
simple_demo_function demo_function;

/* Load dynamically loaded library */
module = dlopen("libhello.so", RTLD_LAZY);
if (!module) {
fprintf(stderr, "Couldn't open libhello.so: %sn",
dlerror());
exit(1);
}

/* Get symbol */
dlerror();
demo_function = dlsym(module, "hello");
if ((error = dlerror())) {
fprintf(stderr, "Couldn't find hello: %sn", error);
exit(1);
}

/* Now call the function in the DL library */
(*demo_function)();

/* All done, close things cleanly */
dlclose(module);
return 0;
}



2. 在編譯時加上 -ldl
gcc -g -o demo_dynamic demo_dynamic.o -ldl

Library 的撰寫和規劃,在大系統的研發專案很好用,可以令各個功能的程式碼分開維護,而且當某一小功能更新,不必將整個專案程式都重新編譯,使整個系統研發流程更簡化。

2006年3月17日 星期五

建立良好的寫程式風格

Standard
大概最近許多人都在趕作業或是要考試吧,有不少人拿著一些他們的心血跑來問我問題。然而,我看到差點沒吐血,我只能說,那真的是一堆人類讀不懂的程式碼。或許我講得太誇張了,但是這卻是不爭的事實。由於許多人良好的 Coding 習慣尚未建立,所以他們所寫出來的東西,真得是『醜』的很可怕。我甚至相信,短短不到100行的程式碼,在半年後他一定讀不出來自己在寫什麼。這一切讓我不禁懷疑,難到他們的老師都沒有告訴他們寫程式的習慣這件事嗎?

其實,擁有好的寫程式習慣,可以讓我們寫出來的程式碼乾淨明瞭,不但在當下能夠容易除錯,日後也容易維護,因它而省下的時間,絕對是值回票價的。因此,建立良好的寫程式習慣是每個程式設計者所不可缺少的一環。

在建立良好的寫程式習慣之前,我們要先建立自己寫程式的風格。在今天資訊科技這麼發達的世界,透過網路的交流,多數程式設計者流傳了一些不成文的風格定義,像是八格的縮排等等。你可以追隨前人,延用他們的風格,也可以自己創造一套風格,但不管你的風格如何,適當的駐解、縮排和乾淨的程式,絕對都是不能缺少的。

這邊提供了一份關於 Linux Kernel 程式的編寫風格說明以供參考:

http://www.linuxjournal.com/node/5780/print


有許多尚未建立習慣風格的人﹝多半是初學者﹞跑來問我問題時,我發現他們寫出來的程式會無法編譯通過,很多都是因為少了一個 { 或 }。追根究底,都是因為程式碼沒有縮排,我們很難看出其巢狀結構,自然就無法發現是否多或少了結尾符號。由此可知,良好的寫程式習慣和風格,是寫好程式的第一要素。

你還沒有好的習慣嗎?趕快去建立吧!

用 C 呼叫 rand() 每次都回傳相同結果?

Standard

為了寫一個隨機產生 ID 的程式,我用C去呼叫 rand() 這亂數產生函式,然而經過測試後發現,每次程式執行所產生的亂數都是一樣的。這問題讓我花了點時間研究了一下,我發現並非每次執行都一樣,而是每秒內產生 出來的亂數是一樣的。很明顯的,這裡的亂數種子設定出了點問題。

在過去習慣上,在使用亂數前 我們會用 srand(time(0)) 設定亂數種子,而一般的書籍也是這樣教我們。但這種方式設定亂數種子,種子的變化是以秒為單位,也就是每秒只變動一次。由於種子在一秒鐘之內都是一樣的, 每次使用 rand() 時,產生出來的亂數也都會是相同的。


這樣設定亂數種子其實 並沒有錯,因為一般程式只需要在開頭設定第一次,之後就不必再設。但如果我們要寫的是一個指令,使用者就可能在一秒鐘之內重覆執行你的程式很多遍。那將會 導致亂數種子被重新定義很多遍。每當種子被重新設定,rand()自然就會把亂數表重頭讀取,當然,每次所得到的亂數就會相同。


我暫時想出了個解決辦法,就是針對種子再給予時間以外的變數,讓每次執行的種子變得更不一樣。於是我將種子設定改成這樣:

srand(time(0)+getpid());


這 以程式執行時的行程處理ID加了進來當種子,因為每個程式的 PID 一定不一樣,如果要一樣也要等許多的程式執行後,重頭分配 PID 才有可能相同。以 PID 再加上原本每秒產生出來的時間,如此產生出來的種子必定獨一無二。這點小技巧能夠解決 rand() 每次回傳都相同的問題。

註:本文是架構在 Linux 上,並以 gcc 編譯所產生的程式當基準,若在 Windows 等其他平台和以 VC++ 、Turbo C等其他款編譯器,不能保證本文所談到的東西能起任何作用。

2006年3月3日 星期五

用心智圖軟體整理自己的思緒

Standard

以前寫各式各樣的程式,總是邊寫邊想其中的邏輯運作。碰到大一點的專案,最多是在紙張上用手畫一畫結構圖,寫一些重點關鍵。但講是講寫一些,最後一定是整疊的資料。


最近因緣際會發現了一個好用的軟體 FreeMind,你可以在
http://freemind.sourceforge.net/ 抓到它,它可以幫我很快的整理思緒,隨時可以刪減、修改或移動。更好用的是,它除了可以用圖顯示外,也可以轉成 HTML、XHTML、XML 等檔案,並條列式的將你的心智圖列出來。什麼是心智圖呢?可以參考下圖:

以 FreeMind 完成的心智圖

以 FreeMind 完成的心智圖


以 前寫程式邊想邊寫,碰到比較複雜的問題,每次都因為太複雜一時想不出頭緒而心情煩躁,嚴重點甚至缺少往下寫的意願。現在,我管他三七二十一,就是先把整個 程式結構流程寫出來就對了。然後就會發現,一項項原本混亂的想法,都可在心智圖上歸類。而且許多想法在此架構下是否妥當,都可以在製作這張心智圖時就會發 現,並很快可以修改。不容易再發生像過去一樣當程式寫到某種程度時,才發現它的架構並不妥當,面臨是否要重新改寫的問題了。當製作完這張圖後,我只要照圖上的邏輯流程,用我的頭腦轉換成程式碼就好了,不用再去煩惱其中的結構問題,真是好用!

因為它,我現在寫程式效率倍增!歡迎大家一同來使用這好用的 FreeMind!

2006年3月1日 星期三

Linux 的 random 總是裝死

Standard
應該有不少人碰過 Apache 啟動時卡在 digest module 過不去,導致 PHP 不能使用,或是碰過某些程式卡在某個地方很久沒動作。這時候大家可能要去檢查一下 /dev/random 這個設備檔案。可以用 cat /dev/random 來看它的內容,如果你發現他一直沒顯示任何內容﹝可能是亂碼數字之類的﹞,那就是它出問題了。


據我從 digest module 的 source code 裡所知,這模組呼叫了一個亂數產生的函數,此函數會呼叫 /dev/random。所以當 /dev/random 卡住顯示不出內容時,很自然的任何 read() 它的程式會停在那等它回應。


我了解 Linux kernel 的 source code 之後發現,/dev/random 會使用 key/mouse/disk 的 interrupt 當做亂數種子的計算來源,好處是會有更大的安全性,但問題是當我們沒在使用 key/mouse/disk 或是他們的 interrupt 訊號量不足以產生亂數種子時,/dev/random 就會永遠是空白沒東西。

這問題有個暫時的解決辦法,就是將 /dev/random 砍掉,先用 link的方式使用 /dev/urandom 取代 /dev/random。

要完全解決此問題,可至 SourceForge 找到 gkernel 這 Project。

下載 rng-tool 回來編譯安裝。

然後於開機的 Scripts 中加入:

rngd -b -o /dev/random -r /dev/urandom

此 Deamon 將會在沒足夠 interrupt 訊號時使用 urandom 的亂數當random 的亂數種子。

用 UPX 幫你的二進位執行檔瘦身

Standard
什麼是 UPX ?它的全名是 the Ultimate Packer for eXecutables,顧名思義就是能將執行檔做壓縮,使執行檔變得很小。有多小呢?一般狀況下可以壓縮成原來的 40% 甚至更小!

程式執行會不會變慢?

關於被壓縮過後的程式執行速度,只有執行剛開始需要解壓縮會比較慢,當程式啟動完畢後就和原先速度一樣,所以最多是啟動時速度比較慢而已。據官方數據統計,在 Pentium 133 的系統上,解壓縮的速度每秒可達 10mb;而在 Athlon XP 2000+ 的系統上,每秒可達 200mb,程式會到達 200 mb 的應該沒有才是。所以說啟動速度只慢個一秒不到,甚至感覺不到有慢,尤其今天的 CPU 隨便就 3.0GHz 以上,解壓縮速度的問題根本就不用煩惱。

能應用在哪裡呢?

一般人不會無聊到要去省那一點點的空間,反正現在硬碟大的很。但是,若用在嵌入式及 LiveCD 系統中,這可以省下不少空間,甚至在 LiveCD 中,程式執行效能還會增加。程式執行效能增加?何解呢?因為光碟讀取是機械動作,速度極慢,尤其當光碟有刮損、不乾淨時,更是一點點資料讀半天。若是用 upx 將執行檔壓縮成一半大小不到,就可以減少光跌機讀取的時間。如此,速度不就增加了嗎?

如何使用呢?

可以去官方網站 Download 壓縮主程式: http://upx.sourceforge.net/

裝好後,就可以用 upx 壓縮執行檔了,如下:

upx <binary-file>

後記

UPX 除了可處理 Linux kernel 和 ELF 檔外,在其他的平台如 Windows、 DOS上也可運作,官方網站上有詳細的資料。