模塊
- 1 構建模塊
- 放在內核源代碼樹中
- 放在內核代碼外
- 2 安裝模塊
- 3 產生模塊依賴性
- 4 載入模塊
- 5 管理配置選項
- 6 模塊參數
- 7 導出符號表
Linux內核是模塊化組成的,它允許內核在運行時動態地向其中插入或從中刪除代碼。
與開發的內核核心子系統不同,模塊開發更接近編寫新的應用程序,因為至少要在模塊文件中具有入口點和出口點。
下面是hello_world內核模塊
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>static int hello_init(void)
{printk(KERN_ALERT "I bear a charmed life.\n");return 0;
}static void hello_exit(void)
{printk(KERN_ALERT "Out,out,brief candle !\n");
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shakespeare");
hello_init()函數是模塊的入口點,它通過module_init()注冊到系統中,在模塊裝載時被調用。調用module_init()實際上不是真正的函數調用,而是一個宏調用,它唯一的參數便是模塊的初始化函數。模塊的所有初始化函數必須符合下面的形式:
int my_init(void);
因為init函數通常不會被外部函數直接調用,所以不必導出該函數,故它可被標記為static類型。init函數會返回一個int型數據,如果初始化順利完成,那么它的返回值為0,失敗的話,返回一個非0值。
hello_exit()函數是模塊的出口函數,它由module_exit()注冊到系統,在模塊從內存卸載時,內核便會調用hello_exit()。在退出函數返回后,模塊就被卸載了。
退出函數必須符號以下形式:
void my_exit(void);
MODULE_LICENSE()宏用于指定模塊的版權,如果載入非GPL模塊到系統內存,則會在內核中設置被污染標志。MODULE_AUTHOR()宏指定代碼作者,完全是用作信息記錄目的。
1 構建模塊
在2.6內核中,由于采用了新的kbuild構建系統,現在構建模塊相比從前更加容易,構建過程中的第一步是決定在哪里管理模塊代碼,你可以把模塊源碼加入到內核源代碼樹中,也可以在內核源碼樹外維護構建模塊源碼。
放在內核源代碼樹中
最理想的情況莫過于模塊正式成為Linux內核的一部分,這樣就會被存放在內核源代碼樹中。
首先你要清楚你的模塊應該在內核源代碼樹中何處。設備驅動程序存放在內核源碼樹根目錄drivers/的子目錄下,在drivers內部,設備驅動文件被進一步細分。如字符設備存在于drivers/char/目錄下,而塊設備存放在drivers/block/目錄下,USB設備則存放在drivers/usb/目錄下。
假設你有一個字符設備,而且希望放在drivers/char/目錄下,那么你要注意,在該目錄下同時會存在大量的C源代碼文件和其他目錄。所以對于僅僅只有一兩個源文件的設備驅動程序,可以直接存放在該目錄下。如果驅動程序包含許多源文件和其他輔助文件,那么可以創建一個新子目錄。
假設你想創建自己代碼的子目錄,你的驅動程序是一個釣魚桿和計算機的接口,名為Fish Master XL 2000 Titanium,那么你應在drivers/char/目錄下建立一個名為fishing的子目錄。現在你需要項drivers/char/下的Makefile文件中添加一行。編輯drivers/char/Makefile加入:
obj-m += fishing/
這行編譯指令告訴模塊構建系統在編譯模塊時需要進入fishing/子目錄中。更可能發生的情況是,你的驅動程序的編譯取決于一個特殊配置選項,比如,可能的CONFIG_FISHING_POLE。如果這樣,你需要用下面的指針替代剛才那條指令:
obj-$(CONFIG_FISHING_POLE) += fishing/
最后,在drivers/char/fishing/下,需要添加一個新的Makefile文件,其中需要有下面這樣:
obj-m += fishing.o
此刻構建系統運行就將會進入fishing/目錄下,并且將fishing.c編譯為fishing.ko模塊,雖然拓展名是.o,但是模塊被編譯后的拓展名是.ko。
要是你的釣魚桿驅動程序編譯時有編譯選項,那么你可能需要這么來做:
obj-$(CONFIG_FISHING_POLE) += fishing.o
如果喜歡把源文件置于drivers/char/目錄下,并且不建立新目錄。那么你要做的便是將前面提到的行(也就是原來處于drivers/char/fishing/下你自己的Makefile中的)都加入到drivers/char/Makefile中。
開始編譯吧,運行內核構建過程,如果模塊編譯取決于配置選項,比如有CONFIG_FISHING_POLE約束,那么在編譯前首先要確保選項被允許。
放在內核代碼外
如果你喜歡脫離內核源代碼樹來維護和構建你的模塊,那么你要做的就是在你自己的源代碼樹目錄建立一個Makefile文件,它只需要一行指令:
obj-m := fishing.o
就可以把fishing.c編譯成fishing.ko。
模塊在內核內或內核外構建的最大區別 在于構建過程。當你的模塊在內核源代碼樹外時,你必須告訴make如何找到內核源代碼文件和基礎的Makefile文件。
make -C /kernel/sourece/location SUBDIRS=$PWD modules
/kernel/source/location是你以配置的內核源碼樹。
2 安裝模塊
編譯后的模塊將被裝入到目錄/lib/modules/version/kernel/下。比如,如果使用的是2.6.10內核,而且你將你的模塊源代碼直接放在drivers/char/下,那么編譯后的釣魚桿驅動程序的存放路徑是:/lib/modules/2.6.10/kernel/drivers/char/fishing.ko
下面的構建命令用來安裝編譯的模塊到合適的目錄下:
make modules_install
3 產生模塊依賴性
Linux模塊之間存在依賴性,也就是說釣魚模塊依賴魚餌模塊,那么當載入釣魚模塊時,魚餌模塊會被自動載入,這里需要的依賴信息必須事先生成。若想產生內核依賴關系的信息,root用戶可運行命令:
depmod
為了執行更快的更新操作,可以只為新模塊生成依賴信息,而不是生成所有的依賴關系,這時root用戶可運行命令:
depmod -A
模塊依賴關系信息存放在/lib/modules/version/modules.dep文件中
4 載入模塊
載入模塊最簡單的方法是通過insmod命令,這是個功能很有限的命令,它的作用就是請求內核載入你指定的模塊。insmod程序不執行任何依賴性分析或進一步的錯誤檢查,它用法簡單,以root運行命令:
insmod module
需要載入的模塊名稱由參數module指定,比如裝載釣魚桿模塊,那你就執行命令:
insmod fishing
卸載一個模塊,你可使用rmmod命令,同樣用root身份執行:
rmmod module
比如,rmmod fishing將卸載釣魚桿模塊。
系統為我們提供了一個更先進的工具modprobe,它提供了模塊依賴性分析,錯誤智能檢查,錯誤報告以及許多其他功能和選項,推薦用這個命令:
modprobe module [module parameters]
module指定需要載入的模塊名稱,后面的參數將在模塊加載時傳入內核。modprobe命令不但會加載指定的模塊,而且會自動加載任何它所依賴的有關模塊。所以說它是加載模塊的最佳技術。
modprobe命令也可用來從內核中卸載模塊,當然這也需要root身份運行。
modprobe -r modules
參數modules指定一個或多個需要卸載的模塊,與rmmod命令不同,modprobe也會卸載給定模塊所依賴的相關模塊,前提是這些相關模塊沒有被使用。
5 管理配置選項
2.6內核中新引入了kbuid系統,加入一個新配置選項是很容易的。你所需做的全部就是向Kconfig文件中添加一項,用于對應內核源碼樹。對驅動程序而言,Kconfig通常和源碼處于同一目錄。如果釣魚桿驅動程序子drivers/char/下,那么你便會發現drivers/char/Konfig同時存在。
如果你建立了一個新子目錄,而且也希望Kconfig文件存在于該目錄中的話,那么必須在一個已存在的Kconfig文件中將它引入:
source "drivers/char/fishing/Kconfig"
可以很方便地在Kconfig文件中加入一個配置選項,請看釣魚桿模塊的選項如下所示:
配置選項第一行定義了該選項所代表的配置文件,注意CONFIG_前綴不需要寫上。這個就是構建模塊時,在Makefile里加入的特殊配置選項CONFIG_FISHING_POLE。
第二行聲明選項類型為tristate,也就是說被編譯進內核(Y),也可作為模塊編譯(M),或干脆不編譯它(N),選Y,CONFIG_FISHING_POLE的值就是Y,依次類推。如果編譯選項代表的是一個系統功能,而不是一個模塊,那么編譯選項將用bool代替tristate,這說明它不允許被編譯成模塊。處于指令之后的引號內文件為該選項指定了名稱。
第三行指定了該選項的默認選擇,這里默認操作是不編譯它。
help指令是為該選項提供幫助文檔。
除了上述選項外,還存在其他選項。
6 模塊參數
Linux允許驅動程序聲明參數,從而用戶可以在系統啟動或者模塊裝載時再指定參數值,這些參數對于你的驅動程序屬于全局變量,模塊參數同時也將出現在sysfs文件系統中。
定義一個模塊參數可通過宏module_param()完成:
module_param(name,type,perm);
參數name既是用戶可見的參數名,也是模塊中存放模塊參數的變量名。參數type則存放參數的類型,最后一個參數perm指定了模塊在sysfs文件系統下對應文件的權限,該值可以是八進制格式,比如0666,或是S_Ifoo的定義形式,比如S_IRUGO|S_IWUSR,如果該值為0,則表示禁止所有的sysfs項。
上面的宏并沒有定義變量,你必須在使用該宏前進行變量定義。通常使用類似下面的語句完成定義。
static int allow_live_bait = 1;
module_param(allow_live_bait,bool,0666);
有可能模塊的外部參數名稱不同于它對應的內部變量名稱,這是就該使用宏module_param_named()定義了:
module_param_named(name,variable,type,perm);
參數name是外部可見的參數名稱,參數variable是參數對應的內部全局變量名稱。比如:
static unsigned int max_text = DEFAULT_MAX_LINE_TEST;
module_param_named(maximum_line_test,max_test,int ,0)
其他宏:
module_param_string(name,string,len,name); /* 拷貝字符串到指定的字符數組 */
module_param_array(name,type,nump,perm); /* 接受逗號分割的參數序列 */
module_param_array_named(name,array,type,nump,perm); /* 將內部參數數組命名區別于外部參數 */
上述所有宏被定義在linux/moduleparam.h文件中。
7 導出符號表
模塊被載入后,就會動態連接到內核。注意,它與用戶空間中的動態連接庫類型,只有當被顯式導出后的外部函數,才可以被動態庫調用。在內核中,導出內核內核函數需要使用特殊的指令:
EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()。
導出的內核函數可以被模塊調用,而未導出的函數模塊則無法使用。導出的內核符號表被看做是導出的內核接口,甚至稱為內核API。
導出符號相當簡單,在聲明函數后緊跟上EXPORT_SYMBOL()指令就搞定了,比如:
int get_priate_beard_color(void)
{return pirate->beard->color;
}
EXPORT_SYMBOL(get_priate_beard_color);
假定get_priate_beard_color同時也定義在一個可訪問的文件中,那么任何模塊現在都可以訪問它。
有一些開發者希望自己的接口僅僅對GPL兼容的模塊可見,內核連接器使用MODULE_LICENSE()宏可滿足這個要求,如果你希望先前的函數僅僅對標記為GPL協議的模塊可見,那么你就需要用:
EXPORT_SYMBOL_GPL(get_priate_beard_color);
如果你的代碼被設置為模塊,那么就必須確保它被編譯為模塊時所用的全部接口已被導出,否則就會產生連接錯誤(而且模塊不能成功編譯)。