Linux是單內(nèi)核系統(tǒng),可通用計算平臺的外圍設備是頻繁變化的,不可能將所有的(包括將來即將出現(xiàn)的)設備的驅(qū)動程序都一次性編譯進內(nèi)核,為了解決這個問題,Linux提出了可加載內(nèi)核模塊(Loadable Kernel Module,LKM)的概念,允許一個設備驅(qū)動通過模塊加載的方式,在內(nèi)核運行起來之后"融入"內(nèi)核,加載進內(nèi)核的模塊和本身就編譯進內(nèi)核的模塊一模一樣。
一個程序在編譯的地址的相對關系就已經(jīng)確定了,運行的時候只是進行簡單的偏移,為了使模塊加載進內(nèi)核后能夠被放置在正確的地址,并正確的調(diào)用系統(tǒng)的運行的導出符號表,編譯模塊的時候必須要使用系統(tǒng)的編譯地址,并調(diào)用系統(tǒng)編譯出得靜態(tài)的導出符號表。即模塊必須使用系統(tǒng)的配置環(huán)境:Makefile+.config,一旦這兩個文件任意一個發(fā)生了變化,都很可能導致模塊的編譯地址與系統(tǒng)的編譯地址不匹配,造成運行時的錯誤甚至宕機。

導出符號表

從提供系統(tǒng)運行效率的角度,一個模塊不是也不應該是完全獨立的,即一個模塊往往會調(diào)用其他模塊提供的功能來實現(xiàn)自己的功能,這樣做能更好實現(xiàn)系統(tǒng)的分工并提高效率。Linux為了實現(xiàn)模塊間的相互調(diào)用,設計了導出符號表,每個模塊都可以將自己的一個私有的標號導出到系統(tǒng)層級,以使該標號對其他模塊可見,系統(tǒng)在編譯一個模塊的時候會自動導出這個模塊的導出符號表到modules.syms文件(如果沒有導出任何符號,可以為空),并在加載一個模塊的時候會自動將該模塊的導出符號表與系統(tǒng)自身的導出符號表合并。一個系統(tǒng)的源碼的導出符號表一般在源碼頂層目錄的modules.syms文件中,查看正在運行的系統(tǒng)導出符號表使用cat /proc/kallsyms。注意,正如前面解釋的,我們的模塊之所以能夠正常運行,一個重要原因就是編譯我們模塊使用的符號地址就是編譯內(nèi)核時使用的符號地址,所以運行起來雖然地址會有偏移,但是模塊中相關的符號的地址也會和內(nèi)核地址一起偏移,也就還能找得到?;谶@種思想,我們也可以直接查看系統(tǒng)當前運行的地址,將地址賦值給一個函數(shù)指針并使用,也是沒有問題的,當然,這只是闡述原理,并不建議這么寫模塊。
下面這個例子可以看出編譯出的地址和運行時的地址是不一樣的:
iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

導出符號表可以大大的提高系統(tǒng)的運行效率,這也是只有開源系統(tǒng)才能提供的一個強大的功能,但是,導出符號表的引入會導致一個小小的麻煩--模塊的依賴,當我們使用lsmod的時候,就可以查看系統(tǒng)當前的模塊,其最后兩列分別是該模塊被引用的次數(shù)以及引用該模塊的內(nèi)核模塊,當一個模塊被其他模塊引用時,我們是不能進行卸載的,同樣,如果模塊A依賴于模塊B,那么如果模塊B不加載的時候模塊A也加載不了。在編寫多模塊的時候尤其要注意這個問題,可以寫一個腳本管理多個依賴模塊。Linux內(nèi)核使用兩個宏來導出一個模塊的符號

EXPORT_SYMBOL(符號名)
EXPORT_SYMBOL_GPL(符號名)

模塊編譯方法

借助內(nèi)核的Makefile,編譯出的XXX.ko(Kernel Object)就是可加載到該內(nèi)核的外部模塊,為了利用內(nèi)核的Makefile,我們可以將編譯外部模塊的Makefile寫成如下的格式:

ifneq ($(KERNELRELEASE),)    export-objs = demo.o
    obj-m = extern.oelse
  &
        
		

網(wǎng)友評論