我的博客: http://www.yewusishi.com/ 希望大家指教
寫在前面的話:
linux的學習有很多的切入點,但是從我學習的觀點來看,設備驅動是一個不錯的切入點。
至於理由,個人覺得最重要的一點就是容易去實踐。眾所周知,讀萬卷書不如行萬里路。研究linux也是如此,只有在實踐中摸索,在實踐中將自己所學的知識融會貫通。而設備驅動的學習可以做到這一點。
在講述設備驅動程序的書籍中,開篇都會講到兩個詞的概念--機制和策略。
我們先來看看ldd3中對它們的定義:
機制---需要提供什么功能
策略---如何使用這些功能
這樣就很清楚了啊,我們驅動程序提供的是一種機制,而所謂的策略則是我們的上一層去解釋,在不同的場合去應用這些機制。
因此,在這里,我們引用ldd3中的一句話:編寫訪問硬件的內核代碼時,不要給用戶強加任何的策略。
好了,廢話不多說了,我們就直接切入主題吧。
作為一個菜鳥,我也就只能將我自己的一些見解和學習中的問題和大家進行討論,如有不足之處,請大家多多批評指教。
最先我們接觸的就是我們今天重點要講的模塊編程。
是不是很想動手寫點什么呢?不要急,穩住心態。工欲善其事,必先利其器。我們似乎還缺少了點什么。對了,就是驅動程序運行的測試系統。我們總不能讓自己寫的驅動“無家可歸”吧。因此,我們首先要構建我們的內核源碼樹。
或許你是第一次看到這個名詞,那么我建議你先去google一下吧,可以找到很多相關的資料。在這里,我只簡單的介紹一下。
所謂構建源碼樹,按我的理解,通俗一點講,就是下載內核,配置和編譯內核,編譯和安裝模塊。最后會在/lib/modules目錄下生成我們以后要用的東西。這里我先不展開了啊。
不過我們在更多時候需要的是一種交叉編譯的系統。如果對交叉編譯還有疑惑的童鞋,先去了解一下吧,我也會寫一篇關於這方面的文章供大家參考。其實交叉編譯和本地編譯最大的區別還是在於一些編譯參數上,因而,也沒必要覺得它會很難。
好了,假設我們的編譯已經OK,我們接下去要做的就是我們期盼已久的編程了。
每種語言的入門程序都是Hello World。那么我們這里也不例外啊。先來看一下Hello World 模塊的代碼。
1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4
5 static int hello_init(void)
6 {
7 printk(KERN_ALERT "Hello, world\n");
8 return 0;
9 }
10
11 static void hello_exit(void)
12 {
13 printk(KERN_ALERT "Goodbye, cruel world\n");
14 }
15
16 module_init(hello_init);
17 module_exit(hello_exit);
我們可以看見上面的代碼中定義了兩個函數,其中一個函數在模塊被裝載到內核時調用(hello_init),而另一個則在模塊移出時調用 (hello_exit)。module_init和module_exit行使用了內核的特殊宏來表示上述兩個函數所扮演的角色。另外一個特殊宏 (MODULE_LICENSE)用來表示內核,該模塊采用自由許可證;如果沒有這樣的聲明,內核在裝載該模塊時會產生抱怨。還有很多類似的特殊宏,如下表所示:
我們可以看見上面的代碼中定義了兩個函數,其中一個函數在模塊被裝載到內核時調用(hello_init),而另一個則在模塊移出時調用 (hello_exit)。module_init和module_exit行使用了內核的特殊宏來表示上述兩個函數所扮演的角色。另外一個特殊宏 (MODULE_LICENSE)用來表示內核,該模塊采用自由許可證;如果沒有這樣的聲明,內核在裝載該模塊時會產生抱怨。還有很多類似的特殊宏,如下 表所示:
在上面,我們似乎看見了一個熟悉的身影---printk()。呵呵,看清楚,不是printf哦。但是看它們的樣子這么 像,應該是兄弟吧。是的,它們的功能都是打印輸出。而printk是用在內核中的,因為內核在運行時不能依賴於C庫。而字符串KERN_ALERT定義了 這條消息的優先級。我們需要在模塊代碼中顯式地指定高優先級的原因是:具有默認優先級的消息可能不會輸出在控制台上。這主要依賴於內核版本。
介紹完最簡單的驅動模塊,我們還是要來了解一點內在的東西---核心模塊與應用程序的對比。
- 執行過程中的不同:大多數小規模及中規模應用程序是從頭到尾執行單個任務,而模塊卻只是預先注冊自己以便服務於將來的某個請求,然后它的初始化函 數就立即結束。換言之,模塊初始化函數的任務就是為以后調用模塊函數預先做准備。最后在退出時,應用程序可以不管資源的釋放或者其他的清除工作。但是模塊 必須仔細撤銷初始化函數所做的一切,否則,在系統重新啟動之前某些東西就會殘留在系統中。
- 調用函數的不同:應用程序可以調用它未定義的函數,因為應用程序可以在連接過程中解析外部引用從而使用適當的函數庫。可是反觀模塊,它僅僅被連接到內核,因此只能調用由內核導出的函數,不存在任何可連接的函數庫。
我們接下來繼續往前走,編寫完模塊之后,我們要做就是對模塊程序進行編譯和加載。那么此時,我們就需要寫一個簡單的Makefile。先來看一下下面的Makefile。
1 #如果已經定義KERNELRELEASE,則說明是從內核構造系統調用的,
2 #因此,可利用其內建語句
3 ifneq ($(KERNELRELEASE),)
4 obj-m := hello.o
5 #否則,是從直接從命令行調用的,
6 #這時要調用內核構造系統。
7 else 8 #這就是我們的內核源碼樹的路徑
9 KERNELDIR := ~/zhoubb/linux-3A/lib/modules/2.6.36-master.lemote/build
10 PWD := $(shell pwd)
11 default:
12 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
13 clean:
14 rm -rf *.o *.mod.* *.ko *.order *.symvers
15 endif
這就是我在編譯Hello World模塊時所用的Makefile文件。當然,你如果要使用的話,記得要將KERNELDIR替換成自己的內核源碼樹路徑哦。
好,現在我們就來演示一下模塊的編譯,加載和卸載。簡單一點,我們就只演示本地編譯的吧。
1。編譯hello.c文件
1 root@yafeng-VirtualBox:~/hello# ls 2 hello.c Makefile
3 root@yafeng-VirtualBox:~/hello# make /*編譯*/
4 make -C /lib/modules/3.0.0-12-generic/build M=/root/hello modules
5 make[1]: Entering directory `/usr/src/linux-headers-3.0.0-12-generic`
6 CC [M] /root/hello/hello.o
7 Building modules, stage 2.
8 MODPOST 1 modules
9 CC /root/hello/hello.mod.o
10 LD [M] /root/hello/hello.ko
11 make[1]: Leaving directory `/usr/src/linux-headers-3.0.0-12-generic`
12 root@yafeng-VirtualBox:~/hello# ls 13 hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers
14 root@yafeng-VirtualBox:~/hello#
上面的hello.ko就是我們最終需要的。
2.加載hello.ko
1 root@yafeng-VirtualBox:~/hello#
2 root@yafeng-VirtualBox:~/hello# ls 3 hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers
4 root@yafeng-VirtualBox:~/hello# insmod hello.ko
5 root@yafeng-VirtualBox:~/hello# lsmod 6 Module Size Used by
7 hello 12393 0
8 vboxvideo 12511 1
9 drm 192226 2 vboxvideo
10 vesafb 13489 1
11 vboxsf 38117 1
我們可以看見hello模塊已經加載到了系統。可是我們並沒有看見我們預期打印的語句!這是和系統設置的打印級別有關,現在我們就去/var/log/syslog文件去看看是不是有我們想要的打印語句。
1 root@yafeng-VirtualBox:/var/log#
2 root@yafeng-VirtualBox:/var/log# cat syslog | grep world
3 Oct 29 16:58:02 yafeng-VirtualBox kernel: [ 4917.585665] Hello,world!
4 root@yafeng-VirtualBox:/var/log#
有沒有發現還少一句啊?是的,因為你沒有卸載模塊啊!
3.卸載hello.ko
root@yafeng-VirtualBox:~/hello#
root@yafeng-VirtualBox:~/hello# rmmod hello
root@yafeng-VirtualBox:~/hello# lsmod Module Size Used by vboxvideo 12511 1
drm 192226 2 vboxvideo
vesafb 13489 1
然后再來看看syslog文件:
1 root@yafeng-VirtualBox:/var/log#
2 root@yafeng-VirtualBox:/var/log# cat syslog | grep world
3 Oct 29 16:58:02 yafeng-VirtualBox kernel: [ 4917.585665] Hello,world!
4 Oct 29 16:58:25 yafeng-VirtualBox kernel: [ 4941.391066] GoodBey,world!
5 root@yafeng-VirtualBox:/var/log#
總結:
本文從整個驅動模塊的准備工作開始到模塊的卸載,比較初略的理了一遍。給出的例程里其實什么都沒有去完成,只是簡簡單單的打印 了數據。但是麻雀雖小,五臟俱全,該程序中已經包含了一個驅動模塊最基本的 東西----init和exit。當然僅僅有這些是不夠的,無法做的更多,因而,我會在下一次中去擴充。