?? (ldd) ch03-字符設備驅動程序(轉載).txt
字號:
(LDD) Ch03-字符設備驅動程序(轉載)
第3章 字符設備驅動程序
本章的目標是編寫一個完整的字符設備驅動程序。由于這類驅動程序適合于大多數簡單
的硬件設備,我們首先開放一個字符設備驅動程序。字符也相對比較好理解,比如說塊
設備驅動程序。我們的最終目標是寫一個模塊化的字符設備驅動程序,但本章我們不再
講述有關模塊化的問題。
本章通篇都是從一個真實的設備驅動程序截取出的代碼塊:這個設備就是scull,是“Si
mple Character Utility for Loading Localities”的縮寫。盡管scull是一個設備,
但它卻是操作內存的字符設備。這種情況的一個副作用就是,只要涉及scull,“設備”
這個詞就可以同“scull使用的內存區”互換使用。
scull的優點是,由于每臺電腦都有內存,所以它與硬件無關。scull用kmalloc分配內存
,而且僅僅操作內存。任何人都可以編譯和運行scull,而且scull可以移植到所有Linux
,而且僅僅操作內存。任何人都可以編譯和運行scull,而且scull可以移植到所有Linux
支持的平臺上。但另一方面,除了演示內核于字符設備驅動程序間的交互過程,可以讓
用戶運行某些測試例程外,scull做不了“有用的”事。
scull的設計
編寫設備驅動程序的第一步就是定義驅動程序提供給用戶程序的能力(“機制”)。由
于我們的“設備”是電腦內存的一部分,我做什么都可以。它可以是順便存取設備,也
可以是隨機存取設備,可以是一個設備,也可以是多個,等等。
為了是scull更有用,可以成為編寫真實設備的驅動程序的模板,我將向你展示如何在電
腦的內存之上實現若干設備抽象操作,每一種操作都有自己的特點。
scull的源碼實現如下設備。由模塊實現的每一種設備都涉及一種類型:
scull0-3
4個設備,共保護了4片內存區,都是全局性的和持久性的。“全局性”是指,如果打開
設備多次,所有打開它的文件描述符共享其中的數據。“持久性”是指,如果設備關閉
后再次打開,數據不丟失。由于可以使用常用命令訪問這個設備,如cp,cat以及shell
I/O重定向等,這個設備操作非常有趣;本章將深入探討它的內部結構。
scullpipe0-3
4個“fifo”設備,操作起來有點象管道。一個進程讀取另一個進程寫入的數據。如果有
多個進程讀同一個設備,他們彼此間競爭數據。通過scullpipe的內部結構可以了解阻塞
型和非阻塞型讀/寫是如何實現的;沒有中斷也會出現這樣的情況。盡管真實的驅動程序
利用中斷與它們的設備同步,但阻塞型和非阻塞型操作是非常重要的內容,從概念上講
與中斷處理(第9章,中斷處理,介紹)無關。
scullsingle
scullpriv
sculluid
scullwuid
這些設備與scull0相似,但在何時允許open操作時都不同方式的限制。第一個(scullsi
ngle)只允許一次一個進程使用驅動程序,而scullpriv對每個虛擬控制臺是私有的(每
個設備對虛擬控制臺是私有的)。sculluid和scullwuid可以多次打開,但每次只能有一
個用戶;如果另一個用戶鎖住了設備,前者返回-EBUSY,而后者則實現為阻塞型open。
通過這些可以展示如何實現不同的訪問策略。
每一個scull設備都展示了驅動程序不同的功能,而且都不同的難度。本章主要講解scul
l0-3的內部結構;第5章,字符設備驅動程序的擴展操作,將介紹更復雜的設備: “一
l0-3的內部結構;第5章,字符設備驅動程序的擴展操作,將介紹更復雜的設備: “一
個樣例實現:scullpipe”介紹scullpipe,“設備文件的訪問控制”介紹其他設備。
主設備號和次設備號
通過訪問文件系統的名字(或“節點”)訪問字符設備,通常這些文件位于/dev目錄。
設備文件是特殊文件,這一點可以通過ls -l輸出的第一列中的“c”標明,它說明它們
是字符節點。/dev下還有塊設備,但它們的第一列是“b”;盡管如下介紹的某些內容也
同樣適用于塊設備,現在我們只關注字符設備。如果你執行ls命令,在設備文件條目的
最新修改日期前你會看到兩個數(用逗號分隔),這個位置通常顯示文件長度。這些數
就是相應設備的主設備號和次設備號。下面的列表給出了我使用的系統上的一些設備。
它們的主設備號是10,1和4,而次設備號是0,3,5,64-65和128-129。
(代碼)
主設備號標識設備對應的驅動程序。例如,/dev/null和/dev/zero都有驅動程序1管理,
而所有的tty和pty都由驅動程序4管理。內核利用主設備號將設備與相應的驅動程序對應
起來。
次設備號只由設備驅動程序使用;內核的其他部分不使用它,僅將它傳遞給驅動程序。
一個驅動程序控制若干個設備并不為奇(如上面的例子所示)――次順便號提供了一種
區分它們的方法。
向系統增加一個驅動程序意味著要賦予它一個主設備號。這一賦值過程應該在驅動程序
向系統增加一個驅動程序意味著要賦予它一個主設備號。這一賦值過程應該在驅動程序
(模塊)的初始化過程中完成,它調用如下函數,這個函數定義在<linux/fs.h>:
(代碼)
返回值是錯誤碼。當出錯時返回一個負值;成功時返回零或正值。參數major是所請求的
主設備號,name是你的設備的名字,它將在/proc/devices中出現,fops是一個指向跳轉
表的指針,利用這個跳轉表完成對設備函數的調用,本章稍后將在“文件操作”一節中
介紹這些函數。
主設備號是一個用來索引靜態字符設備數組的整數。在1.2.13和早期的2.x內核中,這個
數組有64項,而2.0.6到2.1.11的內核則升至128。由于只有設備才處理次設備號,regis
ter_chrdev不傳遞次設備號。
一旦設備已經注冊到內核表中,無論何時操作與你的設備驅動程序的主設備號匹配的設
備文件,內核都會通過在fops跳轉表索引調用驅動程序中的正確函數。
接下來的問題就是如何給程序一個它們可以請求你的設備驅動程序的名字。這個名字必
須插入到/dev目錄中,并與你的驅動程序的主設備號和次設備號相連。
在文件系統上創建一個設備節點的命令是mknod,而且你必須是超級用戶才能創建設備。
除了要創建的節點名字外,該命令還帶三個參數。例如,命令:
(代碼)
創建一個字符設備(c),主設備號是127,次設備號是0。由于歷史原因,次設備號應該
在0-255范圍內,有時它們存儲在一個字節中。存在很多原因擴展可使用的次設備號的范
圍,但就現在而言,仍然有8位限制。
動態分配主設備號
某些主設備號已經靜態地分配給了大部分公用設備。在內核源碼樹的Documentation/dev
ice.txt文件中可以找到這些設備的列表。由于許多數字已經分配了,為新設備選擇一個
唯一的號碼是很困難的――不同的設備要不主設備號多得多。
很幸運(或是感謝某些人天才),你可以動態分配主設備號了。如果你調用register_ch
rdev時的major為零的話,這個函數就會選擇一個空閑號碼并做為返回值返回。主設備號
總是正的,因此不會和錯誤碼混淆。
我強烈推薦你不要隨便選擇一個一個當前不用的設備號做為主設備號,而使用動態分配
機制獲取你的主設備號。
動態分配的缺點是,由于分配給你的主設備號不能保證總是一樣的,無法事先創建設備
節點。然而這不是什么問題,這是因為一旦分配了設備號,你就可以從/proc/devices讀
到。為了加載一個設備驅動程序,對insmod的調用被替換為一個簡單的腳本,它通過/pr
oc/devices獲得新分配的主設備號,并創建節點。
oc/devices獲得新分配的主設備號,并創建節點。
/proc/devices一般如下所示:
(代碼)
加載動態分配主設備號驅動程序的腳本可以利用象awk這類工具從/proc/devices中獲取
信息,并在/dev中創建文件。
下面這個腳本,scull_load,是scull發行中的一部分。使用以模塊形式發行的驅動程序
的用戶可以在/etc/rc.d/rc.local中調用這個腳本,或是在需要模塊時手工調用。此外
還有另一種方法:使用kerneld。這個方法和其他模塊的高級功能將在第11章“Kerneld
和高級模塊化”中介紹。
(代碼)
這個腳本同樣可以適用于其他驅動程序,只要重新定義變量和調整mknod那幾行就可以了
。上面那個腳本創建4個設備,4是scull源碼中的默認值。
腳本的最后兩行看起來有點怪怪的:為什么要改變設備的組和權限呢?原因是這樣的,
由root創建的節點自然也屬于root。默認權限位只允許root對其有寫訪問權,而其他只
有讀權限。正常情況下,設備節點需要不同的策略,因此需要進行某些修改。通常允許
一組用戶訪問對設備,但實現細節卻依賴于設備和系統管理員。安全是個大問題,這超
一組用戶訪問對設備,但實現細節卻依賴于設備和系統管理員。安全是個大問題,這超
出了本書的范圍。scull_load中的chmod和chgrp那兩行僅僅是最為處理權限問題的一點
提示。稍后,在第5章的“設備文件的訪問控制”一節中將介紹sculluid源碼,展示設備
驅動程序如何實現自己的設備訪問授權。
如果重復地創建和刪除/dev節點似乎有點過分的話,有一個解決的方法。如果你看了內
核源碼fs/devices.c的話,你可以看到動態設備號是從127(或63)之后開始的,你可以
用127做為主設備號創建一個長命節點,同時可以避免在每次相關設備加載時調用腳本。
如果你使用了幾個動態設備,或是新版本的內核改變了動態分配的特性,這個技巧就不
能用了。(如果內核發生了修改,基于內核內部結構編寫的代碼并不能保證繼續可以工
作。)不管怎樣,由于開發期間模塊要不斷地加載 托對 ,你會發現這一技術在開發期
間還是很有用的。
就我看來,分配主設備號的最佳方式是,默認采用動態分配,同時留給你在加載時,甚
至是編譯時,指定主設備號的余地。使用我建議的代碼將與自動端口探測的代碼十分相
似。scull的實現使用了一個全局變量,scull_major,來保存所選擇的設備號。該變量
的默認值是SCULL_MAJOR,在所發行的源碼中為0,即“選擇動態分配”。用戶可以使用
這個默認值或選擇某個特定的主設備號,既可以在編譯前修改宏定義,也可以在ins_mod
命令行中指定。最后,通過使用scull_load腳本,用戶可以在scull_load中命令行中將
參數傳遞給insmod。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -