Linux設備驅動程序學習(0)-Hello, world模塊

Linux設備驅動程序學習(0)
-設備驅動介紹& Hello, world!模塊
設備驅動程序的作用

設備驅動程序就是這個進入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.2

PWD := $(shell pwd)

INSTALLDIR = /home/tekkaman/working/rootfs/lib/modules

CROSS_COMPILE    arm-9tdmi-linux-gnu-

CC    = $(CROSS_COMPILE)gcc

obj-m := hello.o

.PHONY: modules modules_install clean

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

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變量中設定的模塊。

5.   編譯模塊

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]#
 

6.   在開發板上的操作:

[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]#

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;
}


模塊參數
內核允許對驅動程序指定參數,而這些參數可在裝載驅動程序模塊時改變
以下是我的實驗程序:
  1. #include <linux/init.h>
  2. #include <linux/module.h> 
  3. #include <linux/moduleparam.h>

  4. static char *whom = "Tekkaman";
  5. static int howmany = 1;
  6. static int param_array[] = {0,0,0,0};
  7. static int param_array_nr;
  8. module_param(howmany, int, S_IRUGO);
  9. module_param(whom, charp, S_IRUGO);
  10. module_param_array(param_array , int , &param_array_nr , S_IRUGO);

  11. static int hello_init(void)
  12. {
  13.     int i;

  14.         printk(KERN_ALERT "Hello, Linux !\n");
  15.         
  16.     for (= 0; i < howmany; i++)    {
  17.         printk(KERN_ALERT "(%d) Hello, %s\n", i, whom);
  18.     }
  19.         
  20.     for (= 0; i < param_array_nr; i++)    {
  21.         printk(KERN_ALERT "param_array[%d] : %d \n", i, param_array[i]);
  22.     }
  23.         
  24.             return 0;
  25. }

  26. static void hello_exit(void)
  27. {
  28.         printk(KERN_ALERT "Goodbye, Linux !\n");
  29. }

  30. module_init(hello_init);
  31. module_exit(hello_exit);

  32. EXPORT_SYMBOL(hello_init);

  33. MODULE_DESCRIPTION("hello_linux test module");
  34. MODULE_ALIAS("hello_world");
  35. MODULE_INFO(tekkaman, "ninja");
  36. MODULE_VERSION("v1.0");
  37. MODULE_AUTHOR("Tekkaman");
  38. MODULE_LICENSE("Dual BSD/GPL");
實驗結果是 :
  1. root@tekkaman:~# ls
  2.  hello_linux.ko
  3. root@tekkaman:~# insmod hello_linux.ko param_array=9,8,7
  4. Hello, Linux !
  5. (0) Hello, Tekkaman
  6. param_array[0] : 9 
  7. param_array[1] : 8 
  8. param_array[2] : 7 
  9. root@tekkaman:~# rmmod hello_linux.ko
  10. Goodbye, Linux !
  11. root@tekkaman:~# insmod hello_linux.ko param_array=9,8
  12. Hello, Linux !
  13. (0) Hello, Tekkaman
  14. param_array[0] : 9 
  15. param_array[1] : 8 
  16. root@tekkaman:~# rmmod hello_linux.ko
  17. Goodbye, Linux !
  18. root@tekkaman:~# insmod hello_linux.ko param_array=9,8,7,6
  19. Hello, Linux !
  20. (0) Hello, Tekkaman
  21. param_array[0] : 9 
  22. param_array[1] : 8 
  23. param_array[2] : 7 
  24. param_array[3] : 6 
  25. root@tekkaman:~# rmmod hello_linux.ko
  26. Goodbye, Linux !
  27. root@tekkaman:~# insmod hello_linux.ko param_array=9,8,7,6,5
  28. param_array: can only take 4 arguments
  29. hello_linux: `9' invalid for parameter `param_array'
  30. insmod: error inserting 'hello_linux.ko': -1 Invalid parameters
module_param_array(param_array , int , &param_array_nr , S_IRUGO);”中
&param_array_nr是用於保存模塊加載時輸入數組參數的成員數目的。
以前我錯誤的以爲這個是輸入參數,其實這個用於內核模塊加載器輸出的。


(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版)》的《第二章 構造和運行模塊》 的學習總結。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章