linux內核模塊編程


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM