一、初探linux內核模塊
內核模塊:內核本身是很龐大的一個結構,需要的組件很多。編譯內核時,用戶 可以把所有的代碼編譯進內核,但是這樣會引起兩個問題:一是內核過大;二是 當需要添加或者刪除內核時,需要重新再編譯內核。所以有了內核模塊的概念。 模塊並不編譯到內核中,編譯後存放在指定的目錄,當需要使用時動態加載。
1.1下面是一個非常經典的hello world代碼:目錄:1st
/*2nd_module/1st*/ #include <linux/module.h> //包含了很多裝載模塊需要的符號和函數的定義 #include <linux/init.h> //用於指定初始化函數和清除函數 static int __init test_init(void) //內核初始化函數 { printk("hello world!\n"); //打印函數,和prinft類似 return 0; } static void __exit test_exit(void)//內核清除函數 { printk("good bye!\n"); } module_init(test_init); //指定初始化函數 module_exit(test_exit); //指定清除函數 MODULE_LICENSE("GPL"); //指定代碼使用的許可證 MODULE_AUTHOR("techbulo"); //指定作者 MODULE_VERSION("1.0"); //指定代碼修訂號
1.2再來一個Makefile:
(注:如果不知道“make -C $(KDIR) M=`pwd` modules ”語句的意思,可以查看linux內核驅動歸納總結(一):內核的相關基礎概念的第六小節)
obj-m += test.o KDIR:=/root/Desktop/drives/nfsroot-29/linux-2.6.29 all: make -C $(KDIR) M=`pwd` modules clean: make -C $(KDIR) M=`pwd` modules clean rm -f modules.order
1.3編寫完畢後在代碼目錄下執行“make”命令,就會產生test.ko文件,在開發板 上通過命令“insmod test.ko”,插入模塊,通過命令“lsmod”查看當前的所有 裝載上的模塊,通過命令“rmmod test”卸載該模塊。並且,加載時會輸出 “hello world!”,卸載時會輸出“good bye!”。
[root: 1st]# rmmod test good bye! [root: 1st]# insmod test.ko hello world! [root: 1st]# lsmod test 1060 0 - Live 0xbf00c000 [root: 1st]# rmmod test good bye! [root: 1st]#
1.4上面的程序包含了三個知識點:
1.4.1內核初始化函數:
static int __init test_init(void) //內核初始化函數 { } module_init(test_init); //指定初始化函數
1)初始化函數是在模塊加載時自動被調用,執行相關的初始化工作。
2)static和__init都是可以不加的,因為初始化函數除了加載時執行外沒有別的 用處,加上static只是聲明一下,該函數只能在模塊內部使用。而加上__init後,它暗 示內核該函數僅在初始化時使用,所以在模塊被裝載後,模塊裝載器就會把該函 數扔掉,釋放占用的內存空間。
3)但是moudle_init()是必須要的,因為這樣才能讓模塊加載器知道這是個初始化 函數,沒有這一步,函數就不會得到調用。
4)初始化函數成功返回0,失敗返回對應的錯誤碼。
1.4.2內核清除函數
static void __exit test_exit(void)//內核清除函數 { } module_exit(test_exit); //指定清除函數
1)內核清除函數是在模塊卸載是自動被調用,執行相關的清除工作。
2)同上,static和__exit都是可以不加的,但如果加上__exit,模塊直接編 譯進內核或者不允許卸載,被標志為__exit的函數會被自動丟棄掉。
3)module_exit是必須的,因為這樣內核才能找到清除函數。
4)清除函數的沒有返回值。
5)一個沒有定義清除函數的模塊,是不允許被加載的。
1.4.3模塊的描述性定義:
MODULE_LICENSE("GPL"); //指定代碼使用的許可證 MODULE_AUTHOR("techbulo"); //指定作者 MODULE_VERSION("1.0"); //指定代碼修訂號
1)以上的都是一些都該模塊的描述,除了上面的還有MODULE_ALIAS(模塊的別名) MODULE_DESCRIPTION(描述用途)等。
2)MODULE_LICENSE一般都是要寫的,告訴內核該程序使用的許可證,不然在加載 時它會提示該模塊污染內核。
3)MODULE_聲明可以聲明在源代碼任意位置,但習慣放在代碼的最後。
二、內核中的printk
printk與printf的用法是差不多的,最大的區別就是printk可以指定打印的優先 級。另外一個區別就是,printf只用在用戶態,printk用於內核態。
下面由程序講解 目錄:2nd
/*2nd_module/2nd*/ #include <linux/module.h> #include <linux/init.h> static int __init test_init(void) { printk("hello world!\n"); printk("<0>" "hello world! 0\n"); printk("<1>" "hello world! 1\n"); printk("<2>" "hello world! 2\n"); printk("<3>" "hello world! 3\n"); printk("<4>" "hello world! 4\n"); printk("<5>" "hello world! 5\n"); printk("<6>" "hello world! 6\n"); printk("<7>" "hello world! 7\n"); return 0; } static void __exit test_exit(void) { printk("good bye!\n"); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("techbulo"); MODULE_VERSION("1.0");
編譯後加載模塊,發現輸出內容為:
[root: 2nd]# insmod test.ko hello world! hello world! 0 hello world! 1 hello world! 2 hello world! 3 hello world! 4 hello world! 5 hello world! 6
輸出唯獨缺少了最後一個"hello world! 7",這是因為printk輸出優先級的導致 的。printk的優先級如下,在內核目錄下inxlude/linux/kernel.h下有記錄:
#define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant condition */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */
其中<0>的優先級最高,<7>優先級最低。上面的printk語句的優先級都可以用字符 串代替,如下面兩句是同等作用的:
printk("<3>" "hello world! 3\n"); printk(KERN_ERR "hello world! 3\n")
如果調用printk使用的優先級低於或等於控制台的默認優先級,就不能被輸出到 控制台終端上顯示,所以在minicom界面中看不到最後一句的輸出。
按照以上的推測,可以得到兩個結論:
一、如果不指定prinfk的優先級,prinfk的默認優先級比控制台的優先級高,所 以才能顯示在控制台上。
二、控制台的優先級是6,因為低於6優先級的語句不能打印出來。
printk的默認優先級在內核目錄kernel/printk.c定義:
/* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ /* We show everything that is MORE important than this.. */ #define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */ #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG*/
文件中定義了printk的默認輸出優先級為4,並定義了一般使用的最大和最小優先 級1和7。
而終端控制台的輸出優先級配置在文件/proc/sys/kernel/printk中:
[root: /]# cat /proc/sys/kernel/printk 7 4 1 7
7 4 1 7分別是:
7:console_loglevel //這個就是控制台的默認優先級
4:default_message_loglevel // 這個是printk的默認輸出優先級
1:minimum_console_level
7:default_console_loglevel
可以通過修改該文件使所有優先級的消息都顯示出來。
[root: /]# echo 8 > /proc/sys/kernel/printk
注意的是,即使沒有顯示在控制台的內核消息,也會追加到/var/log/messages,通過查看/var/log/messages就能看到。
小技巧:可以通過printk的優先級定義是否輸出調試信息:目錄:3rd
#include <linux/module.h> #include <linux/init.h> #define DEBUG_SWITCH 0 #if DEBUG_SWITCH #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTION__, ##args) #else #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTION__, ##args) #endif static int __init test_init(void) { printk("hello world!\n"); P_DEBUG("debug!\n"); return 0; } static void __exit test_exit(void) { printk("good bye!\n"); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("techbulo"); MODULE_VERSION("1.0");
當#define DEBUG_SWITCH 0時,P_DEBUG語句並不輸出到控制台。
相反,當#define DEBUG_SWITCH 1時,P_DEBUG語句輸出到控制台。