2.4. 編譯和加載

2018-02-24 15:49 更新

2.4.?編譯和加載

本章開頭的 "hello world" 例子包含了一個(gè)簡(jiǎn)短的建立并加載模塊到系統(tǒng)中去的演示. 當(dāng)然, 整個(gè)過程比我們目前看到的多. 本節(jié)提供了更多細(xì)節(jié)關(guān)于一個(gè)模塊作者如何將源碼轉(zhuǎn)換成內(nèi)核中的運(yùn)行的子系統(tǒng).

2.4.1.?編譯模塊

第一步, 我們需要看一下模塊如何必須被建立. 模塊的建立過程與用戶空間的應(yīng)用程序的建立過程有顯著不同; 內(nèi)核是一個(gè)大的, 獨(dú)立的程序, 對(duì)于它的各個(gè)部分如何組合在一起有詳細(xì)的明確的要求. 建立過程也與以前版本的內(nèi)核的過程不同; 新的建立系統(tǒng)用起來更簡(jiǎn)單并且產(chǎn)生更正確的結(jié)果, 但是它看起來與以前非常不同. 內(nèi)核建立系統(tǒng)是一頭負(fù)責(zé)的野獸, 我們就看它一小部分. 在內(nèi)核源碼的 Document/kbuild 目錄下發(fā)現(xiàn)的文件, 任何想理解表面之下的真實(shí)情況的人都要閱讀一下.

有幾個(gè)前提, 你必須在能建立內(nèi)核模塊前解決. 第一個(gè)是保證你有版本足夠新的編譯器, 模塊工具, 以及其他必要工具. 在內(nèi)核文檔目錄下的文件 Documentation/Changes 一直列出了需要的工具版本; 你應(yīng)當(dāng)在向前走之前參考一下它. 試圖建立一個(gè)內(nèi)核(包括它的模塊), 用錯(cuò)誤的工具版本, 可能導(dǎo)致不盡的奇怪的難題. 注意, 偶爾地, 編譯器的版本太新可能會(huì)引起和太老的版本引起的一樣的問題. 內(nèi)核源碼對(duì)于編譯器做了很大的假設(shè), 新的發(fā)行版本有時(shí)會(huì)一時(shí)地破壞東西.

如果你仍然沒有一個(gè)內(nèi)核樹在手邊, 或者還沒有配置和建立內(nèi)核, 現(xiàn)在是時(shí)間去做了. 沒有源碼樹在你的文件系統(tǒng)上, 你無法為 2.6 內(nèi)核建立可加載的模塊. 實(shí)際運(yùn)行為其而建立的內(nèi)核也是有幫助的( 盡管不是必要的 ).

一旦你已建立起所有東西, 給你的模塊創(chuàng)建一個(gè) makefile 就是直截了當(dāng)?shù)? 實(shí)際上, 對(duì)于本章前面展示的" hello world" 例子, 單行就夠了:


obj-m := hello.o 

熟悉 make , 但是對(duì) 2.6 內(nèi)核建立系統(tǒng)不熟悉的讀者, 可能奇怪這個(gè) makefile 如何工作. 畢竟上面的這一行不是一個(gè)傳統(tǒng)的 makefile 的樣子. 答案, 當(dāng)然, 是內(nèi)核建立系統(tǒng)處理了余下的工作. 上面的安排( 它利用了由 GNU make 提供的擴(kuò)展語(yǔ)法 )表明有一個(gè)模塊要從目標(biāo)文件 hello.o 建立. 在從目標(biāo)文件建立后結(jié)果模塊命名為 hello.ko.

反之, 如果你有一個(gè)模塊名為 module.ko, 是來自 2 個(gè)源文件( 姑且稱之為, file1.c 和 file2.c ), 正確的書寫應(yīng)當(dāng)是:


obj-m := module.o
module-objs := file1.o file2.o

對(duì)于一個(gè)象上面展示的要工作的 makefile, 它必須在更大的內(nèi)核建立系統(tǒng)的上下文被調(diào)用. 如果你的內(nèi)核源碼數(shù)位于, 假設(shè), 你的 ~/kernel-2.6 目錄, 用來建立你的模塊的 make 命令( 在包含模塊源碼和 makefile 的目錄下鍵入 )會(huì)是:


make -C ~/kernel-2.6 M=`pwd` modules

這個(gè)命令開始是改變它的目錄到用 -C 選項(xiàng)提供的目錄下( 就是說, 你的內(nèi)核源碼目錄 ). 它在那里會(huì)發(fā)現(xiàn)內(nèi)核的頂層 makefile. 這個(gè) M= 選項(xiàng)使 makefile 在試圖建立模塊目標(biāo)前, 回到你的模塊源碼目錄. 這個(gè)目標(biāo), 依次地, 是指在 obj-m 變量中發(fā)現(xiàn)的模塊列表, 在我們的例子里設(shè)成了 module.o.

鍵入前面的 make 命令一會(huì)兒之后就會(huì)感覺煩, 所以內(nèi)核開發(fā)者就開發(fā)了一種 makefile 方式, 使得生活容易些對(duì)于那些在內(nèi)核樹之外建立模塊的人. 這個(gè)竅門是如下書寫你的 makefile:


# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)

 obj-m := hello.o 
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else

 KERNELDIR ?= /lib/modules/$(shell uname -r)/build
 PWD := $(shell pwd) 
default:
 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif 

再一次, 我們看到了擴(kuò)展的 GNU make 語(yǔ)法在起作用. 這個(gè) makefile 在一次典型的建立中要被讀 2 次. 當(dāng)從命令行中調(diào)用這個(gè) makefile , 它注意到 KERNELRELEASE 變量沒有設(shè)置. 它利用這樣一個(gè)事實(shí)來定位內(nèi)核源碼目錄, 即已安裝模塊目錄中的符號(hào)連接指回內(nèi)核建立樹. 如果你實(shí)際上沒有運(yùn)行你在為其而建立的內(nèi)核, 你可以在命令行提供一個(gè) KERNELDIR= 選項(xiàng), 設(shè)置 KERNELDIR 環(huán)境變量, 或者重寫 makefile 中設(shè)置 KERNELDIR 的那一行. 一旦發(fā)現(xiàn)內(nèi)核源碼樹, makefile 調(diào)用 default: 目標(biāo), 來運(yùn)行第 2 個(gè) make 命令( 在 makefile 里參數(shù)化成 $(MAKE))象前面描述過的一樣來調(diào)用內(nèi)核建立系統(tǒng). 在第 2 次讀, makefile 設(shè)置 obj-m, 并且內(nèi)核的 makefile 文件完成實(shí)際的建立模塊工作.

這種建立模塊的機(jī)制你可能感覺笨拙模糊. 一旦你習(xí)慣了它, 但是, 你很可能會(huì)欣賞這種已經(jīng)編排進(jìn)內(nèi)核建立系統(tǒng)的能力. 注意, 上面的不是一個(gè)完整的 makefile; 一個(gè)真正的 makefile 包含通常的目標(biāo)類型來清除不要的文件, 安裝模塊等等. 一個(gè)完整的例子可以參考例子代碼目錄的 makefile.

2.4.2.?加載和卸載模塊

模塊建立之后, 下一步是加載到內(nèi)核. 如我們已指出的, insmod 為你完成這個(gè)工作. 這個(gè)程序加載模塊的代碼段和數(shù)據(jù)段到內(nèi)核, 接著, 執(zhí)行一個(gè)類似 ld 的函數(shù), 它連接模塊中任何未解決的符號(hào)連接到內(nèi)核的符號(hào)表上. 但是不象連接器, 內(nèi)核不修改模塊的磁盤文件, 而是內(nèi)存內(nèi)的拷貝. insmod 接收許多命令行選項(xiàng)(詳情見 manpage), 它能夠安排值給你模塊中的參數(shù), 在連接到當(dāng)前內(nèi)核之前. 因此, 如果一個(gè)模塊正確設(shè)計(jì)了, 它能夠在加載時(shí)配置; 加載時(shí)配置比編譯時(shí)配置給了用戶更多的靈活性, 有時(shí)仍然在用. 加載時(shí)配置在本章后面的 "模塊參數(shù)" 一節(jié)講解.

