Linux核心模組編程與核心模組LICENSE

來源:互聯網
上載者:User

Linux核心模組簡介Linux核心的整體結構已經非常龐大,而其包含的組件也非常多。我們怎樣把需要的部分都包含在核心中呢?
一種方法是把所有需要的功能都編譯到Linux核心。這會導致兩個問題,一是產生的核心會很大,二是如果我們要在現有的核心中新增或刪除功能,將不得不重新編譯核心。
有沒有一種機制使得編譯出的核心本身並不需要包含所有功能,而在這些功能需要被使用的時候,其對應的代碼被動態地載入到核心中呢?
Linux提供了這樣的一種機制,這種機制被稱為模組(Module)。模組具有這樣的特點。
為了使讀者建立對模組的初步感性認識,我們先來看一個最簡單的核心模組“Hello World”,如代碼清單4.1所示。
代碼清單4.1  一個最簡單的Linux核心模組

01 /*02  * a simple kernel module: hello03  *04  * Copyright (C) 2014 Barry Song  (baohua@kernel.org)05  *06  * Licensed under GPLv2 or later.07  */0809 #include <linux/init.h>10 #include <linux/module.h>1112 static int __init hello_init(void)13 {14     printk(KERN_INFO "Hello World enter\n");15     return 0;16 }17 module_init(hello_init);1819 static void __exit hello_exit(void)20 {21     printk(KERN_INFO "Hello World exit\n ");22 }23 module_exit(hello_exit);2425 MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");26 MODULE_LICENSE("GPL v2");27 MODULE_DESCRIPTION("A simple Hello World Module");28 MODULE_ALIAS("a simplest module");

這個最簡單的核心模組只包含核心模組載入函數、卸載函數和對GPL v2許可許可權的聲明以及一些描述資訊,位於本書配套原始碼的/kernel/drivers/hello目錄。編譯它會產生hello.ko目標檔案,通過“insmod ./hello.ko”命令可以載入它,通過“rmmod hello”命令可以卸載它,載入時輸出“Hello World enter”,卸載時輸出“Hello World exit”。
核心模組中用於輸出的函數是核心空間的printk()而非使用者空間的printf(),printk()的用法和printf()基本相似,但前者可定義輸出層級。printk()可作為一種最基本的核心調試手段,在Linux驅動的調試章節中將詳細講解這個函數。
在Linux中,使用lsmod命令可以獲得系統中載入了的所有模組以及模組間的依賴關係,例如:
Module                  Size  Used byhello                   9 472  0nls_iso8859_1          12 032  1nls_cp437              13 696  1vfat                   18 816  1fat                    57 376  1 vfat...

lsmod命令實際上讀取並分析“/proc/modules”檔案,與上述lsmod命令結果對應的“/proc/modules”檔案如下:
$ cat /proc/moduleshello 12393 0 - Live 0xe67a2000 (OF)nls_utf8 12493 1 - Live 0xe678e000isofs 39596 1 - Live 0xe677f000vboxsf 42561 2 - Live 0xe6767000 (OF)...
核心中已載入模組的資訊也存在於/sys/module目錄下,載入hello.ko後,核心中將包含/sys/module/hello目錄,該目錄下又包含一個refcnt檔案和一個sections目錄,在/sys/module/hello目錄下運行“tree –a”得到如下分類樹:
root@barry-VirtualBox:/sys/module/hello# tree -a.├── coresize├── holders├── initsize├── initstate├── notes│   └── .note.gnu.build-id├── refcnt├── sections│   ├── .exit.text│   ├── .gnu.linkonce.this_module│   ├── .init.text│   ├── .note.gnu.build-id│   ├── .rodata.str1.1│   ├── .strtab│   └── .symtab├── srcversion├── taint└── uevent3 directories, 15 files

modprobe命令比insmod命令要強大,它在載入某模組時,會同時載入該模組所依賴的其他模組。使用modprobe命令載入的模組若以“modprobe -r filename”的方式卸載將同時卸載其依賴的模組。模組之間的依賴關係上存放在根檔案系統的/lib/modules/<kernel-version>/modules.dep檔案中,實際上是在整體編譯核心的時候由depmod工具產生的,它的格式非常簡單:
kernel/lib/cpu-notifier-error-inject.ko: kernel/lib/notifier-error-inject.kokernel/lib/pm-notifier-error-inject.ko: kernel/lib/notifier-error-inject.kokernel/lib/lru_cache.ko:kernel/lib/cordic.ko:kernel/lib/rbtree_test.ko:kernel/lib/interval_tree_test.ko:updates/dkms/vboxvideo.ko: kernel/drivers/gpu/drm/drm.ko

