Linux設備驅動程序 二 構造和運行模塊

第一章 設備驅動程序簡介

 

driver在於提供機制,而不是策略。要提供什麼功能,如何使用這些功能。

 

內核功能:

進程管理,內存管理,文件系統,設備控制,網絡

 

設備類型:字符模塊、塊模塊、網絡模塊

 

字符設備:

是能像字節流一樣被訪問的dev,如中斷/dev/console和串口/dev/tty0

通常至少要實現open,close,read,write,,

大多是一個只能順序訪問的通道

 

塊設備

也可以通過/dev/下的文件系統節點訪問。

塊設備可以容納文件系統,

他與字符設備的區別僅僅在於內核內部管理數據的方式

 

還有另一種劃分driver類型的方法:

 

 

第二章,構造和運行模塊

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSDBSD/GPL");

static int hello_init(void)
{
    printk(KERN_ALERT "hello world\n");
    return 0;
}
static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye,cruel world\n");
}

module_init(hello_init);
module_exit(hello_init);

 

模塊被裝載到內核時調用hello_init,被移除時調用hello_init

內核在運行時不能依賴c庫,所以需要單獨的打印函數printk

 

內核模塊與AP的對比:

1,AP是執行單個任務,模塊是預先註冊自己以便於服務於將來的某個請求,

2,AP退出時可以不管資源的釋放和清理,模塊不行,否則會殘留在系統

3,模塊僅僅被鏈接到內核,所以只能調用內核導出的函數,沒有可鏈接的函數庫

4,模塊不能包含通常的頭文件,內核相關都在include/linux和include/asm

 

UMD和KMD

當UMD切到KMD,執行系統調用的內核代碼運行在進程的context裏,可以訪問進程地址空間的所有數據。

而硬件中斷的內核代碼與進程是異步的,與任何特定進程都無關

 

driver完成兩類任務:

1,作爲系統調用的一部分;

2,中斷;

 

內核中的併發:

可能時刻有多個進程在同時使用driver。

Linux內核代碼必須是可重入的,可以同時運行在多個context中。

 

當前進程:

內核模塊不像AP那樣順序執行,但是還是與某個特定進程有關。

內核可以通過全局項current獲得當前進程,在<asm.curren.h>。是指向struct task_struct的指針,它定義在<lunx/sched.h>。

2.6以後driver只需包含<ljinux/sched.h>,下面語句通過訪問struct task_struct來打印:

printk(KERN_INFO "the process is %s ,pid:%i",current->comm, current->pid)

 

其他細節:

1,AP在虛擬內存中,有很大的棧空間。而內核只有非常小的棧空閒,可能是4096字節的頁那麼大。

所以大的結構,需要調用時動態分配結構。

2,__前綴的函數,說明它是接口的底層組件,要謹慎使用

3,內核不能實現浮點數運算。如果打開浮點支持,會有額外開銷

 

編譯:

 

裝載和卸載模塊:

insmod與ld有些類似,它將模塊的代碼和數據裝入內核,然後使用內核的符號表解析模塊中的未解析符號。

如鏈接器不同的是,內核不會修改模塊的磁盤文件,而僅僅修改內存副本。

 

insmod如何工作:

它依賴於/kernel/module.c的一個系統調用。

函數sys_init_module給模塊分配內核內存(vmalloc負責內存分配)以便於裝載模塊

這個系統調用將模塊正文複製到內存區域,通過內核符號表解析模塊中的內核引用,最後調用模塊的init函數。

 

只有系統調用的名字前面有sys__,

modprobe:

如果有當前內核不在存在的符號。modprobe會在模塊搜索路徑查找定義了這些符號的其他模塊。找到了就同時裝載。

 

rmmod移除模塊,如果正在使用就無法移除

 

lsmod,

列出裝載到內核的模塊,它讀取/proc/modules虛擬文件獲得信息。

模塊信息也可以在sysfs虛擬文件系統的/sys/module找到

 

內核符號表:

insmod使用公共內核符號表解析模塊中未定義的符號。

模塊裝入內核後,它導出的符號都會變成內核符號表的一部分。

通常模塊只需要實現自己的功能,不用導出符號。但如果想其他模塊調用它的函數,就要導出符號。

 

模塊層疊技術,如:USB輸入設備模塊層疊在usbcore和input模塊上

modprobe是處理層疊模塊的實用工具

 

linux內核頭文件提供了管理符號對模塊外部可見性的方法,減少名字空間污染,要導出符號:

EXPORT_SYMBOL(name);

 

符號必須在模塊文件的全局部分導出,不能在函數中導出,

因爲該變量將在模塊可執行文件的特殊部分(一個ELF段),

