Linux驅動--(四)內核模塊參數詳解

Linux驅動-內核模塊參數

一、概述

前面幾篇文章中,我們簡單說了一個簡單的內核模塊的編寫。在那裏我說過模塊加載函數接收參數。那麼如果我們想要通過傳參對模塊進行控制那不是很難辦到了。別急,Linux提供了一種命令行的方式來傳遞參數信息,就是所謂的模塊參數

模塊參數:簡單來說模塊參數允許用戶再加載模塊時通過命令行指定參數值,在模塊的加載過程中,加載程序會得到命令行參數,並轉換成相應類型的值,然後複製給對應的變量,這個過程發生在調用模塊初始化函數之前。

內核支持的參數類型主要有:bool、字符串指針、short、int、long、ushort、uint、ulong。這些類型又可以複合成對應的數組類型。

下面申明兩個變量,用於將變量申明爲模塊參數。

module_param(name,type,perm);
module_param_array(name,type,nump,perm);

@name:變量的名字
@type:變量或數組元素的類型
@nump:數組元素個數的指針,可選
@perm:在sysfs文件系統中對應的文件的權限屬性。可參考				       <linux/stat.h>,和普通文件的權限一樣,但是當perm爲0時,	       sysfs文件文件系統中將不會出現對應的文件。
//備註一下,sysfs文件系統可用於內核和應用程序間通信的一種方式

下面來舉個例子,還是利用前面的代碼:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

static int baudrate = 9600;
static int port[4] = {0,1,2,3};
static char *name = "vser";

module_param(baudrate,int,S_IRUGO);
module_param_array(port,int,NULL,S_IRUGO);
module_param(name,charp,S_IRUGO);

static int __init hello_init(void)
{
    int i;
    
    printk("init module:hello world!\n ");
    printk("baudrate:%d\n");
    for(i = 0;i < ARRAY_SIZE(port);i++)
    {
        printk("%d",port[i]);
    }
    printk("\n");
    printk("name: %s\n",name);
    
    return 0;
}

