摘要
Linux核心模組編程的資料有些紛繁複雜,有的過於簡單,有的過於龐雜,我試圖用筆記的形式想讀者展示怎樣來進程Linux模組編程,力圖做到簡 明扼要,這篇文章也是作為本人備忘的資料,所以有些地方過於簡略是難免的。本來這篇文章的目的就是讓使用者知其然,至於所以然還是請參考相應的資料,其實最 好的資料莫過於Linux Kernel Source。
適用範圍:
#sudo apt-get install linux-kernel-devel
安裝核心標頭檔
#sudo apt-get install linux-headers-`uname -r`
當然, gcc /make 等工具天生就是需要的。
安裝kernel 必須的開發庫
Linux模組簡介
首先這個module不同於microkernel的module,microkernel的module是一個個的daemon進程,工作於使用者 空間,Linux的module只是一個核心的目標代碼,核心通過執行運行時的串連,來把它整合到kernel中去,所以說Linux的module機制 並沒有改變Linux核心為monolithic OS本質,其module也是工作於核心模式,享有核心的所有特權。
至於為什麼要引入Linux Kernle Module(一下簡稱LKM),我想至少有一下幾點:
- 模組化編程的需要,降低開發和維護成本。
- 增強系統的靈活性,使得修改一些核心功能而不必重新編譯核心和重啟系統。
- 降低核心編程的複雜性,使入門門檻降低。
相關宏及標頭檔
LKM需要包含以下標頭檔:<linux/kernel.h> <linux/module.h>
需要定義以下宏:__KERNEL__, MODULE
一個簡單的核心模組樣本
/*file: hello.c*/
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_ALERT "Hello, my LKM."n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Bye, my LKM."n");
}
module_init(hello_init);
module_exit(hello_exit);
很簡答吧,不是嗎?這個LKM的功能其實也很簡單,就是當通過insmod載入它的時候,他列印Hello, my LKM.通過rmmod卸載它的時候他列印bye, my LKM.一個最基本的核心模組一般都包含有兩個函數,一個是初始化函數(比如說這裡的hello_init),一個是卸載函數(hello_exit), 當然也可以沒有任何函數,只是提供一些變數。但是初始化函數和卸載函數必須成對出現。並且init函數當操作成功時傳回值大於等於零,當操作失敗時,返回 非零。宏module_init和module_exit用於註冊初始化函數和卸載函數。
LKM的編譯
一個樣本的Makefile如下所示
obj-m := hello.o
KERNELBUILD := /lib/modules/`uname -r`/build
default:
make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
rm -rf *.o .*.cmd *.ko *.mod.c .tmp_versions
如果這個目錄下面還有其它模組,只需要在hello.o後面添加就行了。
obj-m := hello.o mod.o
在模組所在目錄執行make,等成功後就可以得到我們想要的模組(hello.ko)了。
如果一個模組存在許多源檔案,比如:hello, 由hello1.c hello2.c共同串連而成,需要在Makefile中加入如下行
hello-objs := hello1.o hello2.o
LKM的載入
Linux為使用者提供了modutils,用來操縱模組。這個工具集主要包括:
insmod 安裝模組
rmmod 刪除模組
modprobe 比較進階的載入和刪除模組,可以解決模組之間的依賴性
lsmod 列出已經載入的模組和其資訊
modinfo 用於查詢模組的相關資訊,比如作者,著作權...
試著用命令insmod hello.ko載入模組,rmmod刪除模組,看看有什麼事情發生了。你有可能看不見任何輸出,難道是有錯誤發生?No,執行命令tail /var/log/message呵呵,是不是看到了?
Feb 19 00:07:35 gentux Hello, my LKM.
Feb 19 00:07:38 gentux Bye, my LKM.
也可以用dmesg 就可以看到 產生的核心message
模組其它資訊
比較常用資訊常常包括:作者、描述、著作權等,為此LKM為我們提供了如下宏:
MODULE_AUTHOR("author");
MODULE_DESCRIPTION("the description");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("dev"); // 裝置驅動程式所支援的裝置。
比較常用的Free license有"GPL", "GPL v2", "GPL and additional rights", "Dual BSD/GPL", "Dual MPL/GPL"。
模組參數
使用者空間的應用程式可以接受使用者的參數,LKM也可以做到,只是方式有些不同而已。相關的宏有:
MODULE_PARM(var, type);
MODULE_PARM_DESC(var, "the description of the var");
模組參數的類型(即MODULE_PARM中的type)有一下幾種:
- b byte(unsigned char)
- h short
- i int
- l long
- s string(char*)
這些參數最好有預設值,如果有些必要參數使用者沒有設定可以通過在module_init指定的init函數返回負值來拒絕模組的載入。 LKM還支援數群組類型的模組,如果在類型符號前加上數字n則表示最大程度為n的數組,用“-”隔開的數字分別代表最小和最大的數組長度。
樣本:
MODULE_PARM(var, "4i"); // 最大長度為4的整形數組
MODULE_PARM(var, "2-6i"); // 最小長度為2,最大長度為6的整形數組
如何用insmod傳入參數,其實man一下就可以了,不過現在的man有些過於簡單,所以在此說明一下:
insmod variable=value[,value2...] ...
其中value可以用引號括起來,也可以不用。但是有一點“=”前後不能留有空格,並且value中也不能有空格。
模組符號的匯出
和使用者空間的應用程式不同的是,引入一個模組的目的常常是為了給核心提供一些routine,來完成特定的功能,很少有模組什麼符號都不匯出,為此Linux為使用者提供了如下宏:
EXPORT_SYMBOL(var); // 輸出symbol var
EXPORT_SYMBOL_GPL(var); // 輸出的symbol著作權為GPL
模組之間的依賴性
有的時候兩個模組之間可能有依賴性,要載入的模組A,依賴於模組B,此時insmod是無能為力的,只能用modprobe來載入模組和其依賴的模組,否則只能手動一個個載入。
modprobe通過讀取由depmod -a產生的/lib/modules/version/modules.dep來獲得其所依賴的模組列表(也有可能是一個模組樹),然後調用insmod來一個個按順序載入。
命名空間的問題
- 對於不需要export的全域symbol最好用static進行修飾,限制其範圍為本檔案,以防汙染核心的命名空間。
- 對於由核心或其它模組export的一些symbol,最好用extern進行修飾,以示其不在本檔案。
- 在可能用到errno變數的場合,因為核心沒有export此symbol,只能有使用者自行定義,比如:int errno;
一個較複雜的模組樣本
/*file: hello.c*/
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_AUTHOR("xiaosuo <xiaosuo@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("This module is a example.");
static int int_var = 0;
static const char *str_var = "default";
static int int_array[6];
MODULE_PARM(int_var, "i");
MODULE_PARM_DESC(int_var, "A integer variable");
MODULE_PARM(str_var, "s");
MODULE_PARM_DESC(str_var, "A string variable");
MODULE_PARM(int_array, "2-6i");
MODULE_PARM_DESC(int_array, "A integer array");
static int __init hello_init(void)
{
int i;
printk(KERN_ALERT "Hello, my LKM."n");
printk(KERN_ALERT "int_var %d."n", int_var);
printk(KERN_ALERT "str_var %s."n", str_var);
for(i = 0; i < 6; i ++){
printk("int_array[%d] = %d"n", i, int_array[i]);
}
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Bye, my LKM."n");
}
module_init(hello_init);
module_exit(hello_exit);
參考資料
The Linux Kernel Module Programming Guide
Linux 核心編程指南(第三版)
Linux核心分析及編程