主題: 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,表示驅動所支持的設備列表。
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,表示驅動所支持的設備列表。