裝載時,內核通過這個段尋找模塊導出的變量。

 

預備知識:頭文件

一定包含:

<linux/module.h>,包含可裝載模塊需要的大量符號和函數定義。

<linux/init.h>,指定初始化和清理函數

 

<modulepatam.h>,向模塊傳遞參數

 

初始化和關閉:

初始化函數如何註冊模塊提供的設施,可以使完整driver,可以僅僅是一個軟件抽象

init函數:

staitc int __init initialization_function(void)
{
    //init code
}
module_init(initialization_function);

聲明爲static,因爲在之外沒有意義。

__init表示這個函數只是在init時用,之後就可以扔掉釋放內存。

 

module_init的調用是強制性的,這個宏會在模塊的目標代碼中增加一個特殊的段,用於說明內核init函數所在的位置。

沒有他,init函數永遠不會被調用。

 

傳遞到內核註冊函數的參數通常是指向描述新設施的數據結構指針,

而數據結構通常包含指向模塊函數的指針,這樣模塊體重的函數會在恰當的時間被內核調用

 

可以註冊的設施類型:串口,雜項設備,sysfs入口,/proc文件,

 

註冊函數一般帶有前綴register_,可以grep用

 

清除函數module_exit();

 

init過程的錯誤處理:

init出現錯誤,模塊必須自行撤銷已註冊的設施

錯誤恢復的處理,用gote語句比較有效。

int __init my_init_function(void)
{
    int err;
    err = register_this(ptr1, "skull");
    if(err) goto fail_this;
    err = register_that(prt2, "skull");
    if(err) goto fail_that;
    
    fail_that:unregister_this(ptr2, "skull");
    fail_this:return err;
}

 

如果不用goto,會消耗更多CPU時間。

 

err是錯誤編碼,定義在<linux/errno.h>的負整數。

AP可以通過perror函數把他們轉成有意義的字符串。

 

發生錯誤時從init函數調用清除函數,可以減少重複代碼,

因爲清理函數在非退出代碼使用,所以不能標記爲__exit

void my_cleanup(void)
{
    if(item1)
        release_thing(item1);
    if(item2)
        release_thing(item2);
    if(stuff_ok)
        unregister_stuff();
       returnl    
}

int __init my_init(void)
{
    int err = -ENOMEM;
    item1 = allocate_thind(arg1);
    item2 = allocate_thing2(arg2);
    if(!item1 || !item2)
        goto fail;
    err = register_stuff(item1, item2);
    if(!err)
        stuff_ok = 1;
    else 
        goto fail;
    return 0;
    
   fail:
       my_cleanup();    
       return err;
}

 

 

模塊裝載競爭:

在init函數還在運行時,內核可能就會調用我們的模塊。

所以,某個設施完成內部init前,不要註冊

 

模塊參數:

參數可以在運行insmod和modprobe命令裝在模塊時賦值,

modprobe還可以從/etc/modprob.conf中讀,

 

模塊必須讓參數對insmod可見,參數要使用module_param宏聲明,定義在moduleparam.h。

module_param()

它需要三個參數:變量名,類型,用於sysfs入口項的訪問許可掩碼。

它必須放在函數之外

module_param(howmany, int, S_IRUGO);

 

支持類型:

bool

charp,字符指針,給字符串分配內存並設置指針,

如果需要其他類型,可以通過模塊代碼的hook定義這些類型。

 

設置訪問許可,

如果給0,不會有對應的sysfs的入口項

否則,模塊參數就會出現在/sys/module,如果給S_IRUGO,任何人都可以訪問

 

如果參數通過sysfs修改了,就真的修改而且不會通知模塊,所以不該讓模塊參數是可寫的。除非我們會檢測修改並作出相應的動作。

 

用戶空間編寫driver:

優勢:

1 ,可以和C庫鏈接

2,可以使用通常的調試器

3,如果用戶空間driver被掛起,kill掉就行了,不會影響系統,

4,用戶內存可以換出,所以driver很大也不會佔太多內存

5,仍然要支持對設備的併發訪問

7,用戶空間driver可以容易避免印修改內核接口而導致的不明確的許可問題。

 

X Server是UMD driver程序的一個例子,

 

缺點:

1,中斷在UMD不可用

2,只有通過mmap映射/dev/mem才能直接訪問內存,只有特權用戶纔可以執行這個額操作

3,只有調用ioperm或者iopl後纔可以訪問I/O端口

4,響應時間很慢。因爲client和硬件傳遞數據和動作要切context

5,如果driver被換出到磁盤,響應會非常慢,使用mlock系統調用可以緩解這個問題,但UMD程序要鏈接多個庫,所以佔用更多內存頁,

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章