一個學習Linux設備驅動程序都會碰到的第一個例程:
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void) { printk(KERN_ALERT "Hello, Tekkaman Ninja !/n"); return 0; }
static void hello_exit(void) { printk(KERN_ALERT "Goodbye, Tekkaman Ninja !/n Love Linux !Love ARM ! Love KeKe ! /n "
); }
module_init(hello_init); module_exit(hello_exit);
|
我將其複製到我的工作目錄,並編寫了一個簡單的Makefile文件:
KERNELDIR = /home/tekkaman/working/SBC2440/linux-2.6.22.2 # The current directory is passed to sub-makes as argument PWD := $(shell pwd) INSTALLDIR = /home/tekkaman/working/rootfs/lib/modules
CROSS_COMPILE =/home/tekkaman/working/crosstool-gcc410-k26222/gcc-4.1.0-glibc-2.3.2/arm-9tdmi-linux-gnu/bin/arm-9tdmi-linux-gnu- CC = $(CROSS_COMPILE)gcc
obj-m := hello.o
modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install: cp hello.ko $(INSTALLDIR)
clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
|
說實話,以上是我參考了《Linux設備驅動程序(第3版)》的Makefile源碼修改得來的。我對Makefile不是很瞭解,是該好好學習學習了!
然後就是make modules 、 make modules_install 。
[root@Tekkaman-Ninja Helloworld]# make modules make -C /home/tekkaman/working/SBC2440/linux-2.6.22.2 M=/home/tekkaman/working/Linuxdriver/Helloworld modules make[1]: Entering directory `/home/tekkaman/working/SBC2440/linux-2.6.22.2' CC [M] /home/tekkaman/working/Linuxdriver/Helloworld/hello.o Building modules, stage 2. MODPOST 1 modules CC /home/tekkaman/working/Linuxdriver/Helloworld/hello.mod.o LD [M] /home/tekkaman/working/Linuxdriver/Helloworld/hello.ko make[1]: Leaving directory `/home/tekkaman/working/SBC2440/linux-2.6.22.2' [root@Tekkaman-Ninja Helloworld]# make modules_install cp hello.ko /home/tekkaman/working/rootfs/lib/modules [root@Tekkaman-Ninja Helloworld]#
|
在我的開發板上的操作:
[Tekkaman2440@SBC2440V4]#cd /lib/modules/
[Tekkaman2440@SBC2440V4]#ls
cs89x0.ko hello.ko p80211.ko prism2_usb.ko
[Tekkaman2440@SBC2440V4]#insmod hello.ko
Hello, Tekkaman Ninja !
[Tekkaman2440@SBC2440V4]#lsmod
Module Size Used by Not tainted
hello 1376 0
[Tekkaman2440@SBC2440V4]#rmmod hello
Goodbye, Tekkaman Ninja !
Love Linux !Love ARM ! Love KeKe !
[Tekkaman2440@SBC2440V4]#lsmod
Module Size Used by Not tainted
[Tekkaman2440@SBC2440V4]#
|
學習心得:
(1)驅動模塊運行在內核空間,運行時不能依賴於任何函數庫和模塊連接,所以在寫驅動時所調用的函數只能是作爲內核一部分的函數。
(2)驅動模塊和應用程序的一個重要不同是:應用程序退出時可不管資源釋放或者其他的清除工作,但模塊的退出函數必須仔細撤銷初始化函數所作的一切,否則,在系統重新引導之前某些東西就會殘留在系統中。(3)處理器的多種工作模式(級別)其實就是爲了操作系統的用戶空間和內核空間設計的。在Unix類的操作系統中只用到了兩個級別:最高和最低級別。
(4)要十分注意驅動程序的併發處理。
(5)內核API中具有雙下劃線(_ _)的函數,通常是接口的底層組件,應慎用。
(6)內核代碼不能實現浮點書運算。
(7)Makefile文件分析:
obj-m := hello.o 代表了我們要構造的模塊名爲hell.ko,make 會在該目錄下自動找到hell.c文件進行編譯。如果
hello.o是由其他的源文件生成(比如file1.c和
file2.c
)的,則在下面加上(注意紅色字體的對應關係):
hello
-objs := file1.o file2.o ......
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
其中
-C $(KERNELDIR) 指定了內核源代碼的位置,其中保存有內核的頂層makefile文件。
M=$(PWD)
指定了模塊源代碼的位置
modules目標指向obj-m變量中設定的模塊。
(8)insmod使用公共內核符號表來解析模塊中未定義的符號。公共內核符號表中包含了所有的全局內核項(即函數和變量的地址),這是實現模塊化驅動程序所必須的。
(9)Linux使用模塊層疊技術,我們可以將模塊劃分爲多個層,通過簡化每個層可縮短開發週期。如果一個模塊需要向其他模塊到處符號,則使用下面的宏:
EXPORT_SYMBOL(name); EXPORT_SYMBOL_GPL(name);
|
符號必須在模塊文件的全局變量部分導出,因爲這兩個宏將被擴展爲一個特殊變量的聲明,而該變量必須是全局的。
(10)所有模塊代碼中都包含一下兩個頭文件:
#include <linux/init.h> #include <linux/module.h>
|
(11)所有模塊代碼都應該指定所使用的許可證:
MODULE_LICENSE("Dual BSD/GPL");
|
此外還有可選的其他描述性定義:
MODULE_AUTHOR(""); MODULE_DESCRIPTION(""); MODULE_VERSION(""); MODULE_ALIAS(""); MODULE_DEVICE_TABLE("");
|
上述MODULE_
聲明習慣上放在文件最後。
(12)初始化和關閉
初始化的實際定義通常如下:
static int _ _ init initialization_function(void) { /*初始化代碼*/ }
module_init(initialization_function)
|
清除函數的實際定義通常如下:
static int _ _exit cleanup_function(void) { /*清除代碼*/ }
module_exit(cleanup_function)
|
(13) Linux內核模塊的初始化出錯處理一般使用“goto”語句。通常情況下很少使用“goto”,但在出錯處理是(可能是唯一的情況),它卻非常有用。在大二學習C語言時,老師就建議不要使用“goto”,並說很少會用到。在這裏也是我碰到的第一個建議使用“goto”的地方。“在追求效率的代碼中使用goto語句仍是最好的錯誤恢復機制。”--《Linux設備驅動程序(第3版)》以下是初始化出錯處理的推薦代碼示例:
struct something *item1; struct somethingelse *item2; int stuff_ok;
void my_cleanup(void) { if (item1)
release_thing(item1); if (item2) release_thing2(item2); if (stuff_ok) unregister_stuff(); return; } int __init my_init(void) { int err = -ENOMEM; item1 = allocate_thing(arguments); item2 = allocate_thing2(arguments2); if (!item2 || !item2) goto fail; err = register_stuff(item1, item2); if (!err) stuff_ok = 1; else goto fail; return 0; /* success */
fail: my_cleanup( ); return err; }
|
(14)模塊參數:內核允許對驅動程序指定參數,而這些參數可在裝載驅動程序模塊時改變。
以下是我的實驗程序:
#include <linux/init.h> #include <linux/module.h> #include <linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *whom = "Tekkaman Ninja"; static int howmany = 1; static int TNparam[] = {1,2,3,4}; static int TNparam_nr = 4; module_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO); module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);
static int hello_init(void) { int i; for (i = 0; i < howmany; i++) printk(KERN_ALERT "(%d) Hello, %s !/n", i, whom); for (i = 0; i < 8; i++) printk(KERN_ALERT "TNparam[%d] : %d /n", i, TNparam[i]); return 0; }
static void hello_exit(void) { printk(KERN_ALERT "Goodbye, Tekkaman Ninja !/n Love Linux !Love ARM ! Love KeKe !/n"); }
module_init(hello_init); module_exit(hello_exit);
|
實驗結果是 :
[Tekkaman2440@SBC2440V4]#cd /lib/modules/ [Tekkaman2440@SBC2440V4]#ls cs89x0.ko hello.ko prism2_usb.ko hello-param.ko p80211.ko [Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1 (0) Hello, KeKe ! (1) Hello, KeKe ! TNparam[0] : 4 TNparam[1] : 3 TNparam[2] : 2 TNparam[3] : 1 TNparam[4] : 1836543848 TNparam[5] : 7958113 TNparam[6] : 1836017783 TNparam[7] : 0 [Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,6,7,8 TNparam: can only take 4 arguments hello_param: `4' invalid for parameter `TNparam' insmod: cannot insert 'hello-param.ko': Invalid parameters (-1): Invalid argument [Tekkaman2440@SBC2440V4]#
|
我這個實驗除了對參數的改變進行實驗外,我的一個重要的目的是測試“ module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);
”中&TNparam_nr
對輸入參數數目的限制作用。經過我的實驗,表明&TNparam_nr
並沒有對輸入參數的數目起到限制作用。真正起到限制作用的是“static int TNparam[] = {1,2,3,4};
”本身定義的大小,我將程序進行修改:
static int TNparam[] = {1,2,3,4};
改爲 static int TNparam[] = {1,2,3,4,5,6,7,8};
其他都不變。
編譯後再進行實驗,其結果是:
[Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,6,7,8 (0) Hello, KeKe ! (1) Hello, KeKe ! TNparam[0] : 4 TNparam[1] : 3 TNparam[2] : 2 TNparam[3] : 1 TNparam[4] : 5 TNparam[5] : 6 TNparam[6] : 7 TNparam[7] : 8 [Tekkaman2440@SBC2440V4]#
|
(15)“#include <linux/sched.h
>” 最重要的頭文件之一。包含驅動程序使用的大部分內核API的定義,包括睡眠函數以及各種變量聲明。
(16)“#include <linux/version.h
>” 包含所構造內核版本信息的頭文件。
在學習過程中找到了幾篇很好的參考文檔:
(1)第一章 模塊(Modules) URL:http://greenlinux.blogcn.com/diary,103232026.shtml
(2)《從 2.4 到 2.6:Linux 內核可裝載模塊機制的改變對設備驅動的影響》
URL:http://www.ibm.com/developerworks/cn/linux/l-module26/
(3)《Linux2.6內核驅動移植參考》
URL:http://blog.chinaunix.net/u1/40912/showart_377391.html
對《Linux設備驅動程序(第3版)》的《第二章 構造和運行模塊》 的學習總結。