使用modinfo <模組名>命令可以獲得模組的資訊,包括模組作者、模組的說明、模組所支援的參數以及vermagic:
# modinfo hello.kofilename:       hello.koalias:          a simplest moduledescription:    A simple Hello World Modulelicense:        GPL v2author:         Barry Song <21cnbao@gmail.com>srcversion:     081230411494509792BD4A3depends:        vermagic:       3.8.0-39-generic SMP mod_unload modversions 686

Linux核心模組程式結構一個Linux核心模組主要由如下幾個部分組成。
(1)模組載入函數
當通過insmod或modprobe命令載入核心模組時,模組的載入函數會自動被核心執行,完成本模組的相關初始化工作。
(2)模組卸載函數
當通過rmmod命令卸載某模組時,模組的卸載函數會自動被核心執行,完成與模組卸載函數相反的功能。
(3)模組許可證聲明
許可證(LICENSE)聲明描述核心模組的許可許可權,如果不聲明LICENSE,模組被載入時,將收到核心被汙染 (kernel tainted)的警告。
在Linux核心模組領域,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”(關於模組是否可以採用非GPL許可權如“Proprietary”,這個在學術界和法律界都有爭議)。
大多數情況下,核心模組應遵循GPL相容許可權。Linux核心模組最常見的是以MODULE_LICENSE( "GPL v2" )語句聲明模組採用GPL v2。
(4)模組參數(可選)。
模組參數是模組被載入的時候可以被傳遞給它的值,它本身對應模組內部的全域變數。
(5)模組匯出符號(可選)。
核心模組可以匯出符號(symbol,對應於函數或變數),這樣其他模組可以使用本模組中的變數或函數。
(6)模組作者等資訊聲明(可選)。

 模組載入函數Linux核心模組載入函數一般以_ _init標識聲明,典型的模組載入函數的形式如代碼清單4.2所示。
代碼清單4.2  核心模組載入函數
1     static int _ _init initialization_function(void)2     {    3         /* 初始化代碼 */4     }5     module_init(initialization_function);

模組載入函數則以“module_init(函數名)”的形式被指定。它返回整型值,若初始化成功,應返回0。而在初始化失敗時,應該返回錯誤編碼。在Linux核心裡,錯誤編碼是一個接近於0的負值,在<linux/errno.h>中定義,包含-ENODEV、-ENOMEM之類的符號值。總是返回相應的錯誤編碼是種非常好的習慣,因為只有這樣,使用者程式才可以利用perror等方法把它們轉換成有意義的錯誤資訊字串。
在Linux核心中,可以使用request_module(const char *fmt, …)函數載入核心模組,驅動開發人員可以通過調用
request_module(module_name);
這種靈活的方式載入其他核心模組。
在Linux中,所有標識為_ _init的函數如果直接編譯進入核心,成為核心鏡像的一部分,在串連的時候都放在.init.text這個區段內。
#define _ _init        _ _attribute_ _ ((_ _section_ _ (".init.text")))
所有的_ _init函數在區段.initcall.init中還儲存了一份函數指標,在初始化時核心會通過這些函數指標調用這些_ _init函數,並在初始化完成後,釋放init區段(包括.init.text、.initcall.init等)的記憶體。
除了函數以外,資料也可以被定義為_ _initdata,對於只是初始化階段需要的資料,核心在初始化完後,也可以釋放它們佔用的記憶體。例如,下面的代碼中將hello_data定義為__initdata。
static int hello_data __initdata = 1;static int __init hello_init(void){    printk(KERN_INFO "Hello, world %d\n", hello_data);    return 0;}module_init(hello_init);static void __exit hello_exit(void){    printk(KERN_INFO "Goodbye, world\n");}module_exit(hello_exit);

模組卸載函數
Linux核心模組載入函數一般以_ _exit標識聲明,典型的模組卸載函數的形式如代碼清單4.3所示。
代碼清單4.3  核心模組卸載函數
1    static void _ _exit cleanup_function(void)2    {3          /* 釋放代碼 */4    }5    module_exit(cleanup_function);

模組卸載函數在模組卸載的時候執行,不返回任何值,必須以“module_exit(函數名)”的形式來指定。通常來說,模組卸載函數要完成與模組載入函數相反的功能。
我們用__exit來修飾模組卸載函數,可以告訴核心如果相關的模組被直接編譯進核心(即built-in),則cleanup_function() 函數會被省略直接不串連進最後的鏡像。既然模組被built-in了,就不可能卸載它了,卸載函數也就沒有存在的必要了。除了函數以外,只是退出階段採用的資料也可以用__exitdata來形容。

模組參數
我們可以用“module_param(參數名,參數類型,參數讀/寫入權限)”為模組定義一個參數,例如下列代碼定義了1個整型參數和1個字元指標參數:
static char *book_name = "dissecting Linux Device Driver";module_param(book_name, charp, S_IRUGO);static int book_num = 4000;module_param(book_num, int, S_IRUGO);

