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

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


系統平臺:Ubuntu 10.04

開發平臺:S3C2440開發板

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


一。初探linux內核模塊


內核模塊:內核本身是很龐大的一個結構,需要的組件很多。編譯內核時,用戶 可以把所有的代碼編譯進內核,但是這樣會引起兩個問題:一是內核過大;二是 當需要添加或者刪除內核時,需要重新再編譯內核。所以有了內核模塊的概念。 模塊並不編譯到內核中,編譯後存放在指定的目錄,當需要使用時動態加載。

1.1下面是一個非常經典的hello world代碼: 目錄:1st

/*2nd_module/1st*/

1 #include //包含了很多裝載模塊需要的符號和函數的定義

2 #include //用於指定初始化函數和清除函數

3

4 static int __init test_init(void) //內核初始化函數

5 {

6         printk("hello world!\n"); //打印函數,和prinft類似

7         return 0;

8 }

9

10 static void __exit test_exit(void)//內核清除函數

11 {

12         printk("good bye!\n");

13 }

14

15 module_init(test_init); //指定初始化函數

16 module_exit(test_exit); //指定清除函數

17

18 MODULE_LICENSE("GPL"); //指定代碼使用的許可證

19 MODULE_AUTHOR("xiao bai"); //指定作者

20 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("xiao bai"); //指定作者

MODULE_VERSION("1.0"); //指定代碼修訂號

1)以上的都是一些都該模塊的描述,除了上面的還有MODULE_ALIAS(模塊的別名) MODULE_DESCRIPTION(描述用途)等。

2)MODULE_LICENSE一般都是要寫的,告訴內核該程序使用的許可證,不然在加載 時它會提示該模塊污染內核。

3)MODULE_聲明可以聲明在源代碼任意位置,但習慣放在代碼的最後。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


二。內核中的printk


printkprintf的用法是差不多的,最大的區別就是printk可以指定打印的優先 級。另外一個區別就是,printf只用在用戶態,printk用於內核態。

下面由程序講解 目錄:2nd

/*2nd_module/2nd*/

1 #include

2 #include

3

4 static int __init test_init(void)

5 {

        printk("hello world!\n");

        printk("<0>" "hello world! 0\n");

        printk("<1>" "hello world! 1\n");

        printk("<2>" "hello world! 2\n");

10       printk("<3>" "hello world! 3\n");

11       printk("<4>" "hello world! 4\n");

12       printk("<5>" "hello world! 5\n");

13       printk("<6>" "hello world! 6\n");

14       printk("<7>" "hello world! 7\n");

15       return 0;

16 }

17

18 static void __exit test_exit(void)

19 {

20         printk("good bye!\n");

21 }

22

23 module_init(test_init);

24 module_exit(test_exit);

25

26 MODULE_LICENSE("GPL");

27 MODULE_AUTHOR("xiao bai");

28 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下有記錄:

91 #define KERN_EMERG "<0>" /* system is unusable */

92 #define KERN_ALERT "<1>" /* action must be taken immediately */

93 #define KERN_CRIT "<2>" /* critical conditions */

94 #define KERN_ERR "<3>" /* error conditions */

95 #define KERN_WARNING "<4>" /* warning conditions */

96 #define KERN_NOTICE "<5>" /* normal but significant condition */

97 #define KERN_INFO "<6>" /* informational */

98 #define KERN_DEBUG "<7>" /* debug-level messages */

其中<0>的優先級最高,<7>優先級最低。上面的printk語句的優先級都可以用字符 串代替,如下面兩句是同等作用的:

p { margin-bottom: 0.21cm; }

printk("<3>" "hello world! 3\n");

printk(KERN_ERR "hello world! 3\n")

如果調用printk使用的優先級低於或等於控制檯的默認優先級,就不能被輸出到 控制檯終端上顯示,所以在minicom界面中看不到最後一句的輸出。

按照以上的推測,可以得到兩個結論:

一、如果不指定prinfk的優先級,prinfk的默認優先級比控制檯的優先級高,所 以才能顯示在控制檯上。

二、控制檯的優先級是6,因爲低於6優先級的語句不能打印出來。


printk的默認優先級在內核目錄kernel/printk.c定義:

47 /* printk's without a loglevel use this.. */

48 #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */

49

50 /* We show everything that is MORE important than this.. */

51 #define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */

52 #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG*/

文件中定義了printk的默認輸出優先級爲4,並定義了一般使用的最大和最小優先 級17


而終端控制檯的輸出優先級配置在文件/proc/sys/kernel/printk中:

[root: /]# cat /proc/sys/kernel/printk

7 4 1 7

7 4 1 7分別是:

7console_loglevel //這個就是控制檯的默認優先級

4default_message_loglevel // 這個是printk的默認輸出優先級

1minimum_console_level

7default_console_loglevel


可以通過修改該文件使所有優先級的消息都顯示出來。

[root: /]# echo 8 > /proc/sys/kernel/printk


