Windows XP Windows 7 Windows 2003 Windows Vista Windows教程綜合 Linux 系統教程
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟件教程
 Windows教程網 >> Linux系統教程 >> Linux教程 >> linux設備驅動歸納總結:模塊的相關基礎概念

linux設備驅動歸納總結:模塊的相關基礎概念

日期:2017/2/7 14:29:21      編輯:Linux教程
 

一、初探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語句輸出到控制台。

 

 

Copyright © Windows教程網 All Rights Reserved