感興趣的讀者可能想看看內(nèi)核如何支持 insmod: 它依賴一個(gè)在 kernel/module.c 中定義的系統(tǒng)調(diào)用. 函數(shù) sys_init_module 分配內(nèi)核內(nèi)存來存放模塊 ( 這個(gè)內(nèi)存用 vmalloc 分配; 看第 8 章的 "vmalloc 和其友" ); 它接著拷貝模塊的代碼段到這塊內(nèi)存區(qū), 借助內(nèi)核符號(hào)表解決模塊中的內(nèi)核引用, 并且調(diào)用模塊的初始化函數(shù)來啟動(dòng)所有東西.

如果你真正看了內(nèi)核代碼, 你會(huì)發(fā)現(xiàn)系統(tǒng)調(diào)用的名子以 sys_ 為前綴. 這對(duì)所有系統(tǒng)調(diào)用都是成立的, 并且沒有別的函數(shù). 記住這個(gè)有助于在源碼中查找系統(tǒng)調(diào)用.

modprobe 工具值得快速提及一下. modprobe, 如同 insmod, 加載一個(gè)模塊到內(nèi)核. 它的不同在于它會(huì)查看要加載的模塊, 看是否它引用了當(dāng)前內(nèi)核沒有定義的符號(hào). 如果發(fā)現(xiàn)有, modprobe 在定義相關(guān)符號(hào)的當(dāng)前模塊搜索路徑中尋找其他模塊. 當(dāng) modprobe 找到這些模塊( 要加載模塊需要的 ), 它也把它們加載到內(nèi)核. 如果你在這種情況下代替以使用 insmod , 命令會(huì)失敗, 在系統(tǒng)日志文件中留下一條 " unresolved symbols "消息.

如前面提到, 模塊可以用 rmmod 工具從內(nèi)核去除. 注意, 如果內(nèi)核認(rèn)為模塊還在用( 就是說, 一個(gè)程序仍然有一個(gè)打開文件對(duì)應(yīng)模塊輸出的設(shè)備 ), 或者內(nèi)核被配置成不允許模塊去除, 模塊去除會(huì)失敗. 可以配置內(nèi)核允許"強(qiáng)行"去除模塊, 甚至在它們看來是忙的. 如果你到了需要這選項(xiàng)的地步, 但是, 事情可能已經(jīng)錯(cuò)的太嚴(yán)重以至于最好的動(dòng)作就是重啟了.

lsmod 程序生成一個(gè)內(nèi)核中當(dāng)前加載的模塊的列表. 一些其他信息, 例如使用了一個(gè)特定模塊的其他模塊, 也提供了. lsmod 通過讀取 /proc/modules 虛擬文件工作. 當(dāng)前加載的模塊的信息也可在位于 /sys/module 的 sysfs 虛擬文件系統(tǒng)找到.

2.4.3.?版本依賴

記住, 你的模塊代碼一定要為每個(gè)它要連接的內(nèi)核版本重新編譯 -- 至少, 在缺乏 modversions 時(shí), 這里不涉及因?yàn)樗鼈兏嗟氖墙o內(nèi)核發(fā)布制作者, 而不是開發(fā)者. 模塊是緊密結(jié)合到一個(gè)特殊內(nèi)核版本的數(shù)據(jù)結(jié)構(gòu)和函數(shù)原型上的; 模塊見到的接口可能一個(gè)內(nèi)核版本與另一個(gè)有很大差別. 當(dāng)然, 在開發(fā)中的內(nèi)核更加是這樣.