static void __exit hello_exit(void)
{
    printk("exit module:hello_exit!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Fan <[email protected]>");
MODULE_DESCRIPTION("A hello module");
MODULE_ALIAS("printf_hello");

​ 編譯完成後,加載模塊。如果不指定模塊參數的值,那麼就會打印函數中已 經定義的值。如果按照如下指定:

# depmod
# modprobe vser baudrate=115200 port=1,2,3,4 name="hello"
# dmesg

​ 我們會發現內核會按照我們指定的參數進行打印,而原來的參數值會被覆蓋掉。

​ 同樣的我們可以去/sys/mododule/hello/ 目錄下面去看看我們給參數。

二、內核模塊的依賴關係

  • 在前面的代碼中,我們使用了printk函數。我們知道他是內核函數,可是我們在只build模塊的情況下,原來的代碼竟然build過了。這是爲什麼呢?

    原來,我們剛纔的代碼並沒有僅僅是進行了編譯並沒有進行鏈接。編譯出來的hello.ko文件是一個普通的ELF目標文件,使用file命令和nm命令可以得到該模塊的相關的細節信息。

    # file hello.ko
    # nm hello.ko
    

    此時呢,printk對整個文件而言是一個未決符號,因爲他放在其他文件中(內核源碼:/kernel/printk/printk.c)。所以我們編譯時並不知道這個函數的地址,那麼我們該怎麼解決呢?其實這裏Linux使用了一個很巧妙的類似於動態鏈接的方式。下面來說明一下:

    首先介紹一下EXPORT_SYMBOL的宏將printk導出,其目的是爲動態加載的模塊提供地址信息。(當然啦,此處我們也可以使用另一個宏:EXPORT_SYMBOL_GPL來進行導出)

    我們可以在Linux的源碼該函數的實現代碼中找到這樣一行代碼:

    EXPORT_SYMBOL(printk);
    

    其目的是爲動態加載的模塊提供printk的地址信息。

    工作原理大概是這樣的:利用EXPORT_SYMBOL宏生成一個特定的結構並放到ELF文件的一個特定的段中,在內核的啓動過程中,會將符號確切地址填充到這個結構的特定成員中。模塊加載時,加載程序將去處理未決符號,在特殊段彙總搜索符號的名字,如果找到,則將獲得的地址填充在被加載模塊的相應段中,這樣符號的地址就可以確定了。利用這種方式處理未決符號,就相當於把連接的過程推後進行了動態鏈接。和普通的應用程序使用共享庫函數的道理是類似的。內核中有大量的符號導出,爲模塊提供豐富的基礎設施。

    同樣的,推廣一下,如果一個模塊需要提供全局變量或函數給到另外的模塊使用,那麼就需要將這些符號導出。

    這樣細心的小夥伴就會發現了,兩個模塊之間會存在依賴關係。

    爲了方便我們這裏命名兩個不同的模塊:模塊A、模塊B。

    模塊A提供給了模塊B一些全局變量和全局函數。

    1. 如果我們在使用insmod命令加載模塊B時,就必須先加載模塊A。因爲模塊B使用了模塊A導出的符號。如果沒有加載模塊A而先加載模塊B,那麼加載時就會失敗,因爲加載過程中因爲處理那些未決符號而失敗。

      這裏我們要說明一下,上一篇博客提到的模塊加載指令modprobe命令優於insmod的地方了。modprobe可以自動加載被依賴的模塊,而這又歸功於depmod命令將會生成模塊的依賴信息保存在/lib/modules/3.14.0-32-gener.c/modules.dep文件中。3.14.0-32-gener.c爲內核源碼版本。

    2. 因爲兩個模塊存在依賴關係,如果分別編譯連個模塊,將會出現警告,並且幾遍加載順序正確,加載也不會成功。

      這是因爲,在編譯模塊時,模塊B在內核符號表中找不到由模塊A提供的一些函數和變量,而這些符號也不知道模塊A的存在。解決的辦法有兩個:1.將兩個模塊一起編譯。2.將模塊A放到內核源碼中,現在內核源碼下編譯完所有的模塊再編譯模塊B。

    3. 卸載模塊時,要先卸載模塊模塊B,在卸載模塊A。否則,因爲模塊A會被模塊B使用而無法卸載。

      內核將會創建模塊依賴關係的鏈表,只有當這個模塊的依賴鏈表爲空時,模塊才能被卸載。

三、總結

  • Linux內核是一個開源的項目,世界上有很多優秀的開發者在對齊進行着維護。爲了與時俱進,Linux內核也在不斷的更新換代。而這對於我們這些內核開發者而言,影響是巨大。因爲每一次更新換代,Linux就有可能將一些以前的接口等進行修改,或者直接被剔除內核。那麼如果改動了,我們之前編寫的內核模塊的調用的接口部分,那麼對我們的影響是巨大的。內核模塊.ko文件中會記錄內核源碼信息、系統結構想你想、函數接口信息等,在開啓版本控制選項的內核中加載一個模塊時,齧合將覈對這些信息,如果不一致就會拒絕加載。所以呢,作爲一個內核開發者,我們應該時刻關注Linux版本的更新詳情。當然啦,如果你一直用同一個版本進行開發,而不到算進行跟換內核版本的話也是沒有影響的。

  • 下面總結一下內核莫誇與普通應用程序之間的區別:

    1. 內核模塊時操作系統的一部分,運行在內核空間;而應用程序運行在用戶空間。
    2. 內核模塊中的函數是被動的調用,比如模塊的初始化函數和清除函數分別在模塊加載和卸載時調用,而應用程序則是順序執行,然後通常進入一個循環反覆調用某些函數。
    3. 內核模塊處於C函數庫之下,自然不能調用C庫函數,當然啦,內核源碼中會有相似的實現,例如printk函數;而應用程序卻可以隨意調用C庫函數。
    4. 內核模塊要做一些清除性的工作,比如在一個操作失敗後或者在誒和的清除函數中;而應用程序的有些工作通常可以不用做,比如在程序退出前關閉打開的文件。因爲操作系統(內核)會幫它去做。
    5. 內核模塊如果產生了非法訪問將會導致整個系統的崩潰(如野指針),所以在進行內核塊編程的時候,一定要注意指針的使用;而應用程序訪問了非法內存只會影響自己,因爲操作系統(內核)會幫它去處理。
    6. 內核模塊中的併發更多,比如中斷、多處理器;而應用程序中一般只考慮多進程或多線程。
    7. 整個內核的空間調用鏈上只有4kb或8kb的棧,相對於應用程序來說非常的小。如果需要更大內存空間通常需要動態分配。
    8. 雖然printk和printf的行爲非常相似,但是通常printk不支持浮點數,例如要打印一個浮點變量。編譯時會出現警告,並且模塊也不會加載成功。內核開發中應該避免使用浮點數。

    好啦,說了這麼多。下一步分開始字符設備的驅動了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章