在裝載核心模組時,使用者可以向模組傳遞參數,形式為“insmode(或modprobe)模組名 參數名=參數值”,如果不傳遞,參數將使用模組內定義的預設值。如果模組被built-in,就無法insmod了,但是bootloader可以通過在bootargs裡設定“模組名.參數名=值”的形式給該built-in的模組傳遞參數。
參數類型可以是byte、short、ushort、int、uint、long、ulong、charp(字元指標)、bool或invbool(布爾的反),在模組被編譯時間會將module_param中聲明的類型與變數定義的類型進行比較,判斷是否一致。
除此之外,模組也可以擁有參數數組,形式為“module_param_array(數組名,數群組類型,數組長,參數讀/寫入權限)”。
模組被載入後,在/sys/module/目錄下將出現以此模組名命名的目錄。當“參數讀/寫入權限”為0時,表示此參數不存在sysfs檔案系統下對應的檔案節點,如果此模組存在“參數讀/寫入權限”不為0的命令列參數,在此模組的目錄下還將出現parameters目錄,包含一系列以參數名命名的檔案節點,這些檔案的許可權值就是傳入module_param()的“參數讀/寫入權限”,而檔案的內容為參數的值。
運行insmod或modprobe命令時,應使用逗號分隔輸入的數組元素。
現在我們定義一個包含兩個參數的模組(如代碼清單4.4,位於本書原始碼/kernel/drivers/param目錄),並觀察模組載入時被傳遞參數和不傳遞參數時的輸出。
代碼清單4.4  帶參數的核心模組
01 #include <linux/init.h>02 #include <linux/module.h>0304 static char *book_name = "dissecting Linux Device Driver";05 module_param(book_name, charp, S_IRUGO);0607 static int book_num = 4000;08 module_param(book_num, int, S_IRUGO);0910 static int __init book_init(void)11 {12     printk(KERN_INFO "book name:%s\n", book_name);13     printk(KERN_INFO "book num:%d\n", book_num);14     return 0;15 }16 module_init(book_init);1718 static void __exit book_exit(void)19 {20     printk(KERN_INFO "book module exit\n ");21 }22 module_exit(book_exit);2324 MODULE_AUTHOR("Barry Song <baohua@kernel.org>");25 MODULE_LICENSE("GPL v2");26 MODULE_DESCRIPTION("A simple Module for testing module params");27 MODULE_VERSION("V1.0");

對上述模組運行“insmod book.ko”命令載入,相應輸出都為模組內的預設值,通過查看“/var/log/messages”記錄檔可以看到核心的輸出:
# tail -n 2 /var/log/messagesJul  2 01:03:10 localhost kernel:  <6> book name:dissecting Linux Device DriverJul  2 01:03:10 localhost kernel:  book num:4000

當使用者運行“insmod book.ko book_name='GoodBook' book_num=5000”命令時,輸出的是使用者傳遞的參數:
# tail -n 2 /var/log/messagesJul  2 01:06:21 localhost kernel:  <6> book name:GoodBookJul  2 01:06:21 localhost kernel:  book num:5000Jul  2 01:06:21 localhost kernel:  book num:5000
另外,在/sys目錄下,也可以看到book模組的參數:
barry@barry-VirtualBox:/sys/module/book/parameters$ tree.├── book_name└── book_num
匯出符號Linux的“/proc/kallsyms”檔案對應著核心符號表,它記錄了符號以及符號所在的記憶體位址。
模組可以使用如下宏匯出符號到核心符號表:
EXPORT_SYMBOL(符號名);
EXPORT_SYMBOL_GPL(符號名);
    匯出的符號將可以被其他模組使用,使用前聲明一下即可。EXPORT_SYMBOL_GPL()只適用於包含GPL許可權的模組。代碼清單4.5給出了一個匯出整數加、減運算函數符號的核心模組的例子。
代碼清單4.5  核心模組中的符號匯出
01 #include <linux/init.h>02 #include <linux/module.h>0304 int add_integar(int a, int b)05 {06     return a + b;07 }08 EXPORT_SYMBOL_GPL(add_integar);0910 int sub_integar(int a, int b)11 {12     return a - b;13 }14 EXPORT_SYMBOL_GPL(sub_integar);1516 MODULE_LICENSE("GPL v2");

從“/proc/kallsyms”檔案中找出add_integar、sub_integar相關資訊:
# grep integar /proc/kallsymse679402c r __ksymtab_sub_integar    [export_symb]e679403c r __kstrtab_sub_integar    [export_symb]e6794038 r __kcrctab_sub_integar    [export_symb]e6794024 r __ksymtab_add_integar    [export_symb]e6794048 r __kstrtab_add_integar    [export_symb]e6794034 r __kcrctab_add_integar    [export_symb]e6793000 t add_integar    [export_symb]e6793010 t sub_integar    [export_symb]