內(nèi)核不只是認(rèn)為一個(gè)給定模塊是針對(duì)一個(gè)正確的內(nèi)核版本建立的. 建立過程的其中一步是對(duì)一個(gè)當(dāng)前內(nèi)核樹中的文件(稱為 vermagic.o)連接你的模塊; 這個(gè)東東含有相當(dāng)多的有關(guān)要為其建立模塊的內(nèi)核的信息, 包括目標(biāo)內(nèi)核版本, 編譯器版本, 以及許多重要配置變量的設(shè)置. 當(dāng)嘗試加載一個(gè)模塊, 這些信息被檢查與運(yùn)行內(nèi)核的兼容性. 如果不匹配, 模塊不會(huì)加載; 代之的是你見到如下內(nèi)容:


# insmod hello.ko
Error inserting './hello.ko': -1 Invalid module format

看一下系統(tǒng)日志文件(/var/log/message 或者任何你的系統(tǒng)被配置來用的)將發(fā)現(xiàn)導(dǎo)致模塊無法加載特定的問題.

如果你需要編譯一個(gè)模塊給一個(gè)特定的內(nèi)核版本, 你將需要使用這個(gè)特定版本的建立系統(tǒng)和源碼樹. 前面展示過的在例子 makefile 中簡(jiǎn)單修改 KERNELDIR 變量, 就完成這個(gè)動(dòng)作.

內(nèi)核接口在各個(gè)發(fā)行之間常常變化. 如果你編寫一個(gè)模塊想用來在多個(gè)內(nèi)核版本上工作(特別地是如果它必須跨大的發(fā)行版本), 你可能只能使用宏定義和 #ifdef 來使你的代碼正確建立. 本書的這個(gè)版本只關(guān)心內(nèi)核的一個(gè)主要版本, 因此不會(huì)在我們的例子代碼中經(jīng)常見到版本檢查. 但是這種需要確實(shí)有時(shí)會(huì)有. 在這樣情況下, 你要利用在 linux/version.h 中發(fā)現(xiàn)的定義. 這個(gè)頭文件, 自動(dòng)包含在 linux/module.h, 定義了下面的宏定義:

UTS_RELEASE
這個(gè)宏定義擴(kuò)展成字符串, 描述了這個(gè)內(nèi)核樹的版本. 例如, "2.6.10".

LINUX_VERSION_CODE
這個(gè)宏定義擴(kuò)展成內(nèi)核版本的二進(jìn)制形式, 版本號(hào)發(fā)行號(hào)的每個(gè)部分用一個(gè)字節(jié)表示. 例如, 2.6.10 的編碼是 132618 ( 就是, 0x02060a ). [4]有了這個(gè)信息, 你可以(幾乎是)容易地決定你在處理的內(nèi)核版本.

KERNEL_VERSION(major,minor,release)
這個(gè)宏定義用來建立一個(gè)整型版本編碼, 從組成一個(gè)版本號(hào)的單個(gè)數(shù)字. 例如, KERNEL_VERSION(2.6.10) 擴(kuò)展成 132618. 這個(gè)宏定義非常有用, 當(dāng)你需要比較當(dāng)前版本和一個(gè)已知的檢查點(diǎn).

大部分的基于內(nèi)核版本的依賴性可以使用預(yù)處理器條件解決, 通過利用 KERNEL_VERSION 和 LINUX_VERSION_VODE. 版本依賴不應(yīng)當(dāng), 但是, 用繁多的 #ifdef 條件來搞亂驅(qū)動(dòng)的代碼; 處理不兼容的最好的方式是把它們限制到特定的頭文件. 作為一個(gè)通用的原則, 明顯版本(或者平臺(tái))依賴的代碼應(yīng)當(dāng)隱藏在一個(gè)低級(jí)的宏定義或者函數(shù)后面. 高層的代碼就可以只調(diào)用這些函數(shù), 而不必關(guān)心低層的細(xì)節(jié). 這樣書寫的代碼易讀并且更健壯.

