主題: linux核心模組的程式結構--模組載入函數(必須),模組卸載函數(必須),模組許可證聲明(必須),模組參數(可選),模組匯出符號(可選),模組作者的等資訊聲明(可選)
一個linux核心模組主要由以下幾個部分組成。
1、模組載入函數"用module_init()來指定"(必須)
當通過insmod和modprobe命令載入核心模組時,模組的載入函數會自動被核心執行,完成本模組的相關初始化工作。
linux模組載入函數一般以 __init表示聲明。典型聲明如下::
static int __init initialization_function(void)
{
/*初始化代碼*/
}
module_init(initialization_function);
模組載入函數必須使用module_init(函數名)的形式被指定。它返回整型值,若初始化成功,應返回0,而初始化失敗時,應返回錯誤編碼。在linux核心中,錯誤編碼是一個負值,在<linux/errno.h>中定義,包括-ENODEV、-ENOMEM之類的符號值。返回相應的錯誤編碼是種非常好的習慣,只有這樣,應用程式才能利用perror等方法把他們轉換成有意義的錯誤資訊字串。
在2.6核心中,可以使用“request_module(const char *fmt,...)函數”載入核心模組(注意:前面載入模組都是通過insmod和modprobe來實現的),驅動開發人員可以通過調用::
request_module(module_name);
或
request_module("char-major-%d-%d",MAJOR(dev),MINOR(dev));
來載入其他核心模組。
在linux核心中,所有表示為__init的函數在串連的時候放在.init.text這個區段內,此外,所有的__init函數在段.initcall.init中還儲存了一份函數指標,在初始化時,核心會通過這些指標調用這些__init函數,並在初始化完成後釋放init區段(.init.text,.initcall.init等)。
////////////////////////////////////////////////////////////////////
2、模組卸載函數"用module_exit()來指定"(必須)
當通過rmmod和modprobe -r命令卸載核心模組時,模組的卸載函數會自動被核心執行,完成與模組載入函數相反的功能。
linux核心模組於在函數一般以__exit表示說明,典型的模組卸載函數的形式如下::
static void __exit cleanup_function(void)
{
/*釋放代碼*/
}
module_exit(cleanup_function);
模組卸載函數在模組卸載的時候執行,不返回任何值,必須以"module_exit(函數名)"的形式來指定。
通常來說,模組卸載函數要完成與模組載入函數相反的功能,如下::
1>若模組載入函數註冊了XXX,則模組卸載函數應該登出XXX;
2>若模組記載函數的動態申請了記憶體,則模組函數應該釋放該該記憶體。
3>若模組載入函數申請了硬體資源(中斷,DMA通道、I/O連接埠和I/O記憶體等)的佔用,則模組卸載函數應該釋放這些硬體資源。
4>模組載入函數一般用來開啟硬體,模組卸載函數一般要關閉硬體。
和__init一樣,__exit也可以使用對應函數在運行完成後自動回收記憶體。實際上,__init和__exit都是宏,
分別定義為::
#define __init __attribute__((__section__(".init.text")))
和
#ifdef MODULE
#define __exit __attribute__((__section__(".exit.text")))
#else
#define __exit / __attribute__used____attribute((__section__(".exit.text")))
#endif
/////////////////////////////////////////////////////////////////////
3、模組許可證聲明"MODULE_LICENSE("Daul BSD/GPL")"(必須)
模組許可證(LICENSE)聲明描述核心模組的許可許可權,如果不聲明LICENSE,模組被載入時,將收到核心被汙染(kernel tainted)的警告。
在linux2.6核心中,可接受的LICENSE包括"GPL"、"GPL v2"、"GPL and additional rights"、"Dual BSD/GPL"、"Dual MPL/GPL"和"Proprietary"
大多數情況下,核心模組應遵循GPL相容許可權。linux2.6核心模組中最常見的是以MODULE_LICENSE("Dual BSD/GPL")語句聲明模組採用BSD/GPL雙LICENSE.
//////////////////////////////////////////////////////////////////////
4、模組參數(可選)
“模組參數”是“模組被載入的時候可以被傳遞給模組的值”,它本身對應模組內部的“全部變數”。
我們可以使用"module_param(參數名,參數類型,讀/寫入權限)"為模組定義一個參數,例如::下列代碼定義了一個整型參數和一個字元指標參數。
static char *book_name="深入淺出linux裝置驅動";
static int num = 4000;
module_param(num,int,S_IRUGO);
module_param(book_name,charp,S_IRGUO);
在裝載核心模組時,使用者可以向核心模組傳遞參數,
形式為"sudo insmod/modprobe 模組名(例如linux.ko) 參數名=參數值",若果不傳遞,參數將使用模組內定義的預設值。
向核心模組傳遞參數時,參數的類型可以是byte(位元組),short(短整型),ushort(無符號短整型),int,uint(無符號int),long,ulong(無符號long)、charp(字元指標)、bool或invbool(布爾的反),在模組被編譯時間會將module_param中聲明的類型與變數定義的類型進行比較,判斷是否一致。
模組被載入後,在/sys/module目錄下將出現以此模組名命名的目錄。當"參數讀/寫入權限"為0時,表示此“參數不存在sysfs檔案系統下對應的檔案節點”,如果此模組存在"參數讀/寫入權限"不為0的命令列參數,在此模組的目錄下將出現parameters目錄,包含一系列“以參數名命名的檔案節點”。同時,這些檔案的許可權就是通過傳入module_param()的"參數讀/寫入權限",而檔案的內容為參數的值。
除此之外,模組也可以擁有參數數組,形式為"module_param_array(數組名,數群組類型,數組長,參數讀/寫入權限)",在2.6.0~2.6.10版本,需將數組常變數名賦給"數組長",從2.6.10版本開始,需將數組長變數的指標賦給"數組長",當不需要儲存實際輸入的數組元數個數時,可以設定"數組長"為NULL。
運行insmod和modprobe命令時,應使用逗號分割輸入的數組元素。
例如::
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *book_name="Dissecting Linux Device Driber";
static int num=4000;
static int __init book_init(void)
{
printk(KERN_INFO"book name :%s/n",book_name);
printk(KERN_INFO"book num :%s/n",num);
return 0;
}
static void __exit book_exit(void)
{
printk(KERN_INFO"Book module exit/n");
}
module_init(book_init);
module_exit(book_exit);
module_param(num,int,S_IRUGO);
module_param(book_name,charp,S_IRUGO);
MODULE_AUTHOR("chenbaihu");
MODULE_VERSION("v1.0");
MODULE_DESCRIPTION("A simple Module for testing module params");
編譯該模組,Makefile為::
obj-m := module_param.o
kernel_path=/usr/src/kernels/2.6.29.6-217.2.16.fc11.i686.PAE
//核心路徑
all:
make -C $(kernel_path) M=$(PWD) modules
clean:
make -C $(kernel_path) M=$(PWD) clean
然後,運行make命令,進行編譯.產生module_param.ko檔案.
載入該模組.
第一種方案::
“sudo insmod module_param.ko”命令時,運行結果為::
book name :Dissecting Linux Device Driber
book num :4000
第二種方案::
“sudo insmod module_param.ko num=5000”命令時,運行結果為::
book name :Dissecting Linux Device Driber
book num :5000 //參數傳入了。
進入/sys/module/module_param/下,輸入tree命令::
.
|-- holders
|-- initstate
|-- notes
|-- parameters
| |-- book_name //模組參數檔案
| `-- num //模組參數檔案
|-- refcnt
|-- sections
| `-- __param
|-- srcversion
`-- version
//////////////////////////////////////////////////////////////////////
5、模組匯出符號(可選)
核心模組可以匯出符號(symbol,對應與函數或變數),這樣其他模組可以使用本模組中的變數和函數。
linux2.6的"/proc/kallsyms"檔案對應這核心符號表,它記錄了符號以及符號符號所在的記憶體位址。
模組可以使用如下宏匯出符號到核心符號表::
EXPORT_SYMBOL(符號名);
EXPORT_SYMBOL_GPL(符合名); //只是用於GPL許可權模組。
匯出的符合將可以被其他模組使用,使用前聲明以下既可以。
核心模組中的符號匯出(例子)::
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("Daul BSD/GPL");
int add_integar(int a,int b)
{
return a+b;
}
int sub_integer(int a,int b)
{
return a-b;
}
EXPORT_SYMBOL(add_integar); //匯出函數
EXPORT_SYMBOL(sub_integer); //匯出函數
編譯後,sudo insmod export_symbol.ko將該模組加入核心。
從"/proc/kallsyms"中可以找到add_integae/sub_integer相關資訊。
使用"cat /proc/kallsyms|grep integar"命令,就可以看到下面的結果::
f99f8048 r __ksymtab_add_integar [export_symbol]
f99f805c r __kstrtab_add_integar [export_symbol]
f99f8000 T add_integar [export_symbol]
6、模組作者等資訊(可選)
MODULE_AUTOR("作者資訊");
MODULE_DESCRIPTION("模組描述資訊");
MODULE_VERSION("版本資訊");
MODULE_ALIAS("別名資訊");
MODULE_DEVICE_TABLE("裝置表資訊");
對於USB,PCI等裝置驅動,通常會建立一個MODULE_DEVICE_TABLE,表示驅動所支援的裝置列表。