天天看點

module的認識1

不知道在什幺時候,Linux 出現了 module 這種東西,的确,它是 Linux 的一大革新。有了 module 之後,寫 device driver 不再是一項惡夢,修改 kernel 也不再是一件痛苦的事了。因為你不需要每次要測試 driver 就重新 compile kernel 一次。那簡直是會累死人。Module 可以允許我們動态的改變 kernel,加載 device driver,而且它也能縮短我們 driver development 的時間。在這篇文章裡,我将要跟各位介紹一下 module 的原理,以及如何寫一個 module。

module 翻譯成中文就是子產品,不過,事實上去翻譯這個字一點都沒意義。在講子產品之前,我先舉一個例子。相信很多人都用過 RedHat。在 RedHat 裡,我們可以執行 sndconfig,它可以幫我們 config 聲霸卡。config 完之後如果捉得到你的聲霸卡,那你的聲霸卡馬上就可以動了,而且還不用重新激活計算機。這是怎幺做的呢 ? 就是靠module。module 其實是一般的程式。但是它可以被動态載到 kernel 裡成為 kernel的一部分。載到 kernel 裡的 module 它具有跟 kernel 一樣的權力。可以 access 任何 kernel 的 data structure。你聽過 kdebug 嗎 ? 它是用來 debug kernel 的。它就是先将它本身的一個 module 載到 kernel 裡,而在 user space 的 gdb 就可以經由跟這個 module 溝通,得知 kernel 裡的 data structure 的值,除此之外,還可以經由載到 kernel 的 module 去更改 kernel 裡 data structure。

我們知道,在寫 C 程式的時候,一個程式隻能有一個 main。Kernel 本身其實也是一個程式,它本身也有個 main,叫 start_kernel()。當我們把一個 module 載到 kernel 裡的時候,它會跟 kernel 整合在一起,成為 kernel 的一部分。請各位想想,那 module 可以有 main 嗎 ? 答案很明顯的,是 No。理由很簡單。一個程式隻能有一個 main。在使用 module 時,有一點要記住的是 module 是處于被動的角色。它是提供某些功能讓别人去使用的。

Kernel 裡有一個變量叫 module_list,每當 user 将一個 module 載到 kernel 裡的時候,這個 module 就會被記錄在 module_list 裡面。當 kernel 要使用到這個 module 提供的 function 時,它就會去 search 這個 list,找到 module,然後再使用其提供的 function 或 variable。每一個 module 都可以 export 一些 function 或變量來讓别人使用。除此之外,module 也可以使用已經載到 kernel 裡的 module 提供的 function。這種情形叫做 module stack。比方說,module A 用到 module B 的東西,那在加載 module A 之前必須要先加載 module B。否則 module A 會無法加載。除了 module 會 export 東西之外,kernel 本身也會 export 一些 function 或 variable。同樣的,module 也可以使用 kernel 所 export 出來的東西。由于大家平時都是撰寫 user space 的程式,是以,當突然去寫 module 的時候,會把平時寫程式用的 function 拿到 module 裡使用。像是 printf 之類的東西。我要告訴各位的是,module 所使用的 function 或 variable,要嘛就是自己寫在 module 裡,要嘛就是别的 module 提供的,再不就是 kernel 所提供的。你不能使用一般 libc 或 glibc所提供的 function。像 printf 之類的東西。這一點可能是各位要多小心的地方。(也許你可以先 link 好,再載到 kernel,我好象試過,但是忘了)

剛才我們說到 kernel 本身會 export 出一些 function 或 variable 來讓 module 使用,但是,我們不是萬能的,我們怎幺知道 kernel 有開放那裡東西讓我們使用呢 ? Linux 提供一個 command,叫 ksyms,你隻要執行 ksyms -a 就可以知道 kernel 或目前載到 kernel 裡的 module 提供了那些 function 或 variable。底下是我的系統的情形:

c0216ba0 drive_info_R744aa133

c01e4a44 boot_cpu_data_R660bd466

c01e4ac0 EISA_bus_R7413793a

c01e4ac4 MCA_bus_Rf48a2c4c

