網頁

2008年3月1日 星期六

Linux 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 程序的時候,一個程序只能有一個
mainKernel 本身其實也是一個程序,它本身也有個
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 <linux/modversions.h>
#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,感覺有點麻煩。


當我們使用 EXPORT_SYMBOL 把一些 function variable export
出來之後,我們使用 ksyma -a 去看一些結果。我們發現
EXPORT_SYMBOL(full) 的確是把 full export出來了 :


c8822200 full [my_module]
c01b8e08 pci_find_slot_R454463b5
. . .


但是,結果怎?跟我們想像中的不太一樣,照理說,應該是
full_Rxxxxxx 之類的東西才對啊,怎?才出現 full 而已呢
? 奇怪,問題在那裡呢 ?


其實,問題就在於我們沒有對本身的 module export
出來的 function variable 的名字做
encode。想想,如果在 module 的開頭。我們加入一行


#define full full_Rxxxxxx


之後,我們再重新 compile module 一次,載到 kernel
之後,就可以發現 ksyms -a 顯示的是


c8822200 full_Rxxxxxx [my_module]
c01b8e08 pci_find_slot_R454463b5
. . . . .


了。那是不是說,我們要去對每一個 export 出來的
variable function define 的動作呢 ?
當然不是囉。記得嗎,前頭我們講去使用 kernel export
function 時,由於 include 了一些 .ver
的檔案,以致於我們不用再做 define
的動作。現在,我們也要利用 .ver
的檔案來幫我們,使我們 module export 出來的 function
也可以自動加入 kernel version information。也就是變成
full_Rxxxxxx 之類的東西。


Linux 裡提供了一個 command,叫
genksyms,就是用來幫我們產生這種 .ver
的檔案的。它會從 stdin 裡讀取 source code,然後檢查
source code 裡是否有 export variable
function。如果有,它就會自動為每個 export
出來的東西產生一些 define。這些 define
就是我們之前說的。等我們有了這些 define
之後,只要在我們的 module 裡加入這些 define,那 export
出來的 function variable 就會變成上面那個樣子。


假設我們的程序都放在一個叫 main.c
的檔案裡,我們可以使用下列的方式產生這些 define


gcc -E -D__GENKSYMS__ main.c genksyms -k 2.2.1 > main.ver


gcc -E 參數是指將 preprocessing 的結果 show
出來。也就是說將它 include 的檔案,一些 define
的結果都展開。-D__GENKSYMS__
是一定要的。如果沒有定義這個
constant,你將不會看到任何的結果。用一個管線是因為
genksyms 是從 stdin 讀資料的,所以,經由管線將 gcc
的結果傳給 genksyms-k 2.2.1 是指目前使用的 kernel
版本是 2.2.1,如果你的 kernel
版本不一樣,必須指定你的 kernel 的版本。產生的
define 將會被放到 main.ver 裡。產生完 main.ver
檔之後,在 main.c 裡將它 include 進來,那一切就 OK
了。有件事要告訴各位的是,使用這個方式產生的
module,其 export 出來的東西會經由 main.ver define
改頭換面。所以如果你要讓別人使用,那你必須將
main.ver 公開,不然,別人就沒辦法使用你 export
出來的東西了。


講了這?多,相信各位應該都已經比較清楚 module
kernel 中是怎?樣一回事,也應該知道為什?有時候
module
會無法加載了。除此之外,各位應該還知道如何使自己
module export 出來的東西也具有 kernel version
information


接下來,要跟各位講的就是,如何寫一個 module
了。其實,寫一個 module
很簡單的。如果你瞭解我上面所說的東西。那我再講一次,再用個例子,相信大家就都會了。要寫一個
module,必須要提供兩個 function。這兩個 function 是給
insmod rmmod 使用的。它們分別是 init_module(),以及
cleanup_module()


int init_module();
void cleanup_module();


相信大家都知道在 Linux 裡可以使用 insmod 這個 command
來將某個 module 加載。比方說,我有一個 module
hello.o,那使用 insmod hello.o 就可以將 hello 這個 module
載到 kernel 裡。觀察 /etc/modules 應該就可以看到 hello
這個 module 的名字。如果要將 hello 這個 module
移除,則只要使用 rmmod hello 就可以了。insmod 在加載
module 之後,就會去呼叫 module 所提供的
init_module()。如果傳回 0 表示成功,那 module
就會被加載。如果失敗,那加載的動作就會失敗。一般來講,我們在
init_module()
做的事都是一些初始化的工作。比方說,你的 module
需要一塊內存,那你就可以在 init_module() kmalloc
的動作。想當然爾。cleanup_module() 就是在 module
要移除的時候做的事。做的事一般來講就是一些善後的工作,比方像把之前
kmalloc 的內存 free 掉。


由於 module 是載到 kernel 使用的,所以,可能別的 module
會使用你的 module,甚至某些 process 也會使用到你的
module,為了避免 module 還有人使用時就被移除,每個
module 都有一個 use count。用來記錄目前有多少個 process
module 正在使用這個 module。當 module use count
不等於 0 時,module 是不會被移除掉的。也就是說,當
module use count 不等於 0 時,cleanup_module()
是不會被呼叫的。


在此,我要介紹三個 macro,是跟 module use count
有關的。


MOD_INC_USE_COUNT
MOD_DEC_USE_COUNT
MOD_IN_USE


MOD_INC_USE_COUNT 是用來增加 module use count,而
MOD_DEC_USE_COUNT 是用來減少 module use count。至於
MOD_IN_USE 則是用來檢查目前這個 module
是不是被使用中。也就是檢查 use count 是否為 0module
use count 必須由寫 module 的人自己來
maintain。系統並不會自動為你把 use count
加一或減一。一切都得由自己控制。下面有一個例子,但是,並不會介紹這三個
macro
的使用方法。將來如果有機會,我再來介紹這三個
macro 的用法。


這個例子很簡單。其實只是示範如何使用 init_module()
以及 cleanup_module() 來寫一個 module。當然,這兩個
function 只是構成 module 的基本條件罷了。至於 module
裡要提供的功能則是看各人的需要。


main.c
#define MODULE
#include <linux/module.h>
#include <asm/uaccess.h>
int full;
EXPORT_SYMBOL(full); /* full export 出去 */
int init_module( void )
{
printk( "<5> Module is loadedn" );
return 0;


}


void cleanup_module( void )
{
printk( "<5> Module is unloadedn" );


}


關於 printk 是這樣子的,它是 kernel
所提供的一個打印訊息的 functionkernel export 這個
function。所以你可以自由的使用它。它的用法跟 printf
幾乎一模一樣。唯獨訊息的開頭是
<5>,其實,不見得這三個字符啦。也可以是
<4><3><7> 等等的東西。這是代表這個訊息的 prioirty
level<5> 表示的是跟 KERNEL 有關的訊息。

main.ver:


利用 genksyms 產生出來的。


gcc -E -D__GENKSYMS__ main.c genksyms -k 2.2.1 > main.ver


接下來,就是要把 main.c compile main.o


gcc -D__KERNEL__ -DMODVERSIONS -DEXPORT_SYMTAB -c -I/usr/src/linux/include/linux -include /usr/src/linux/include/linux/modversions.h -include ./main.ver main.c


好了。main.o 已經成功的 compile 出來了,現在下一個
command


insmod main.o


檢查看 /proc/modules 裡是否有 main 這個
module。如果有,表示 main 這個 module 已經載到 kernel
了。再下一個指令,看看 full export 出去的結果。


ksyms


結果顯示


Address Symbol Defined by
c40220e0 full_R355b84b2 [main]
c401d04c ne_probe [ne]
c401a04c ei_open [8390]
c401a094 ei_close [8390]
c401a504 ei_interrupt [8390]
c401af1c ethdev_init [8390]
c401af80 NS8390_init [8390]


可以看到 full_R355b84b2,表示,我們已經成功的將 full
的名字加上 kernel version information
了。當我們不需要這個 module 時,我們就可以下一個
command


rmmod main


這樣 main 就會被移除掉了。再檢查看看 /proc/modules
就可以發現 main 那一行不見了。各位現在可以看一下
/var/log/message 這個檔案,應該可以發現以兩行


Apr 12 14:19:05 host kernel: Module is loaded
Apr 12 14:39:29 host kernel: Module is unloaded


這兩行就是 printk 印出來的。


關於 module 的介紹已經到此告一段落了。其實,使用
module 實在是很簡單的一件事。對於要發展 driver
或是增加 kernel 某些新功能的人來講,用 module
不啻為一個方便的方式。希望這篇文章對各位能有所幫助。

沒有留言:

張貼留言