有沒有一種機制使得編譯出的內核自己並不需要包括所有功能,而在這些功能需要被使用的時候,其相應的代碼被動態地載入到內核中呢?
Linux提供了這樣的一種機制,這樣的機制被稱爲模塊(Module)。模塊具備這樣的特色。
linux
代碼清單4.1 一個最簡單的Linux內核模塊
shell
01 /* 02 * a simple kernel module: hello 03 * 04 * Copyright (C) 2014 Barry Song (baohua@kernel.org) 05 * 06 * Licensed under GPLv2 or later. 07 */ 08 09 #include <linux/init.h> 10 #include <linux/module.h> 11 12 static int __init hello_init(void) 13 { 14 printk(KERN_INFO "Hello World enter\n"); 15 return 0; 16 } 17 module_init(hello_init); 18 19 static void __exit hello_exit(void) 20 { 21 printk(KERN_INFO "Hello World exit\n "); 22 } 23 module_exit(hello_exit); 24 25 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");
內核模塊中用於輸出的函數是內核空間的printk()而非用戶空間的printf()。printk()的使用方法和printf()基本相似,但前者可定義輸出級別。printk()可做爲一種最主要的內核調試手段,在Linux驅動的調試章節中將具體解說這個函數。
在Linux中。使用lsmod命令可以得到系統中載入了的所有模塊以及模塊間的依賴關係,好比:
數組
Module Size Used by hello 9 472 0 nls_iso8859_1 12 032 1 nls_cp437 13 696 1 vfat 18 816 1 fat 57 376 1 vfat ...
$ cat /proc/modules hello 12393 0 - Live 0xe67a2000 (OF) nls_utf8 12493 1 - Live 0xe678e000 isofs 39596 1 - Live 0xe677f000 vboxsf 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 └── uevent 3 directories, 15 files
使用modprobe命令載入的模塊若以「modprobe -r filename」的方式卸載將同一時候卸載其依賴的模塊。模塊之間的依賴關係上存放在根文件系統的/lib/modules/<kernel-version>/modules.dep文件裏,其實是在整體編譯內核的時候由depmod工具生成的,它的格式很easy:
app
kernel/lib/cpu-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko kernel/lib/pm-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko kernel/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 hello.ko filename: hello.ko alias: a simplest module description: A simple Hello World Module license: GPL v2 author: Barry Song <21cnbao@gmail.com> srcversion: 081230411494509792BD4A3 depends: vermagic: 3.8.0-39-generic SMP mod_unload modversions 686
Linux內核模塊最多見的是以MODULE_LICENSE( "GPL v2" )語句聲明模塊採用GPL v2。
(4)模塊參數(可選)。
模塊參數是模塊被載入的時候可以被傳遞給它的值,它自己相應模塊內部的全局變量。
(5)模塊導出符號(可選)。
內核模塊可以導出符號(symbol。相應於函數或變量),這樣其它模塊可以使用本模塊中的變量或函數。
(6)模塊做者等信息聲明(可選)。
ide
代碼清單4.2 內核模塊載入函數
函數
1 static int _ _init initialization_function(void) 2 { 3 /* 初始化代碼 */ 4 } 5 module_init(initialization_function);
在Linux內核裏,錯誤編碼是一個接近於0的負值,在<linux/errno.h>中定義。包括-ENODEV、-ENOMEM之類的符號值。工具
老是返回相應的錯誤編碼是種很好的習慣。因爲僅僅有這樣,用戶程序才幹夠利用perror等方法把它們轉換成有意義的錯誤信息字符串。
在Linux內核中,可以使用request_module(const char *fmt, …)函數載入內核模塊,驅動開發者可以經過調用
request_module(module_name);
這樣的靈活的方式載入其它內核模塊。post
在Linux中,所有標識爲_ _init的函數假設直接編譯進入內核,成爲內核鏡像的一部分,在鏈接的時候都放在.init.text這個區段內。
#define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text")))
所有的_ _init函數在區段.initcall.init中還保存了一份函數指針,在初始化時內核會經過這些函數指針調用這些_ _init函數,並在初始化完畢後,釋放init區段(包括.init.text、.initcall.init等)的內存。
除了函數之外,數據也可以被定義爲_ _initdata,對於僅僅是初始化階段需要的數據,內核在初始化完後,也可以釋放它們佔用的內存。好比,如下的代碼中將hello_data定義爲__initdata。
ui
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);
1 static void _ _exit cleanup_function(void) 2 { 3 /* 釋放代碼 */ 4 } 5 module_exit(cleanup_function);
咱們用__exit來修飾模塊卸載函數,可以告訴內核假設相關的模塊被直接編譯進內核(即built-in),則cleanup_function() 函數會被省略直接不鏈接進最後的鏡像。既然模塊被built-in了。就不可能卸載它了。卸載函數也就沒有存在的必要了。除了函數之外,僅僅是退出階段採用的數據也可以用__exitdata來形容。
this
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);
代碼清單4.4 帶參數的內核模塊
01 #include <linux/init.h> 02 #include <linux/module.h> 03 04 static char *book_name = "dissecting Linux Device Driver"; 05 module_param(book_name, charp, S_IRUGO); 06 07 static int book_num = 4000; 08 module_param(book_num, int, S_IRUGO); 09 10 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); 17 18 static void __exit book_exit(void) 19 { 20 printk(KERN_INFO "book module exit\n "); 21 } 22 module_exit(book_exit); 23 24 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");
# tail -n 2 /var/log/messages Jul 2 01:03:10 localhost kernel: <6> book name:dissecting Linux Device Driver Jul 2 01:03:10 localhost kernel: book num:4000
# tail -n 2 /var/log/messages Jul 2 01:06:21 localhost kernel: <6> book name:GoodBook Jul 2 01:06:21 localhost kernel: book num:5000 Jul 2 01:06:21 localhost kernel: book num:5000另外,在/sys文件夾下,也可以看到book模塊的參數:
barry@barry-VirtualBox:/sys/module/book/parameters$ tree . ├── book_name └── book_num
01 #include <linux/init.h> 02 #include <linux/module.h> 03 04 int add_integar(int a, int b) 05 { 06 return a + b; 07 } 08 EXPORT_SYMBOL_GPL(add_integar); 09 10 int sub_integar(int a, int b) 11 { 12 return a - b; 13 } 14 EXPORT_SYMBOL_GPL(sub_integar); 15 16 MODULE_LICENSE("GPL v2");
# grep integar /proc/kallsyms e679402c 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]
MODULE_AUTHOR(author); MODULE_DESCRIPTION(description); MODULE_VERSION(version_string); MODULE_DEVICE_TABLE(table_info); MODULE_ALIAS(alternate_name);
1 /* 相應此驅動的設備表 */ 2 static struct usb_device_id skel_table [] = { 3 { USB_DEVICE(USB_SKEL_VENDOR_ID, 4 USB_SKEL_PRODUCT_ID) }, 5 { } /* 表結束 */ 6 }; 7 8 MODULE_DEVICE_TABLE (usb, skel_table);
模塊的使用計數通常沒必要由模塊自身管理。而且模塊計數管理還考慮了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 modules obj-m += hello.o # Specify flags for the module compilation. #EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
所以,這樣的作法可能構成「蓄意侵權(willful infringement)」。
第二種作法是寫一個wrapper內核模塊(這個模塊遵循GPL)。把EXPORT_SYMBOL_GPL()導出的符號封裝一次再次以EXPORT_SYMBOL()形式導出,而其它的模塊不直接調用內核而是調用wrapper函數。如圖4.1所看到的。這樣的作法也具備爭議。
圖4.1將EXPORT_SYMBOL_GPL又一次以EXPORT_SYMBOL導出通常以爲。保守的作法是Linux內核不能使用非GPL許可權。