Linux裝置驅動程式學習(0) -裝置驅動介紹& Hello, world!模組

來源:互聯網
上載者:User
裝置驅動程式的作用 裝置驅動程式就是這個進入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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.