c010cc34 __verify_write_R203afbeb

. . . . .

在 kernel 裡,有一個 symbol table 是用來記錄 export 出去的 function 或 variable。除此之外,也會記錄着那個 module export 那些 function。上面幾行中,表示 kernel 提供了 drive_info 這個 function/variable。是以,我們可以在 kernel 裡直接使用它,等載到 kernel 裡時,會自動做好 link 的動作。由此,我們可以知道,module 本身其實是還沒做 link 的一些 object code。一切都要等到 module 被加載 kernel 之後,link 才會完成。各位應該可以看到 drive_info 後面還接着一些奇怪的字元串。_R744aa133,這個字元串是根據目前 kernel 的版本再做些 encode 得出來的結果。為什幺額外需要這一個字元串呢 ?

Linux 不知道從那個版本以來,就多了一個 config 的選項,叫做 Set version number in symbols of module。這是為了避免對系統造成不穩定。我們知道 Linux 的 kernel 更新的很快。在 kernel 更新的過程,有時為了效率起見,會對某些舊有的 data structure 或 function 做些改變,而且一變可能有的 variable 被拿掉,有的 function 的 prototype 跟原來的都不太一樣。如果這種情形發生的時候,那可能以前 2.0.33 版本的 module 拿到 2.2.1 版本的 kernel 使用,假設原來 module 使用了 2.0.33 kernel 提供的變量叫 A,但是到了 2.2.1 由于某些原因必須把 A 都設成 NULL。那當此 module 用在 2.2.1 kernel 上時,如果它沒去檢查 A 的值就直接使用的話,就會造成系統的錯誤。也許不會整個系統都死掉,但是這個 module 肯定是很難發揮它的功能。為了這個原因,Linux 就在 compile module 時,把 kernel 版本的号碼 encode 到各個 exported function 和 variable 裡。

是以,剛才也許我們不應該講 kernel 提供了 drive_info,而應該說 kernel 提供了 driver_info_R744aa133 來讓我們使用。這樣也許各位會比較明白。也就是說,kernel 認為它提供的 driver_info_R744aa133 這個東西,而不是 driver_info。是以,我們可以發現有的人在加載 module 時,系統都一直告訴你某個 function 無法 resolved。這就是因為 kernel 裡沒有你要的 function,要不然就是你的 module 裡使用的 function 跟 kernel encode 的結果不一樣。是以無法 resolve。解決方式,要嘛就是将 kernel 裡的 set version 選項關掉,要嘛就是将 module compile 成 kernel 有辦法接受的型式。

那有人就會想說,如果 kernel 認定它提供的 function 名字叫做 driver_info_R744aa133 的話,那我們寫程式時,是不是用到這個 funnction 的地方都改成 driver_info_R744aa133 就可以了。答案是 Yes。但是,如果每個 function 都要你這樣寫,你不會覺得很煩嗎 ? 比方說,我們在寫 driver 時,很多人都會用到 printk 這個 function。這是 kernel 所提供的 function。它的功能跟 printf 很像。用法也幾乎都一樣。是 debug 時很好用的東西。如果我們 module 裡用了一百次 printk,那是不是我們也要打一百次的 printk_Rdd132261 呢 ? 當然不是,聰明的人馬上會想到用 #define printk printk_Rdd132261 就好了嘛。是以啰,Linux 很體貼的幫我們做了這件事。

如果各位的系統有将 set version 的選項打開的話,那大家可以到 /usr/src/linux/include/linux/modules 這個目錄底下。這個目錄底下有所多的 ..ver檔案。這些檔案其實就是用來做 #define 用的。我們來看看 ksyms.ver 這個檔案裡,裡面有一行是這樣子的 :

#define printk _set_ver(printk)