2.4.4.?平臺(tái)依賴性

每個(gè)電腦平臺(tái)有其自己的特點(diǎn), 內(nèi)核設(shè)計(jì)者可以自由使用所有的特性來獲得更好的性能. in the target object file ???

不象應(yīng)用程序開發(fā)者, 他們必須和預(yù)編譯的庫(kù)一起連接他們的代碼, 依附在參數(shù)傳遞的規(guī)定上, 內(nèi)核開發(fā)者可以專用某些處理器寄存器給特別的用途, 他們確實(shí)這樣做了. 更多的, 內(nèi)核代碼可以為一個(gè) CPU 族里的特定處理器優(yōu)化, 以最好地利用目標(biāo)平臺(tái); 不象應(yīng)用程序那樣常常以二進(jìn)制格式發(fā)布, 一個(gè)定制的內(nèi)核編譯可以為一個(gè)特定的計(jì)算機(jī)系列優(yōu)化.

例如, IA32 (x86) 結(jié)構(gòu)分為幾個(gè)不同的處理器類型. 老式的 80386 處理器仍然被支持( 到現(xiàn)在 ), 盡管它的指令集, 以現(xiàn)代的標(biāo)準(zhǔn)看, 非常有限. 這個(gè)體系中更加現(xiàn)代的處理器已經(jīng)引入了許多新特性, 包括進(jìn)入內(nèi)核的快速指令, 處理器間的加鎖, 拷貝數(shù)據(jù), 等等. 更新的處理器也可采用 36 位( 或者更大 )的物理地址, 當(dāng)在適當(dāng)?shù)哪J较? 以允許他們尋址超過 4 GB 的物理內(nèi)存. 其他的處理器家族也有類似的改進(jìn). 內(nèi)核, 依賴不同的配置選項(xiàng), 可以被建立來使用這些附加的特性.

清楚地, 如果一個(gè)模塊與一個(gè)給定內(nèi)核工作, 它必須以與內(nèi)核相同的對(duì)目標(biāo)處理器的理解來建立. 再一次, vermagic.o 目標(biāo)文件登場(chǎng). 當(dāng)加載一個(gè)模塊, 內(nèi)核為模塊檢查特定處理器的配置選項(xiàng), 確認(rèn)它們匹配運(yùn)行的內(nèi)核. 如果模塊用不同選項(xiàng)編譯, 它不會(huì)加載.

如果你計(jì)劃為通用的發(fā)布編寫驅(qū)動(dòng), 你可能很奇怪你怎么可能支持所有這些不同的變體. 最好的答案, 當(dāng)然, 是發(fā)行你的驅(qū)動(dòng)在 GPL 兼容的許可之下, 并且貢獻(xiàn)它給主流內(nèi)核. 如果沒有那樣, 以源碼形式和一套腳本發(fā)布你的驅(qū)動(dòng), 以便在用戶系統(tǒng)上編譯可能是最好的答案. 一些供應(yīng)商已發(fā)行了工具來簡(jiǎn)化這個(gè)工作. 如果你必須發(fā)布你的驅(qū)動(dòng)以二進(jìn)制形式, 你需要查看由你的目標(biāo)發(fā)布所提供的不同的內(nèi)核, 并且為每個(gè)提供一個(gè)模塊版本. 要確認(rèn)考慮到了任何在產(chǎn)生發(fā)布后可能發(fā)行的勘誤內(nèi)核. 接著, 要考慮許可權(quán)的問題, 如同我們?cè)诘?1 章的" 許可條款" 一節(jié)中討論的. 作為一個(gè)通用的規(guī)則, 以源碼形式發(fā)布東西是你行于世的易途.

[4] 這允許在穩(wěn)定版本之間多達(dá) 256 個(gè)開發(fā)版本.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)