注意的是,即使沒有顯示在控制檯的內核消息,也會追加到/var/log/messages,通過查看/var/log/messages就能看到。


小技巧:可以通過printk的優先級定義是否輸出調試信息:目錄:3rd

1 #include

2 #include

3

4 #define DEBUG_SWITCH 0

5 #if DEBUG_SWITCH

6         #define P_DEBUG(fmt, args...) printk("<1>" "[%s]"fmt, __FUNCTION__, ##args)

7 #else

        #define P_DEBUG(fmt, args...) printk("<7>" "[%s]"fmt, __FUNCTION__, ##args)

9 #endif

10

11

12 static int __init test_init(void)

13 {

14         printk("hello world!\n");

15         P_DEBUG("debug!\n");

16         return 0;

17 }

18

19 static void __exit test_exit(void)

20 {

21         printk("good bye!\n");

22 }

23

24 module_init(test_init);

25 module_exit(test_exit);

26

27 MODULE_LICENSE("GPL");

28 MODULE_AUTHOR("xiao bai");

29 MODULE_VERSION("1.0");


#define DEBUG_SWITCH 0時,P_DEBUG語句並不輸出到控制檯。

相反,當#define DEBUG_SWITCH 1時,P_DEBUG語句輸出到控制檯。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


三。內核傳參————module_param


在用戶態的C語言中,函數的傳參使用main(int argc, char* argv),內核的傳參使用了另外一種方法:

步驟一、在內核函數中用module_param指定模塊參數。

步驟二、加載內核時傳遞參數給模塊。


module_param函數使用方法:

module_param(name, type, perm)

name:內核參數的名稱,自己定義;

type:內核參數的類型,常見的類型byteshortintlongulongbool charp(字符指針);

perm:內核參數的權限S_IRUGO(對模塊參數具有讀權限)。其實權限和文件的 權限差不多,具體可以查看"include/linux/stat.h"


所以,要定義一個int模塊參數,權限是0644,在函數中需要定義:

int a=0;

module_paaram(a, int, 0644);


內核加載模塊時傳遞參數的方法:

使用命令:insmod xxx.ko a=1

如果加載模塊時不指定參數,模塊會使用默認值0,否則會使用1


模塊加載後,並且他的權限不爲0,就可以在/sys/module/xxx/parameter目錄下 找到對應的模塊參數。

模塊參數使用例子:目錄 4th

/*2nd_module/4th*/

1 #include

2 #include

3

4 int num = 123;

5 char *name = "xiao bai";

6

7 static int __init test_init(void)

8 {

        printk("hello world!\n");

10        printk("num = %d, name:[%s]\n", num, name);

11        return 0;

12 }

13

14 static void __exit test_exit(void)

15 {

16        printk("good bye!\n");

17 }

18

19 module_init(test_init);

20 module_exit(test_exit);

21 module_param(num, int, 0644);

22 module_param(name, charp, 0644);

23

24 MODULE_LICENSE("GPL");

25 MODULE_AUTHOR("xiao bai");

26 MODULE_VERSION("1.0");


幾種指定模塊參數時的效果:

[root: 4th]# insmod test.ko

hello world!

num = 123, name:[xiao bai]

[root: 4th]# rmmod test

good bye!

[root: 4th]# insmod test.ko num=321 name='haha'

hello world!

num = 321, name:[haha]

[root: 4th]# rmmod test

good bye!

[root: 4th]# insmod test.ko name='haha'

hello world!

num = 123, name:[haha]


查看/sys/module/test/parameter目錄,出現了模塊參數:

[root: 4th]# ls /sys/module/test/parameters/

name num


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


四、內核函數間的調用


內核函數間的調用有兩種方法:

1)、把需要調用的函數編譯進模塊,與C語言的靜態庫類似。

2)、把許壓迫被調用的函數導出到符號表,方便模塊使用,與C語言的動態庫類似


先說第一個方法:多文件編譯 目錄 5th

1)編寫被調用的函數文件haha.c

1 #include

2 #include "haha.h"

3

4 void haha(void)

5 {

        printk("haha!\n");

7 }


2)編寫頭文件haha.h

1 #ifndef __HAHA_H__

2 #define __HAHA_H__

3

4 void haha(void);

5

6 #endif


3)編寫模塊文件test.c

1 #include

2 #include

3 #include "haha.h"

4

5 static int __init test_init(void)

6 {

        printk("hello world!\n");

        haha();

        return 0;

10 }

11

12 static void __exit test_exit(void)

13 {

14         printk("good bye!\n");

15 }

16

17 module_init(test_init);

18 module_exit(test_exit);

19

20 MODULE_LICENSE("GPL");

21 MODULE_AUTHOR("xiao bai");

22 MODULE_VERSION("1.0");


4)編寫Makefile,與之前的Makefile不一樣,因爲涉及到多文件編譯

1 obj-m += test_haha.o //生成test_haha.ko

2 test_haha-objs += haha.o test.o //test_haha.kohaha.otest.o組成

3

