標籤:
模組的使用能使linux核心便於裁剪,根據不同的應用需求得到一個最小的核心,同時調試核心驅動也更為方便,比如如果調試i2c驅動,如果不採用模組的方式,那麼每次修改i2c驅動就得編譯整個核心,對於編譯調試極其耗時,使用模組,一個簡單的insmod就將模組載入進了核心,如果覺得不合適,需要調試,只需要rmmod就可以將模組卸載。
一個簡單的驅動模組:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 MODULE_LICENSE("Dual BSD/GPL"); 4 5 static int __init test_init(void) 6 { 7 printk(KERN_ALERT"test modules init\n"); 8 return 0; 9 }10 11 static void __exit test_exit(void)12 {13 printk(KERN_ALERT"test modules exit\n");14 }15 16 17 module_init(test_init);18 module_exit(test_exit);
這個驅動什麼都沒幹,就列印了幾句話,但是具備了驅動程式最基本的幾個要素:
- 驅動初始化函數 XXX_init(void),使用宏module_init(test_init);告訴核心這是模組的初始化函數入口。
- 驅動退出函數XXX_exot(void),使用宏module_exit(test_exit);告訴核心這是模組的退出函數入口。
- 驅動模組遵循的協議 MODULE_LICENSE("Dual BSD/GPL")
注意到初始化函數和退出函數分別被__init,__exit修限定,其實不用它們來限定2個函數也是可以的,那麼用不用這2個東西限定有什麼區別呢?首先看它們到底是2個什麼東西:
1 #define __init __section(.init.text) __cold notrace2 #define __exit __section(.exit.text) __exitused __cold notrace
是2個宏,其中__section也是個宏
1 # define __section(S) __attribute__ ((__section__(#S)))
__attribute__是針對gcc的特有關鍵字,__attribute__ 可以設定函數屬性(Function Attribute )、變數屬性(Variable Attribute )和類型屬性(Type Attribute ),我見的比較多的是在設定資料結構的對齊屬性上:
1 struct S {2 3 short b[3];4 5 } __attribute__ ((aligned (8)));
在這裡設定為.init.text是指把這個函數放到特殊的.init.text段,看過彙編代碼就可能有映象.text段,也就是程式碼片段,函數都是放這個段的。.init.text段的特性是等模組載入完了,這個段將被釋放,因為就好比我們寫一個單片機程式一樣,一般都會有個init函數,它完了就是個死迴圈,驅動實際在運行過程中XXX_init函數就沒有什麼作用了,因此就可以把XXX_init函數載入到記憶體的那段釋放掉,騰出空間來給別人用。
__exit就是在卸載模組完成後,將它標識的函數所佔用的記憶體釋放。
除了__init,__exit,還有__initdata,__exitdata。作用基本類似,我原本猜想帶data的應該是修飾僅在初始化函數和退出函數裡面用到的全域變數,比如一個數組之類的東西,但是我試了用__initdata修飾函數好像編譯並不報錯,暫時不深究。
MODULE_LICENSE("Dual BSD/GPL"),少了它不影響編譯,但是載入模組的時候會警告提示這個模組汙染了核心:
[22321.826339] test: module license ‘unspecified‘ taints kernel.
[22321.826349] Disabling lock debugging due to kernel taint
[22321.826759] test modules init
編譯模組的makefile
1 ifneq ($(KERNELRELEASE), ) 2 obj-m := test.o book.o 3 else 4 KERNELDIR ?= /lib/modules/$(shell uname -r)/build 5 PWD := $(shell pwd) 6 defualt: 7 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 8 endif 9 10 clean:11 rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers12 13 .PHONY:clean
載入模組:
1 sudo insmod test.ko
為了圖方便,使用一個簡單的shell指令碼監視dmesg的輸出,指令碼如下:
1 #!/bin/bash2 3 set -o nounset # Treat unset variables as an error4 5 while true6 do7 dmesg -c 8 sleep 19 done
卸載模組:
1 sudo rmmod test
卸載模組不需要後面的.ko。
參數在驅動裡面必須是一個全域變數,一般加static限定。
聲明參數,使用宏:
1 module_param(<參數名>, <參數類型>, <參數讀寫權限>);
參數類型支援:byte、short、ushort、int、uint、long、ulong、charp(字元指標)、bool 或 invbool(布爾的反)
例如:
1 static int num = 5;2 3 module_param(num, int, S_IRUGO);4 5 static int __init test_init(void)6 {7 printk(KERN_ALERT"num:%d\n",num);8 return 0;9 }
如果在insmod的時候,指定num的值:
1 insmod num=10
那麼輸出:num:10
需要注意的是,參數在聲明時要給一個預設值,因為如果insmod指定參數值,那麼它就使用這個預設值。
一個參數(在驅動裡面的一個全域變數)怎麼會有讀寫權限?載入這個模組後,進入/sys/module目錄,會看到一個test的目錄,這個目錄記錄了這個test模組的一些資訊,與未加模組參數的時候相比,它多出了一個parameters的目錄,進如這個目錄,發現會有個與模組參數同名的num檔案,cat它裡面的內容就是它的預設值5,再查看這個num檔案的許可權:
-r--r--r-- 1 root root 4096 5月 3 21:30 num
和我在代碼中指定的一樣,原來許可權就是指這個檔案的許可權,參數與之對應的一個檔案,也就是說參數的預設值是可以被更改的,只要許可權拿到了,而我們使用insmod num=10的方法並不是修改這個預設值(很明顯,我指定num為10成功了,但是我並沒有對num檔案的寫入權限),只是在載入模組時覆蓋變數的值。
使用 cat /proc/kallsyms | grep "\[test\]"查看與test.ko這個模組相關的符號:
1 00000000 t test_exit [test]2 00000000 r __param_num [test]3 00000000 r __param_str_num [test]4 00000000 d num [test]5 00000000 d __this_module [test]6 00000000 t cleanup_module [test]
可以看到2個我們預料中的符號,num和test_exit,如果我們不使用__init限定test_init,那麼可以看到test_init也在裡面,也就證明了__init的作用是在調用初始化函數後釋放了與之對應的記憶體:
1 00000000 t test_init [test]2 00000000 t test_exit [test]3 00000000 r __param_num [test]4 00000000 r __param_str_num [test]5 00000000 d num [test]6 00000000 d __this_module [test]7 00000000 t cleanup_module [test]8 00000000 t init_module [test]
使用module_param是指外面可以傳一個值進來,如果要這個值對外可見,我們明顯不能採用去掉static限定的辦法,而是使用EXPORT_SYMBOL(num);這個宏是指將模組內的全域變數num對外可見,它不僅僅可以用於變數也可以對函數起作用。
模組A:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 MODULE_LICENSE("Dual BSD/GPL"); 4 5 static int num = 5; 6 module_param(num, int, S_IRUGO); 7 8 static int __init test_init(void) 9 {10 printk(KERN_ALERT"test modules init\n");11 return 0;12 }13 14 static void test_saymyname(void)15 {16 printk(KERN_ALERT"you call me\n");17 }18 static void __exit test_exit(void)19 {20 printk(KERN_ALERT"test modules exit\n");21 }22 23 24 module_init(test_init);25 module_exit(test_exit);26 EXPORT_SYMBOL(test_saymyname);
模組B調用模組A的 test_saymyname
1 #include <linux/init.h> 2 #include <linux/module.h> 3 MODULE_LICENSE("Dual BSD/GPL"); 4 5 extern void test_saymyname(void); 6 7 static int __init book_init(void) 8 { 9 test_saymyname();10 return 0;11 }12 13 static void __exit book_exit(void)14 {15 printk(KERN_ALERT"book modules exit\n");16 }17 18 module_init(book_init);19 module_exit(book_exit);
分別insmod test.ko和insmod book.ko,再卸載模組時,如果先卸載test.ko會發現提示test.ko裡面有函數被book.ko調用,無法卸載,只有先卸載book.ko才行。
linux核心模組