?? (ldd) ch03-字符設備驅動程序(轉載).txt
字號:
于scull0-3設備被設計為全局的和永久性的,這段代碼無需做什么。特別是,由于我們
無法維護scull的打開計數,也就是模塊的使用計數,因此沒有類似于“首次打開時初始
化設備”這類動作。
唯一實際操作在設備上的操作是,當設備寫打開時將設備截斷為長度0。截斷是scull設
計的一部分:用一個較短的文件覆蓋設備,以便縮小設備數據區,這與普通文件寫打開
截斷為0很相似。
但“打開是截斷”有一個嚴重的缺點:若干因為某些原因設備內存正在使用,釋放這些
內存會導致系統失效。盡管可能性不大,這種情況總會發生:如果read和write方法在數
據傳輸時睡眠了,另一個進程可能寫打開這個設備,這時麻煩就來了。處理競爭條件是
一個相當高級的主題,我將在第9章的“競爭條件”中講解。scull處理這個問題的簡單
方法就是在內存還是使用時不釋放內存,“Scull的內存使用”一節中將說明。
以后我們看到其他scull個體(personality)時將會看到一個真正的初始化工作如何完
成。
release方法
release方法的作用正好與open相反。這個設備方法有時也稱為close。它應該:
l 使用計數減1。
l 釋放open分配在filp->private_data中的內存。
l 在最后一次關閉操作時關閉設備。
scull的基本模型無需進行關閉設備動作,所以所需代碼是很少的*:
(代碼)
使用計數減1是非常重要的,因為如果使用計數不歸0,內核是不會卸載模塊的。
如果某個時刻一個從沒被打開的文件被關閉了計數將如何保證一致呢?我們都知道,dup
和fork都會在不調用open的情況下,將一個打開文件復制為2個,但每一個都會在程序終
止時關閉。例如,大多數程序從來不打開它們的stdin文件(或設備),但它們都會在終
止關閉它。
答案很簡單。如果open沒有調用,release也不會調。內核維護一個file結構被使用了多
少次的使用計數。無論是fork還是dup都不創建新的數據結構;它們僅是增加已有結構的
計數。
新的struct file僅由open創建。只有在該結構的計數歸0時close系統調用才會執行clos
新的struct file僅由open創建。只有在該結構的計數歸0時close系統調用才會執行clos
e方法,這只有在刪除這個結構時才會進行。close方法與close系統調用間的關系保證了
模塊使用計數永遠是一致的。
Scull的內存使用
在介紹讀寫操作以前,我們最好先看看scull如何完成內存分配以及為什么要完成內存分
配。為了全面理解代碼我們需要知道“如何分配”,而“為什么”則反映了驅動程序編
寫者需要做出的選擇,盡管scull絕不是一個典型設備,但同樣需要。
本節只講解scull中的內存分配策略,而不會講解你寫實際驅動程序時需要的硬件管理技
巧。這些技巧將在第8章“硬件管理”和第9章中介紹。因此,如果你對針對內存操作的s
cull驅動程序的內部工作原理不感興趣的話,你可以跳過這一節。
scull使用的內存,這里也稱為“設備”,是變長的。你寫的越多,它就增長得越多;消
減的過程只在用短文件覆蓋設備時發生。
所選的實現scull的方法不是很聰明。實現較聰明的源碼會更難讀,而且本節的目的只是
講解read和write,而不是內存管理。這也就是為什么雖然整個頁面分配會更有效,但代
碼只使用了kmalloc和kfree,而沒有涉及整個頁面的分配的操作。
而另一面,從理論和實際角度考慮,我又不想限制“設備”區的尺寸。理論上將,給所
管理的數據項強加任何限制總是很糟糕的想法。從實際出發,為了測試系統在內存短缺
時的性能,scull可以幫助將系統的剩余內存用光。進行這樣的測試有助于你理解系統的
時的性能,scull可以幫助將系統的剩余內存用光。進行這樣的測試有助于你理解系統的
內部行為。你可以使用命令cp /dev/zero /dev/scull用光所有的物理內存,而且你也可
以用工具dd選擇復制到scull設備中多少數據。
在scull中,每個設備都是一組指針的鏈表,而每一個指針又指向一個Scull_Dev結構。
每一個這樣的結構通過一個中間級指針數組最多可引用4,000,000個字節。發行的源碼中
使用了一個有1000個指針的數組,每個指針指向4000個字節。我把每一個內存區稱為一
個“量子”,數組(或它的長度)稱為“量子集”。scull設備和它的內存區如圖3-1所
示。
所選擇的數字是這樣的,向scull寫一個字節就會消耗內存8000了字節:每個量子4個,
量子集4個(在大多數平臺上,一個指針是4個字節;當在Alpha平臺編譯時量子集本身就
會耗費8000個字節,在Alpha平臺上指針是8個字節)。但另一方面,如果你向scull寫大
量的數據,由于每4MB數據只對應一個表項,而且設備的最大尺寸只限于若干MB,不可能
超出計算機內存的大小,遍歷這張鏈表的代價不是很大。
為量子和量子集選擇合適的數值是一個策略問題,而非機制問題,而且最優數值依賴于
如何使用設備。源碼中為處理這些問題允許用戶修改這些值:
l 在編譯時,可以修改scull.h中的SCULL_QUANTUM和SCULL_QSET。
l 在加載時,可以利用insmod修改scull_quantum和scull_qset整數值。
l 在運行時,用ioctl方法改變默認值和當前值。ioctl將在第5章的“ioctl”一
節中介紹。
使用宏和整數值進行編譯時和加載時配置讓人想起前面提到的如何選擇主設備號。無論
何時驅動程序需要一個隨意的數值或這個數值與策略相關,我都使用這種技術。
留下來的唯一問題就是如何選擇默認數值。盡管有時驅動程序編寫者也需要事先調整配
置參數,但他們在編寫自己的模塊時不會碰到同樣的問題。在這個特殊的例子里,問題
的關鍵在于尋找因未填滿的量子和量子集導致的內存浪費和量子和量子集太小帶來的分
配、釋放和指針連接等操作的代價之間的平衡。
此外,還必須考慮kmalloc的內部設計。現在我們還無法講述太多的細節,只能簡單規定
“比2次冪稍小一點是最佳尺寸”比較好。kmalloc的內部結構將在第7章“Getting
Hold of Memory”的“The Real Story of kmalloc”一節中探討。
默認數值的選擇基于這樣的假設,大部分程序員不會受限與4MB的物理內存,那樣大的數
據量有可能會寫到scull中。一臺內存很多的計算機的屬主可能因測試向設備寫數十MB的
數據。因此,所選的默認值是為了優化中等規模的系統和大數據量的使用。
保存設備信息的數據機構如下:
(代碼)
(代碼)
下面的代碼給出了實際工作時是如何利用Scull_Dev保存數據的。其中給出的函數負責釋
放整個數據區,并且在文件寫打開時由scull_open調用。如果當前設備內存正在使用,
該函數就不釋放這些內存(象“open方法”中所說那樣);否則,它簡單地遍歷鏈表,
釋放所有找到的量子和量子集。
(代碼)
讀和寫
讀寫scull設備也就意味著要完成內核空間和用戶進程空間的數據傳輸。由于指針只能在
當前地址空間操作,而驅動程序運行在內核空間,數據緩沖區則在用戶空間,這一操作
不能通過通常利用指針或memcpy完成。
由于驅動程序不過怎樣都要在內核空間和用戶緩沖區間復制數據,如果目標設備不是RAM
而是擴展卡,也有同樣的問題。事實上,設備驅動程序的主要作用就是管理設備(內核
空間)和應用(用戶空間)間的數據傳輸。
在Linux里,跨空間復制是通過定義在<asm/segment.h>里的特殊函數實現的。完成這種
操作函數針對不同數據尺寸(char,short,int,long)進行了優化;它們中的大部分
將在第5章的“使用ioctl參數”一節中介紹。
scull中read和write的驅動程序代碼需要完成到用戶空間和來自用戶空間的整個數據段
scull中read和write的驅動程序代碼需要完成到用戶空間和來自用戶空間的整個數據段
的復制。下面這些提供這些功能,它們可以傳輸任意字節:
(代碼)
這兩個函數的名字可以追溯到第1版Linux,那時唯一支持的體系結構是i386,而且C代碼
中還可以窺見許多匯編碼。在Intel平臺上,Linux通過FS段寄存器訪問用戶空間,到Lin
ux 2.0時仍沿用了以前的名字。在Linux 2.1中它們改變了,但是2.0是本書的主要目標
。詳情可見第17章的“訪問用戶空間”。
盡管上面介紹的函數看起來很象正常的memcpy函數,但當在內核代碼中訪問用戶空間時
必須額外注意一些問題;正在被訪問的用戶頁面現在可能不在內存中,而且頁面失效處
理函數有可能在傳送頁面的時候讓進程進入睡眠狀態。例如,必須從交換區讀取頁面時
會發生這種情況。對驅動程序編寫者來說,靜效果就是對于任何訪問用戶空間的函數都
必須是可重入的,而且能夠與其他驅動程序函數并發執行。這就是為什么scull實現中不
允許在dev->usage不為0時釋放設備:read和write方法在它們使用memcpy函數前先完成u
sage計數加1。
現在談談實際的設備方法,讀方法的任務是將數據從設備復制到用戶空間(使用memcpy_
tofs),而寫方法必須將數據從用戶空間復制到設備(使用memcpy_tofs)。每一個read
或write系統調用請求傳輸一定量的字節,但驅動程序可以隨意傳送其中一部分數據――
讀與寫的具體規則稍有不同。
允許在dev->usage不為0時釋放設備:read和write方法在它們使用memcpy函數前先完成u
sage計數加1。
現在談談實際的設備方法,讀方法的任務是將數據從設備復制到用戶空間(使用memcpy_
tofs),而寫方法必須將數據從用戶空間復制到設備(使用memcpy_tofs)。每一個read
或write系統調用請求傳輸一定量的字節,但驅動程序可以隨意傳送其中一部分數據――
讀與寫的具體規則稍有不同。
如果有錯誤發生,read和write都返回一個負值。返回給調用程序一個大于等于0的數值
,告訴它成功傳輸了多少字節。如果某個數據成功地傳輸了,隨后發生了錯誤,返回值
必須是成功傳輸
--
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -