第一章 設備驅動程序簡介
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程序要鏈接多個庫,所以佔用更多內存頁,