模組聲明與描述
在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,表明該驅動模組所支援的裝置,如代碼清單4.6所示。
代碼清單4.6  驅動所支援的裝置列表
1 /* 對應此驅動的裝置表 */2 static struct usb_device_id skel_table [] = {3 { USB_DEVICE(USB_SKEL_VENDOR_ID,4       USB_SKEL_PRODUCT_ID) },5     { } /* 表結束 */6 };78 MODULE_DEVICE_TABLE (usb, skel_table);

此時,並不需要讀者理解MODULE_DEVICE_TABLE的作用,後續相關章節會有詳細介紹。
 模組的使用計數
Linux 2.4核心中,模組自身通過MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT宏來管理自己被使用的計數。
Linux 2.6以後的核心提供了模組計數管理介面try_module_get(&module)和module_put (&module),從而取代Linux 2.4核心中的模組使用計數管理宏。模組的使用計數一般不必由模組自身管理,而且模組計數管理還考慮了SMP與PREEMPT機制的影響。

int try_module_get(struct module *module);

該函數用於增加模組使用計數;若返回為0,表示調用失敗,希望使用的模組沒有被載入或正在被卸載中。

void module_put(struct module *module);

該函數用於減少模組使用計數。
try_module_get ()與module_put()的引入與使用與Linux 2.6以後的核心下的裝置模型密切相關。Linux 2.6以後的核心為不同類型的裝置定義了struct module *owner域,用來指向管理此裝置的模組。當開始使用某個裝置時,核心使用try_module_get(dev->owner)去增加管理此裝置的owner模組的使用計數;當不再使用此裝置時,核心使用module_put(dev->owner)減少對管理此裝置的owner模組的使用計數。這樣,當裝置在使用時,管理此裝置的模組將不能被卸載。只有當裝置不再被使用時,模組才允許被卸載。
在Linux 2.6以後的核心下,對於裝置驅動而言,很少需要親自調用try_module_get()與module_put(),因為此時開發人員所寫的驅動通常為支援某具體裝置的owner模組,對此裝置owner模組的計數管理由核心裡更底層的代碼如匯流排驅動或是此類裝置共用的核心模組來實現,從而簡化了裝置驅動開發。


模組的編譯

我們可以為代碼清單4.1的模板編寫一個簡單的Makefile:

KVERS = $(shell uname -r)# Kernel modulesobj-m += hello.o# Specify flags for the module compilation.#EXTRA_CFLAGS=-g -O0build: kernel_moduleskernel_modules:      make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modulesclean:      make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

該Makefile檔案應該與原始碼hello.c位於同一目錄,開啟其中的EXTRA_CFLAGS=-g -O0可以得到包含調試資訊的hello.ko模組。運行make命令得到的模組可直接在PC上運行。
如果一個模組包括多個.c檔案(如file1.c、file2.c),則應該以如下方式編寫Makefile:
obj-m := modulename.o
modulename-objs := file1.o file2.o    

模組與GPLLinux核心有2種方法匯出符號給模組使用,一種方法是EXPORT_SYMBOL(),另外一種EXPORT_SYMBOL_GPL()。這一點和模組A匯出符號給模組B用是一致的。
核心的Documentation/DocBook/kernel-hacking.tmpl明確表明“the symbols exported by EXPORT_SYMBOL_GPL()can only be seen by modules with a MODULE_LICENSE() that specifies a GPL compatible license.”由此可見核心用EXPORT_SYMBOL_GPL()匯出的符號是不可以被非GPL模組引用的。
由於相當多的核心符號都是以EXPORT_SYMBOL_GPL()匯出的,所以曆史上曾經有一些公司的做法是把核心的EXPORT_SYMBOL_GPL()直接改為EXPORT_SYMBOL(),然後將修改後的核心以GPL形式發布。這樣修改核心之後,模組不再使用核心的EXPORT_SYMBOL_GPL()符號,因此模組不再需要GPL。對此Linus的回複是:“I think both them said that anybody who were to change a xyz_GPL to the non-GPL one in order to use it with a non-GPL module would almost immediately fall under the "willful infringement" thing, and that it would make it MUCH easier to get triple damages and/or injunctions, since they clearly knew about it”。因此,這種做法可能構成“蓄意侵權(willful infringement)”。
    另外一種做法是寫一個wrapper核心模組(這個模組遵循GPL),把EXPORT_SYMBOL_GPL()匯出的符號封裝一次再次以EXPORT_SYMBOL()形式匯出,而其他的模組不直接調用核心而是調用wrapper函數,4.1所示。這種做法也具有爭議。
 
圖4.1將EXPORT_SYMBOL_GPL重新以EXPORT_SYMBOL匯出
一般認為,保守的做法是Linux核心不能使用非GPL許可權。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.