Linux內核模塊


1、什么是內核模塊?

內核模塊是Linux提供的一種機制,允許在內核運行時動態加載進內核中,具有兩個特點:

         1)內核模塊本身不編譯入內核映像,有效控制縮減內核鏡像大小

         2)內核模塊一旦被加載,他就和內核中的其他部分完全一樣

2、為什么需要內核模塊?

如果在內核編譯時把所有的功能都編譯進去,就會導致內核很大,而且要往內核中添加或刪除功能時必須重新編譯內核

比如在Ubuntu在通用PC平台上,預先無法知道需要什么設備,就不知道預先編譯什么驅動。

3、內核模塊和應用程序的區別

 

工作模式

工作性質

層次

權限

影響

競態

運行方式

應用程序

USR模式

策略性

用戶層

局部

局部

主動

內核模塊

SVC模式

功能性

內核層

全局

全局

被擋

 

          

 

 

 

4、內核模塊的基本構成

|——兩個函數(一般需要)
|        |——模塊初始化(加載)函數:當內核模塊加載進內核的時候,做一些准備工作
|        |——模塊卸載函數:回收、清理資源
|
|——授權(許可證聲明)(必須):Linux內核受GPL(General Public License)授權約束
|——模塊參數(可選):模塊被加載時可以被傳遞給它的值,本身對應模塊內的全局變量
|——模塊導出符號(可選)
|——模塊信息說明(可選)

 5、模塊加載(初始化)函數

一般以 __init標識聲明

函數命名規則 xxx_init           xxx設備名       init功能名(初始化)

函數形式:

static ini __init  xxx_init(void)
{
         /* 初始化代碼
            * 返回值:           成功:0                    失敗:負數,絕對值是錯誤碼
* 應用層得到的返回值是-1,錯誤碼保存到errno(每個進程有一個);      標准化errno.h已經明確定義linux/errno.h
    */
}

注冊方式: module_init(x);  x為模塊初始化函數的首地址

 6、模塊卸載函數

一般以 __exit標識聲明

函數命名規則 xxx_exit          xxx設備名       exit功能名(卸載)

static ini __exit  xxx_exit(void)
{
         /* 釋放代碼 */
}

注冊方式: module_exit(x); x為模塊卸載函數的首地址

7、模塊許可證聲明

MODULE_LICENSE(_license)   //_license就是授權名稱的字符串
//"GPL"                            [GNU Public License v2 or later]
//"GPL v2"                         [GNU Public License v2]
//"GPL and additional rights"     [GNU Public License v2 rights and more]
//"Dual BSD/GPL"                     [GNU Public License v2 or BSD license choice]
//"Dual MIT/GPL"                     [GNU Public License v2 or MIT license choice]
//"Dual MPL/GPL"                     [GNU Public License v2 or Mozilla license choice]

 8、模塊聲明與描述

在Linux內核模塊中,我們可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分別來聲明模塊的作者、描述、版本、設備表和別名,例如:

MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);

對於USB、PCI等設備驅動,通常會創建一個MODULE_DEVICE_TABLE,表明該驅動模塊支持的設備,如:

/* 對應此驅動的設備列表 */

static struct usb_device_id skel_table [ ] = {
         {
                   USB_DEVICE(USB_SKEL_VENDOR_ID,
      USB_SKEL_PRODUCT_ID) },
       { } /* 表結束 */
         }
};
MODULE_DEVICE_TABLE (usb, skel_table);

9、模塊參數:在加載模塊時,可以給模塊傳參

頭文件 linux/moduleparam.h 

A、傳遞普通變量

