裝置驅動程式的作用 裝置驅動程式就是這個進入Linux核心世界的大門。裝置驅動程式在Linux核心中扮演著特殊的角色。它是一個獨立的“黑盒子”,使某個特定硬體響應一個定義好的內部編程介面,這些介面完全隱藏了裝置的工作細節。使用者的操作通過一組標準化的調用執行,而這些調用獨立於特定的驅動程式。
將這些調用映射到作用於實際硬體的裝置特有操作上,則是裝置驅動程式的任務。
裝置驅動的分類
字元裝置:字元(char)裝置是個能夠像位元組流(類似檔案)一樣被訪問的裝置。字元裝置驅動程式通常至少要實現open、close、read和write系統調用。
塊裝置:一個塊裝置驅動程式主要通過傳輸固定大小的資料來訪問裝置。塊裝置和字元裝置的區別僅僅在於核心內部管理資料的方式,也就是核心及驅動程式之間的軟體介面,而這些不同對使用者程式是透明的。在核心中,和字元驅動程式相比,塊驅動程式具有完全不同的介面。
網路介面:任何網路事務都經過一個網路介面形成,即一個能夠和其他主機交換資料的裝置。它可以是個硬體裝置,但也可能是個純軟體裝置。訪問網路介面的方法仍然是給它們分配一個唯一的名字(比如eth0),但這個名字在檔案系統中不存在對應的節點。核心和網路裝置驅動程式間的通訊,完全不同於核心和字元以及塊驅動程式之間的通訊,核心調用一套和資料包傳輸相關的函數而不是read、write等。
驅動模組的特點 (1)驅動模組運行在核心空間,運行時不能依賴於任何標準C庫等應用程式層的庫、模組,所以在寫驅動時所調用的函數只能是作為核心一部分的函數,即使用“EXPORT_SYMBOL”匯出的函數。
- insmod使用公用核心符號表來解析模組中未定義的符號。公用核心符號表中包含了所有的全域核心項(即函數和變數的地址),這是實現模組化驅動程式所必須的。
- Linux使用模組層疊技術,我們可以將模組劃分為多個層,通過簡化每個層可縮短開發週期。如果一個模組需要向其他模組匯出符號,則使用下面的宏:
EXPORT_SYMBOL(name);EXPORT_SYMBOL_GPL(name);
符號必須在模組檔案的全域變數部分匯出,因為這兩個宏將被擴充為一個特殊變數的聲明,而該變數必須是全域的。 (2)驅動模組和應用程式的一個重要不同是:應用程式退出時可不管資源釋放或者其他的清除工作,但模組的退出函數必須仔細撤銷初始化函數所作的一切,否則,在系統重新引導之前某些東西就會殘留在系統中。 (3)處理器的多種工作模式(層級)其實就是為了作業系統的使用者空間和核心空間設計的。在Unix類的作業系統中只用到了兩個層級:最高和最低層級。 (4)要十分注意驅動程式的並發處理。 (5)核心API中具有雙底線(_ _)的函數,通常是介面的底層組件,應慎用。 (6)核心代碼不能實現浮點數運算。參考資料:http://blog.chinaunix.net/u/30180/showart.php?id=1421920 模組結構介紹利用Linux裝置驅動程式的第一個常式:Hello World模組瞭解核心驅動模組的結構。
#include <linux/init.h>#include <linux/module.h>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);MODULE_LICENSE("Dual BSD/GPL");
1. 所有模組代碼中都包含一下兩個標頭檔:
#include <linux/init.h>#include <linux/module.h>
2. 所有模組代碼都應該指定所使用的許可證:
MODULE_LICENSE("Dual BSD/GPL");此外還有可選的其他描述性定義:
MODULE_AUTHOR("");MODULE_DESCRIPTION("");MODULE_VERSION("");MODULE_ALIAS("");MODULE_DEVICE_TABLE("");
上述
MODULE_
聲明習慣上放在檔案最後。 3. 初始化和關閉初始化的實際定義通常如下:
static int _ _init initialization_function(void){/*初始化代碼*/}module_init(initialization_function)清除函數的實際定義通常如下:
static int _ _exit cleanup_function(void){/*清除代碼*/}module_exit(cleanup_function)4. 一個簡單的Makefile檔案:
KERNELDIR = /home/tekkaman/working/SBC2440/linux-2.6.22.2PWD := $(shell pwd)INSTALLDIR = /home/tekkaman/working/rootfs/lib/modulesCROSS_COMPILE = arm-9tdmi-linux-gnu-CC = $(CROSS_COMPILE)gccobj-m := hello.o.PHONY: modules modules_install cleanmodules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:cp hello.ko $(INSTALLDIR)clean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versionsobj-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變數中設定的模組。 5. 編譯模組make modules 、 make modules_install
[root@Tekkaman-Ninja Helloworld]# make modulesmake -C /home/tekkaman/working/SBC2440/linux-2.6.22.2 M=/home/tekkaman/working/Linuxdriver/Helloworld modulesmake[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.komake[1]: Leaving directory `/home/tekkaman/working/SBC2440/linux-2.6.22.2'[root@Tekkaman-Ninja Helloworld]# make modules_installcp hello.ko /home/tekkaman/working/rootfs/lib/modules[root@Tekkaman-Ninja Helloworld]#
6. 在開發板上的操作:
[Tekkaman2440@SBC2440V4]#cd /lib/modules/[Tekkaman2440@SBC2440V4]#lscs89x0.ko hello.ko p80211.ko prism2_usb.ko[Tekkaman2440@SBC2440V4]#insmod hello.koHello, Tekkaman Ninja ![Tekkaman2440@SBC2440V4]#lsmodModule Size Used by Not taintedhello 1376 0[Tekkaman2440@SBC2440V4]#rmmod helloGoodbye, Tekkaman Ninja !Love Linux !Love ARM ! Love KeKe ![Tekkaman2440@SBC2440V4]#lsmodModule Size Used by Not tainted[Tekkaman2440@SBC2440V4]#
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;}
模組參數 核心允許對驅動程式指定參數,而這些參數可在裝載驅動程式模組時改變。
以下是我的實驗程式:
#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]#lscs89x0.ko hello.ko prism2_usb.kohello-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] : 4TNparam[1] : 3TNparam[2] : 2TNparam[3] : 1TNparam[4] : 1836543848TNparam[5] : 7958113TNparam[6] : 1836017783TNparam[7] : 0[Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,6,7,8TNparam: can only take 4 argumentshello_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] : 4TNparam[1] : 3TNparam[2] : 2TNparam[3] : 1TNparam[4] : 5TNparam[5] : 6TNparam[6] : 7TNparam[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版)》的《第二章 構造和運行模組》 的學習總結。 轉載於:http://blog.chinaunix.net/space.php?uid=20543672&do=blog&cuid=407202