1、什么是內核模塊?
內核模塊是Linux提供的一種機制,允許在內核運行時動態加載進內核中,具有兩個特點:
1)內核模塊本身不編譯入內核映像,有效控制縮減內核鏡像大小
2)內核模塊一旦被加載,他就和內核中的其他部分完全一樣
2、為什么需要內核模塊?
如果在內核編譯時把所有的功能都編譯進去,就會導致內核很大,而且要往內核中添加或刪除功能時必須重新編譯內核
比如在Ubuntu在通用PC平台上,預先無法知道需要什么設備,就不知道預先編譯什么驅動。
3、內核模塊和應用程序的區別
|
工作模式 |
工作性質 |
層次 |
權限 |
影響 |
競態 |
運行方式 |
應用程序 | USR模式 |
策略性 |
用戶層 |
低 |
局部 |
局部 |
主動 |
內核模塊 | SVC模式 |
功能性 |
內核層 |
高 |
全局 |
全局 |
被擋 |
4、內核模塊的基本構成
|——兩個函數(一般需要) | |——模塊初始化(加載)函數:當內核模塊加載進內核的時候,做一些准備工作 | |——模塊卸載函數:回收、清理資源 | |——授權(許可證聲明)(必須):Linux內核受GPL(General Public License)授權約束 |——模塊參數(可選):模塊被加載時可以被傳遞給它的值,本身對應模塊內的全局變量 |——模塊導出符號(可選) |——模塊信息說明(可選)
5、模塊加載(初始化)函數
一般以 __init標識聲明
函數命名規則 xxx_init xxx設備名 init功能名(初始化)
函數形式:
static ini __init xxx_init(void) { /* 初始化代碼 * 返回值: 成功:0 失敗:負數,絕對值是錯誤碼 * 應用層得到的返回值是-1,錯誤碼保存到errno(每個進程有一個); 標准化errno.h已經明確定義linux/errno.h */ }
注冊方式: module_init(x); x為模塊初始化函數的首地址
6、模塊卸載函數
一般以 __exit標識聲明
函數命名規則 xxx_exit xxx設備名 exit功能名(卸載)
static ini __exit xxx_exit(void) { /* 釋放代碼 */ }
注冊方式: module_exit(x); x為模塊卸載函數的首地址
7、模塊許可證聲明
MODULE_LICENSE(_license) //_license就是授權名稱的字符串 //"GPL" [GNU Public License v2 or later] //"GPL v2" [GNU Public License v2] //"GPL and additional rights" [GNU Public License v2 rights and more] //"Dual BSD/GPL" [GNU Public License v2 or BSD license choice] //"Dual MIT/GPL" [GNU Public License v2 or MIT license choice] //"Dual MPL/GPL" [GNU Public License v2 or Mozilla license choice]
8、模塊聲明與描述
在Linux內核模塊中,我們可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分別來聲明模塊的作者、描述、版本、設備表和別名,例如:
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
對於USB、PCI等設備驅動,通常會創建一個MODULE_DEVICE_TABLE,表明該驅動模塊支持的設備,如:
/* 對應此驅動的設備列表 */
static struct usb_device_id skel_table [ ] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* 表結束 */ } }; MODULE_DEVICE_TABLE (usb, skel_table);
9、模塊參數:在加載模塊時,可以給模塊傳參
頭文件 linux/moduleparam.h
A、傳遞普通變量
module_param(name, type, perm); 聲明內核模塊參數 /* name - 接收參數的變量名 type - 變量類型 Standard types are: byte, short, ushort, int, uint, long, ulong charp: a character pointer bool: a bool, values 0/1, y/n, Y/N. invbool: the above, only sense-reversed (N = true) perm - 權限 頭文件 linux/stat.h #define S_IRWXUGO (S_IRWXU|S_IRWXG|S_IRWXO) #define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO) #define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH) #define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH) #define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH) */
范例:
int i = 0; module_param(i, int, 0644);
運行:
# insmod xxx.ko i=10
B、傳遞數組參數
module_param_array(name, type, nump, perm) /* 聲明內核模塊數組參數 name - 數組名 type - 數組成員類型 nump – 一個指向保存數組長度的整型變量的指針 perm - 權限 */
范例:
int arr[] = {1,2,3,4,5,6}; int len=0; module_param(arr, int, &len, 0644);
運行:
# insmod xxx.ko arr=1,2,3,4,5
C、傳遞字符串參數
module_param_string(name, string, len, perm) /* 聲明內核模塊字符串參數 name - 字符串緩存的外部名(傳入變量名) string - 字符串緩存的內部名 nump - 數組的數量 perm - 權限 */
范例:
char insidestr[] = "hello world"; module_param(extstr, insidestr, szieof(insidestr), 0644);
運行:
# insmod xxx.ko extstr="hello"
10、編譯內核模塊
如果一個內核模塊要加載到某個內核中運行,則這個模塊必須使用編譯該內核鏡像的源碼進行編譯,否則運行時會出錯
A、頭文件(語法問題)
B、編譯結果(最主要影響)
- 編譯時符號表(只在編譯時使用)
- 運行時內核符號表
- # cat /proc/kallsyms 運行時內核符號表
C、編譯系統
示例Makefile:
# 內核模塊的Makefile(模塊源碼在內核源碼外,且內核先編譯) # 1、找內核的Makefile # 2、內核的Makefile找內核模塊的Makeifle 內核模塊的Makeifle定義要編譯對象 ifneq ($(KERNELRELEASE),) #要編譯對象表示把demo.c編譯成demo.ko obj-m = demo.o else #內核源碼目錄 KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm -rf .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c
KERNELRELEASE 是在內核源碼的頂層Makefile中定義的一個變量,在第一次讀取執行此Makefile時,KERNELRELEASE沒有被定義,所以make將讀取執行else之后的內容。
當make的目標為all時,-C $(KERNELDIR) 指明跳轉到內核源碼目錄下讀取那里的Makefile;M=$(PWD) 表明然后返回到當前目錄繼續讀入、執行當前的Makefile。
當從內核源碼目錄返回時,KERNELRELEASE已被被定義,kbuild也被啟動去解析kbuild語法的語句,make將繼續讀取else之前的內容。else之前的內容為kbuild語法的語句, 指明模塊源碼中各文件的依賴關系,以及要生成的目標模塊名。
每個內核的名字都包含了它的版本號,這也是 uname -r 命令顯示的值。
11、操作內核模塊
A、加載模塊
- # insmode 模塊文件名
- # modprobe 模塊名 # depmod
- # modinfo 可以產看模塊信息
B、查看模塊
# lsmod
模塊名 模塊大小 使用計數 使用者(by沒有內容的是用戶模塊,有沒用的是內核模塊)
Module size Used by
demo 2333 0 (Used是當前有多少在用,為0時才允許被卸載)
mptscsih 39530 1 mptspi
C、卸載模塊
# rmmod 模塊名(Used為0時才允許被卸載)
D、查看內核輸出信息
# dmesg | tail –n 10 /* 查看內后最后十行信息 */
F、導出符號表
#define EXPORT_SYMBOL(導出符號表),符號可以是全局變量,也可以是函數