一文講解內(nèi)核模塊依賴!
前言
不知大家有沒有想過,在一個(gè)內(nèi)核模塊代碼中,會(huì)用到printk
函數(shù),而這個(gè)函數(shù)不是我們實(shí)現(xiàn)的,它是內(nèi)核代碼的一部分,但我們?yōu)槭裁茨軌蚓幾g通過呢?
我們的代碼之所以能夠編譯通過,是因?yàn)閷?duì)模塊的編譯僅僅是編譯,并沒有鏈接。
編譯出來的.ko
文件是一個(gè)普通的ELF
文件,使用file
命令和nm
命令,我們可以看到相關(guān)的信息:
vser_init
和vser_exit
分別是模塊的入口函數(shù)和出口函數(shù),使用nm
命令查看模塊目標(biāo)文件的符號(hào)信息時(shí),可以看到vser_exit
和vser_init
的符號(hào)類型是t
,表示它們是函數(shù)。
而printk
的 符號(hào)類型是U
,表示它是一個(gè)未決符號(hào)。意思是說在編譯階段不知道這個(gè)符號(hào)的地址,因?yàn)樗欢x在其他文件中,沒有放在模塊代碼一起編譯。
那printk函數(shù)的地址問題怎么解決呢?答案是用EXPORT_SYMBOL
宏將printk
導(dǎo)出即可。
EXPORT_SYMBOL導(dǎo)出符號(hào)
大致原理:利用EXPORT_SYMBOL
宏生成一個(gè)特定的結(jié)構(gòu)并放在ELF
文件的一個(gè)特定段中,在內(nèi)核的啟動(dòng)過程中,會(huì)將符號(hào)的確切地址填充到這個(gè)結(jié)構(gòu)的特定成員中。
模塊加載時(shí),加載程序?qū)⑷ヌ幚砦礇Q符號(hào),在特殊段中搜索符號(hào)的名字,如果找到,則將獲得的地址填充在被加載模塊的相應(yīng)段中,這樣符號(hào)的地址就可以確定。
使用這種方式處理未決符號(hào),其實(shí)相當(dāng)于把鏈接的過程推后,進(jìn)行了動(dòng)態(tài)鏈接,和普通的應(yīng)用程序使用共享庫函數(shù)的道理是類似的??梢园l(fā)現(xiàn),內(nèi)核將會(huì)有大量的符號(hào)導(dǎo)出,為模塊提供了豐富的基礎(chǔ)設(shè)施。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)??


內(nèi)核模塊依賴
通常情況下,一個(gè)模塊只使用內(nèi)核導(dǎo)出的符號(hào),自己不導(dǎo)出符號(hào)。但是如果一個(gè)模塊需要提供全局變量或函數(shù)給另外的模塊使用,那么就需要將這些符號(hào)導(dǎo)出。
這在一個(gè)驅(qū)動(dòng)調(diào)用另一個(gè)驅(qū)動(dòng)代碼時(shí)比較常見,這樣模塊和模塊之間就形成了依賴關(guān)系,使用導(dǎo)出符號(hào)的模塊將會(huì)依賴于導(dǎo)出符號(hào)的模塊。
舉個(gè)具體的例子,下面是兩個(gè)C文件,vser.c
調(diào)用了dep.c
中的變量和函數(shù):
vser.c
dep.c
Makefile關(guān)鍵處:
上述代碼中,dep.c定義了一個(gè)變量expval
和一個(gè)函數(shù)expfun
,并分別用EXPORT_SYMBOL
和EXPORT_SYMBOL_GPL
導(dǎo)出。而vser.c
里則調(diào)用了dep.c
的變量和函數(shù),編譯安裝后:
從輸出信息中可以看到,vser.c
正確引用到了dep.c
的變量和函數(shù)。
這里有三點(diǎn)重要說明:
如果使用
insmod
命令加載模塊,則必須先加載dep模塊,再加載vser模塊。
因?yàn)関ser模塊用到了dep模塊的東西。從這里可以看出,modprobe
命令優(yōu)于insmod
命令的地方在于其可以自動(dòng)加載被依賴的模塊。而這又要?dú)w功于depmod
命令,depmod
命令會(huì)生成模塊的依賴信息,保存在/lib/modules/5.10.111-64-generic/modules.dep
文件中。其中,5.10.111-64-generic是內(nèi)核源碼版本。查看該文件可以發(fā)現(xiàn)vser模塊所依賴的模塊。
兩個(gè)模塊存在依賴關(guān)系,如果分別編譯兩個(gè)模塊,會(huì)出現(xiàn)類似下面的警告信息,并且即便加載順序正確,加載也不會(huì)成功:
這是因?yàn)樵诰幾gvser
模塊時(shí)在內(nèi)核的符號(hào)表中找不到expval
和expfun
的項(xiàng),而vser
模塊又完全不知道dep
模塊的存在。
解決這個(gè)問題的方法是將兩個(gè)模塊放在一起編譯,或者將dep模塊放在內(nèi)核源碼中,先在內(nèi)核源碼下編譯完所有的模塊,再編譯vser模塊。
卸載模塊時(shí)要先卸載vser模塊,再卸載dep模塊,否則會(huì)因?yàn)閐ep模塊被vser模塊使用而不能卸載。
內(nèi)核將會(huì)創(chuàng)建模塊依賴關(guān)系的鏈接,只有當(dāng)依賴于這個(gè)模塊的鏈表為空時(shí),模塊才能被卸載.
原文作者:嵌入式Linux充電站