4 KDIR:=/root/Desktop/drives/nfsroot-29/linux-2.6.29

5 all:

        make -C $(KDIR) M=`pwd` modules

7 clean:

        make -C $(KDIR) M=`pwd` modules clean

        rm -f modules.order


編譯完畢後生成test_haha,ko,看一下效果

[root: 5th]# insmod test_haha.ko

hello world!

haha!


值得指出的是,上面的方法一般不用。應該使用下面的方法。


第二種方法:導出符號表EXPORT_SYMBOL :目錄 6th


導出符號表是指,在定義一個函數後,在模塊中使用語句"EXPORT_SYMBOL(xxxxx)" 將函數導出。通過這樣,內核就知道了該函數和在內存中對應的地址,這樣模塊 就可以調用導出的函數了。在"/proc/kallsyms"文件中對應這符號表,它記錄了函數的符號和函數在內存所在的地址。


看看方法:

1)6th/core目錄下編寫被調用的函數的文件haha.c

1 #include

2 #include

3 #include "../include/haha.h"

4

5 int haha(void)

6 {

        printk("haha!\n");

        return 0;

9 }

10

11 EXPORT_SYMBOL(haha); //導出函數

12 MODULE_LICENSE("GPL");


2)在6th/core目錄下編寫Makefile,用於編譯haha.chaha.ko

1 obj-m += haha.o

2

3 KDIR:=/root/Desktop/drives/nfsroot-29/linux-2.6.29

4 all:

        make -C $(KDIR) M=`pwd` modules

6 clean:

        make -C $(KDIR) M=`pwd` modules clean

        rm -f modules.order


3)6th/include目錄下編寫頭文件haha.h

1 #ifndef __HAHA_H__

2 #define __HAHA_H__

3

4 int haha(void);

5

6 #endif


4)6th/driver目錄下編寫文件test.c

1 #include

2 #include

3 #include "../include/haha.h"

4

5 static int __init test_init(void)

6 {

        printk("hello world!\n");

        haha();

        return 0;

10 }

11

12 static void __exit test_exit(void)

13 {

14         printk("good bye!\n");

15 }

16

17 module_init(test_init);

18 module_exit(test_exit);

19

20 MODULE_LICENSE("GPL");

21 MODULE_AUTHOR("xiao bai");

22 MODULE_VERSION("1.0");


5)6th/driver目錄下編寫Makefile,用於編譯test.c

1 obj-m += test.o

2

3 KDIR:=/root/Desktop/drives/nfsroot-29/linux-2.6.29

4 all:

        make -C $(KDIR) M=`pwd` modules

6 clean:

        make -C $(KDIR) M=`pwd` modules clean

        rm -f modules.order

最終會生成兩個文件,一個是core目錄下的haha.ko,一個是driver目錄下的 test.ko。這兩個文件的加載也要講究順序。

如果先加載test.ko的話會出錯:

[root: driver]# insmod test.ko

test: Unknown symbol haha

insmod: cannot insert 'test.ko': unknown symbol in module or invalid parameter

這是因爲內核不能找到函數haha

[root: core]# cat /proc/kallsyms | grep haha //沒有顯示任何東西


所以需要先加載模塊haha.ko,這樣就可以在/proc/kallsyms中找到

[root: /]# cd review_driver/2nd_module/6th/core/

[root: core]# insmod haha.ko

[root: core]# cat /proc/kallsyms | grep haha

00000000 a haha.c [haha]

bf000000 t $a [haha]

bf00001c t $d [haha]

bf000044 r __kstrtab_haha [haha]

c4827080 ? __mod_license12 [haha]

bf000054 r __ksymtab_haha [haha]

bf000054 r $d [haha]

00000000 a haha.mod.c [haha]

c482708c ? __module_depends [haha]

c4827098 ? __mod_vermagic5 [haha]

bf000280 d __this_module [haha]

bf000000 T haha [haha]

c0225c10 u printk [haha]

[root: core]#

到出符號表之後就可以加載模塊test.ko

[root: core]# cd ../driver/

[root: driver]# insmod test.ko

hello world!

haha!


隨便提一下兩個小問題:

1)、函數是必須有"#include "

原因一:printk的調用需要

原因二:如果不加上面的命令就不會顯示"bf000000 T haha [haha]", 而顯示"bf000000 t haha [haha]",小寫t表示該符號未定義。

2)、模塊的引用技術:

[root: 6th]# insmod core/haha.ko

[root: 6th]# lsmod

haha 904 0 - Live 0xbf000000 //0 無引用

[root: 6th]# insmod driver/test.ko

hello world!

haha!

[root: 6th]# lsmod

test 1084 0 - Live 0xbf003000

haha 904 1 test, Live 0xbf000000 //被引用一次,test模塊引用

可以看到,加載了test.ko時,haha模塊產生了變化,如果卸載的時候先卸載haha 是不可以的,因爲它還被人引用,必須先卸載test


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

原文鏈接:http://blog.chinaunix.net/uid-25014876-id-59415.html

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