module_param(name, type, perm);
聲明內核模塊參數
/*
name - 接收參數的變量名
type - 變量類型
  Standard types are:
  byte, short, ushort, int, uint, long, ulong
  charp: a character pointer
  bool: a bool, values 0/1, y/n, Y/N.
  invbool: the above, only sense-reversed (N = true)
perm - 權限
  頭文件 linux/stat.h
  #define S_IRWXUGO        (S_IRWXU|S_IRWXG|S_IRWXO)
  #define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)
  #define S_IRUGO              (S_IRUSR|S_IRGRP|S_IROTH)
  #define S_IWUGO             (S_IWUSR|S_IWGRP|S_IWOTH)
  #define S_IXUGO              (S_IXUSR|S_IXGRP|S_IXOTH)
*/

 范例:

int i = 0;
module_param(i, int, 0644);

運行:

# insmod xxx.ko i=10

 B、傳遞數組參數

module_param_array(name, type, nump, perm)
/*
聲明內核模塊數組參數
name - 數組名
type - 數組成員類型
nump – 一個指向保存數組長度的整型變量的指針
perm - 權限
*/

范例:

int arr[] = {1,2,3,4,5,6};
int len=0;
module_param(arr, int, &len, 0644);

運行:

# insmod xxx.ko arr=1,2,3,4,5

 C、傳遞字符串參數

module_param_string(name, string, len, perm)
/*
聲明內核模塊字符串參數
name - 字符串緩存的外部名(傳入變量名)
string - 字符串緩存的內部名
nump - 數組的數量
perm - 權限
*/

范例:

char insidestr[] = "hello world";
module_param(extstr, insidestr, szieof(insidestr), 0644);

運行:

# insmod xxx.ko extstr="hello"

 10、編譯內核模塊

如果一個內核模塊要加載到某個內核中運行,則這個模塊必須使用編譯該內核鏡像的源碼進行編譯,否則運行時會出錯

A、頭文件(語法問題)

B、編譯結果(最主要影響)

  • 編譯時符號表(只在編譯時使用)
  • 運行時內核符號表
  • # cat /proc/kallsyms 運行時內核符號表

C、編譯系統

示例Makefile:

# 內核模塊的Makefile(模塊源碼在內核源碼外,且內核先編譯)
# 1、找內核的Makefile
# 2、內核的Makefile找內核模塊的Makeifle
內核模塊的Makeifle定義要編譯對象
ifneq ($(KERNELRELEASE),)
#要編譯對象表示把demo.c編譯成demo.ko
  obj-m = demo.o
else
#內核源碼目錄
KERNELDIR :=  /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
  $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
  rm -rf .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c

KERNELRELEASE 是在內核源碼的頂層Makefile中定義的一個變量,在第一次讀取執行此Makefile時,KERNELRELEASE沒有被定義,所以make將讀取執行else之后的內容。

當make的目標為all時,-C $(KERNELDIR) 指明跳轉到內核源碼目錄下讀取那里的Makefile;M=$(PWD) 表明然后返回到當前目錄繼續讀入、執行當前的Makefile。

當從內核源碼目錄返回時,KERNELRELEASE已被被定義,kbuild也被啟動去解析kbuild語法的語句,make將繼續讀取else之前的內容。else之前的內容為kbuild語法的語句, 指明模塊源碼中各文件的依賴關系,以及要生成的目標模塊名。

每個內核的名字都包含了它的版本號,這也是 uname -r 命令顯示的值。

11、操作內核模塊

A、加載模塊

  1. # insmode 模塊文件名 
  2. # modprobe 模塊名   # depmod
  3. # modinfo  可以產看模塊信息

B、查看模塊

# lsmod

模塊名       模塊大小     使用計數         使用者(by沒有內容的是用戶模塊,有沒用的是內核模塊)

Module           size          Used     by

demo            2333               0                                 (Used是當前有多少在用,為0時才允許被卸載)

mptscsih      39530               1   mptspi

C、卸載模塊

# rmmod 模塊名(Used為0時才允許被卸載)

D、查看內核輸出信息

# dmesg | tail –n 10                  /* 查看內后最后十行信息 */

F、導出符號表

#define EXPORT_SYMBOL(導出符號表),符號可以是全局變量,也可以是函數

 


免責聲明!

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



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