不知道在什幺時候,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 應該不會感到困惑了,應該也有辦法解決了。