set_ver 是一個 macro,就是用來在 printk 後面加上 version number 的。有興趣的朋友可以自行去觀看這個 macro 的寫法。用了這些 ver 檔,我們就可以在 module 裡直接使用 printk 這樣的名字了。而這些 ver 檔會自動幫我們做好 #define 的動作。可是,我們可以發現這個目錄有很多很多的 ver 檔。有時候,我們怎幺知道我們要呼叫的 function 是在那個 ver 檔裡有定義呢 ? Linux 又幫我們做了一件事。/usr/src/linux/include/linux/modversions.h 這個檔案已經将全部的 ver 檔都加進來了。是以在我們的 module 裡隻要 include 這個檔,那名字的問題都解決了。但是,在此,我們奉勸各位一件事,不要将 modversions.h 這個檔在 module 裡 include 進來,如果真的要,那也要加上以下數行:

#ifdef MODVERSIONS

#include

#endif

加入這三行的原因是,避免這個 module 在沒有設定 kernel version 的系統上,将 modversions.h 這個檔案 include 進來。各位可以去試試看,當你把 set version 的選項關掉時,modversions.h 和 modules 這個目錄都會不見。如果沒有上面三行,那 compile 就不會過關。是以一般來講,modversions.h 我們會選擇在 compile 時傳給 gcc 使用。就像下面這個樣子。

gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS main.c \

-include usr/src/linux/include/linux/modversions.h

在這個 command line 裡,我們看到了 -D__KERNEL__,這是說要定義 __KERNEL__ 這個 constant。很多跟 kernel 有關的 header file,都必須要定義這個 constant 才能 include 的。是以建議你最好将它定義起來。另外還有一個 -DMODVERSIONS。這個 constant 我剛才忘了講。剛才我們說要解決 fucntion 或 variable 名字 encode 的方式就是要 include modversions.h,其實除此之外,你還必須定義 MODVERSIONS 這個 constant。再來就是 MODULE 這個 constant。其實,隻要是你要寫 module 就一定要定義這個變量。而且你還要 include module.h 這個檔案,因為 _set_ver 就是定義在這裡的。

講到這裡,相信各位應該對 module 有一些認識了,以後遇到 module unresolved 應該不會感到困惑了,應該也有辦法解決了。

剛才講的都是使用别人的 function 上遇到的名字 encode 問題。但是,如果我們自己的 module 想要 export 一些東西讓别的 module 使用呢。很簡單。在 default 上,在你的 module 裡所有的 global variable 和 function 都會被認定為你要 export 出去的。是以,如果你的 module 裡有 10 個 global variable,經由 ksyms,你可以發現這十個 variable 都會被 export 出去。這當然是個很友善的事啦,但是,你知道,有時候我們根本不想把所有的 variable 都 export 出去,萬一有個 module 沒事亂改我們的 variable 怎幺辦呢 ? 是以,在很多時候,我們都隻會限定幾個必要的東西 export 出去。在 2.2.1 之前的 kernel (不是很确定) 可以利用 register_symtab 來幫我們。但是,現在更新的版本早就出來了。是以,在此,我會介紹 kernel 2.2.1 裡所提供的。kernel 2.2.1 裡提供了一個 macro,叫做 EXPORT_SYMBOL,這是用來幫我們選擇要 export 的 variable 或 function。比方說,我要 export 一個叫 full 的 variable,那我隻要在 module 裡寫:

EXPORT_SYMBOL(full);

就會自動将 full export 出去,你馬上就可以從 ksyms 裡發現有 full 這個變量被 export 出去。在使用 EXPORT_SYMBOL 之前,要小心一件事,就是必須在 gcc 裡定義 EXPORT_SYMTAB 這個 constant,否則在 compile 時會發生 parser error。是以,要使用 EXPORT_SYMBOL 的話,那 gcc 應該要下:

gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS -DEXPORT_SYMTAB \

main.c -include /usr/src/linux/include/linux/modversions.h

如果我們不想 export 任何的東西,那我們隻要在 module 裡下

EXPORT_NO_SYMBOLS;

就可以了。使用 EXPORT_NO_SYMBOLS 用不着定義任何的 constant。其實,如果各位使用過舊版的 register_symbol 的話,一定會覺得新版的方式比較好用。至少我是這樣覺得啦。因為使用 register_symbol 還要先定義出自己的 symbol_table,感覺有點麻煩。

繼續閱讀