字符設(shè)備驅(qū)動(dòng)程序_第1頁
字符設(shè)備驅(qū)動(dòng)程序_第2頁
字符設(shè)備驅(qū)動(dòng)程序_第3頁
字符設(shè)備驅(qū)動(dòng)程序_第4頁
字符設(shè)備驅(qū)動(dòng)程序_第5頁
已閱讀5頁,還剩9頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、第3章 字符設(shè)備驅(qū)動(dòng)程序本章的目標(biāo)是編寫一個(gè)完整的字符設(shè)備驅(qū)動(dòng)程序。由于這類驅(qū)動(dòng)程序適合于大多數(shù)簡(jiǎn)單的硬件設(shè)備,我們首先開放一個(gè)字符設(shè)備驅(qū)動(dòng)程序。字符也相對(duì)比較好理解,比如說塊設(shè)備驅(qū)動(dòng)程序。我們的最終目標(biāo)是寫一個(gè)模塊化的字符設(shè)備驅(qū)動(dòng)程序,但本章我們不再講述有關(guān)模塊化的問題。本章通篇都是從一個(gè)真實(shí)的設(shè)備驅(qū)動(dòng)程序截取出的代碼塊:這個(gè)設(shè)備就是scull,是“Simple Character Utility for Loading Localities”的縮寫。盡管scull是一個(gè)設(shè)備,但它卻是操作內(nèi)存的字符設(shè)備。這種情況的一個(gè)副作用就是,只要涉及scull,“設(shè)備”這個(gè)詞就可以同“scull使用的內(nèi)

2、存區(qū)”互換使用。scull的優(yōu)點(diǎn)是,由于每臺(tái)電腦都有內(nèi)存,所以它與硬件無關(guān)。scull用kmalloc分配內(nèi)存,而且僅僅操作內(nèi)存。任何人都可以編譯和運(yùn)行scull,而且scull可以移植到所有Linux支持的平臺(tái)上。但另一方面,除了演示內(nèi)核于字符設(shè)備驅(qū)動(dòng)程序間的交互過程,可以讓用戶運(yùn)行某些測(cè)試?yán)掏?,scull做不了“有用的”事。scull的設(shè)計(jì)編寫設(shè)備驅(qū)動(dòng)程序的第一步就是定義驅(qū)動(dòng)程序提供給用戶程序的能力(“機(jī)制”)。由于我們的“設(shè)備”是電腦內(nèi)存的一部分,我做什么都可以。它可以是順便存取設(shè)備,也可以是隨機(jī)存取設(shè)備,可以是一個(gè)設(shè)備,也可以是多個(gè),等等。為了是scull更有用,可以成為編寫真實(shí)設(shè)備

3、的驅(qū)動(dòng)程序的模板,我將向你展示如何在電腦的內(nèi)存之上實(shí)現(xiàn)若干設(shè)備抽象操作,每一種操作都有自己的特點(diǎn)。scull的源碼實(shí)現(xiàn)如下設(shè)備。由模塊實(shí)現(xiàn)的每一種設(shè)備都涉及一種類型:scull0-34個(gè)設(shè)備,共保護(hù)了4片內(nèi)存區(qū),都是全局性的和持久性的?!叭中浴笔侵福绻蜷_設(shè)備多次,所有打開它的文件描述符共享其中的數(shù)據(jù)?!俺志眯浴笔侵?,如果設(shè)備關(guān)閉后再次打開,數(shù)據(jù)不丟失。由于可以使用常用命令訪問這個(gè)設(shè)備,如cp,cat以及shell I/O重定向等,這個(gè)設(shè)備操作非常有趣;本章將深入探討它的內(nèi)部結(jié)構(gòu)。scullpipe0-34個(gè)“fifo”設(shè)備,操作起來有點(diǎn)象管道。一個(gè)進(jìn)程讀取另一個(gè)進(jìn)程寫入的數(shù)據(jù)。如果有多個(gè)

4、進(jìn)程讀同一個(gè)設(shè)備,他們彼此間競(jìng)爭(zhēng)數(shù)據(jù)。通過scullpipe的內(nèi)部結(jié)構(gòu)可以了解阻塞型和非阻塞型讀/寫是如何實(shí)現(xiàn)的;沒有中斷也會(huì)出現(xiàn)這樣的情況。盡管真實(shí)的驅(qū)動(dòng)程序利用中斷與它們的設(shè)備同步,但阻塞型和非阻塞型操作是非常重要的內(nèi)容,從概念上講與中斷處理(第9章,中斷處理,介紹)無關(guān)。scullsinglescullprivsculluidscullwuid這些設(shè)備與scull0相似,但在何時(shí)允許open操作時(shí)都不同方式的限制。第一個(gè)(scullsingle)只允許一次一個(gè)進(jìn)程使用驅(qū)動(dòng)程序,而scullpriv對(duì)每個(gè)虛擬控制臺(tái)是私有的(每個(gè)設(shè)備對(duì)虛擬控制臺(tái)是私有的)。sculluid和scullwui

5、d可以多次打開,但每次只能有一個(gè)用戶;如果另一個(gè)用戶鎖住了設(shè)備,前者返回-EBUSY,而后者則實(shí)現(xiàn)為阻塞型open。通過這些可以展示如何實(shí)現(xiàn)不同的訪問策略。每一個(gè)scull設(shè)備都展示了驅(qū)動(dòng)程序不同的功能,而且都不同的難度。本章主要講解scull0-3的內(nèi)部結(jié)構(gòu);第5章,字符設(shè)備驅(qū)動(dòng)程序的擴(kuò)展操作,將介紹更復(fù)雜的設(shè)備: “一個(gè)樣例實(shí)現(xiàn):scullpipe”介紹scullpipe,“設(shè)備文件的訪問控制”介紹其他設(shè)備。主設(shè)備號(hào)和次設(shè)備號(hào)通過訪問文件系統(tǒng)的名字(或“節(jié)點(diǎn)”)訪問字符設(shè)備,通常這些文件位于/dev目錄。設(shè)備文件是特殊文件,這一點(diǎn)可以通過ls -l輸出的第一列中的“c”標(biāo)明,它說明它們是字

6、符節(jié)點(diǎn)。/dev下還有塊設(shè)備,但它們的第一列是“b”;盡管如下介紹的某些內(nèi)容也同樣適用于塊設(shè)備,現(xiàn)在我們只關(guān)注字符設(shè)備。如果你執(zhí)行l(wèi)s命令,在設(shè)備文件條目的最新修改日期前你會(huì)看到兩個(gè)數(shù)(用逗號(hào)分隔),這個(gè)位置通常顯示文件長(zhǎng)度。這些數(shù)就是相應(yīng)設(shè)備的主設(shè)備號(hào)和次設(shè)備號(hào)。下面的列表給出了我使用的系統(tǒng)上的一些設(shè)備。它們的主設(shè)備號(hào)是10,1和4,而次設(shè)備號(hào)是0,3,5,64-65和128-129。(代碼)主設(shè)備號(hào)標(biāo)識(shí)設(shè)備對(duì)應(yīng)的驅(qū)動(dòng)程序。例如,/dev/null和/dev/zero都有驅(qū)動(dòng)程序1管理,而所有的tty和pty都由驅(qū)動(dòng)程序4管理。內(nèi)核利用主設(shè)備號(hào)將設(shè)備與相應(yīng)的驅(qū)動(dòng)程序?qū)?yīng)起來。次設(shè)備號(hào)只由設(shè)備

7、驅(qū)動(dòng)程序使用;內(nèi)核的其他部分不使用它,僅將它傳遞給驅(qū)動(dòng)程序。一個(gè)驅(qū)動(dòng)程序控制若干個(gè)設(shè)備并不為奇(如上面的例子所示)次順便號(hào)提供了一種區(qū)分它們的方法。向系統(tǒng)增加一個(gè)驅(qū)動(dòng)程序意味著要賦予它一個(gè)主設(shè)備號(hào)。這一賦值過程應(yīng)該在驅(qū)動(dòng)程序(模塊)的初始化過程中完成,它調(diào)用如下函數(shù),這個(gè)函數(shù)定義在<linux/fs.h>:(代碼)返回值是錯(cuò)誤碼。當(dāng)出錯(cuò)時(shí)返回一個(gè)負(fù)值;成功時(shí)返回零或正值。參數(shù)major是所請(qǐng)求的主設(shè)備號(hào),name是你的設(shè)備的名字,它將在/proc/devices中出現(xiàn),fops是一個(gè)指向跳轉(zhuǎn)表的指針,利用這個(gè)跳轉(zhuǎn)表完成對(duì)設(shè)備函數(shù)的調(diào)用,本章稍后將在“文件操作”一節(jié)中介紹這些函數(shù)。主

8、設(shè)備號(hào)是一個(gè)用來索引靜態(tài)字符設(shè)備數(shù)組的整數(shù)。在1.2.13和早期的2.x內(nèi)核中,這個(gè)數(shù)組有64項(xiàng),而2.0.6到2.1.11的內(nèi)核則升至128。由于只有設(shè)備才處理次設(shè)備號(hào),register_chrdev不傳遞次設(shè)備號(hào)。一旦設(shè)備已經(jīng)注冊(cè)到內(nèi)核表中,無論何時(shí)操作與你的設(shè)備驅(qū)動(dòng)程序的主設(shè)備號(hào)匹配的設(shè)備文件,內(nèi)核都會(huì)通過在fops跳轉(zhuǎn)表索引調(diào)用驅(qū)動(dòng)程序中的正確函數(shù)。接下來的問題就是如何給程序一個(gè)它們可以請(qǐng)求你的設(shè)備驅(qū)動(dòng)程序的名字。這個(gè)名字必須插入到/dev目錄中,并與你的驅(qū)動(dòng)程序的主設(shè)備號(hào)和次設(shè)備號(hào)相連。在文件系統(tǒng)上創(chuàng)建一個(gè)設(shè)備節(jié)點(diǎn)的命令是mknod,而且你必須是超級(jí)用戶才能創(chuàng)建設(shè)備。除了要?jiǎng)?chuàng)建的節(jié)點(diǎn)

9、名字外,該命令還帶三個(gè)參數(shù)。例如,命令:(代碼)創(chuàng)建一個(gè)字符設(shè)備(c),主設(shè)備號(hào)是127,次設(shè)備號(hào)是0。由于歷史原因,次設(shè)備號(hào)應(yīng)該在0-255范圍內(nèi),有時(shí)它們存儲(chǔ)在一個(gè)字節(jié)中。存在很多原因擴(kuò)展可使用的次設(shè)備號(hào)的范圍,但就現(xiàn)在而言,仍然有8位限制。動(dòng)態(tài)分配主設(shè)備號(hào)某些主設(shè)備號(hào)已經(jīng)靜態(tài)地分配給了大部分公用設(shè)備。在內(nèi)核源碼樹的Documentation/device.txt文件中可以找到這些設(shè)備的列表。由于許多數(shù)字已經(jīng)分配了,為新設(shè)備選擇一個(gè)唯一的號(hào)碼是很困難的不同的設(shè)備要不主設(shè)備號(hào)多得多。很幸運(yùn)(或是感謝某些人天才),你可以動(dòng)態(tài)分配主設(shè)備號(hào)了。如果你調(diào)用register_chrdev時(shí)的major

10、為零的話,這個(gè)函數(shù)就會(huì)選擇一個(gè)空閑號(hào)碼并做為返回值返回。主設(shè)備號(hào)總是正的,因此不會(huì)和錯(cuò)誤碼混淆。我強(qiáng)烈推薦你不要隨便選擇一個(gè)一個(gè)當(dāng)前不用的設(shè)備號(hào)做為主設(shè)備號(hào),而使用動(dòng)態(tài)分配機(jī)制獲取你的主設(shè)備號(hào)。動(dòng)態(tài)分配的缺點(diǎn)是,由于分配給你的主設(shè)備號(hào)不能保證總是一樣的,無法事先創(chuàng)建設(shè)備節(jié)點(diǎn)。然而這不是什么問題,這是因?yàn)橐坏┓峙淞嗽O(shè)備號(hào),你就可以從/proc/devices讀到。為了加載一個(gè)設(shè)備驅(qū)動(dòng)程序,對(duì)insmod的調(diào)用被替換為一個(gè)簡(jiǎn)單的腳本,它通過/proc/devices獲得新分配的主設(shè)備號(hào),并創(chuàng)建節(jié)點(diǎn)。/proc/devices一般如下所示:(代碼)加載動(dòng)態(tài)分配主設(shè)備號(hào)驅(qū)動(dòng)程序的腳本可以利用象awk這

11、類工具從/proc/devices中獲取信息,并在/dev中創(chuàng)建文件。下面這個(gè)腳本,scull_load,是scull發(fā)行中的一部分。使用以模塊形式發(fā)行的驅(qū)動(dòng)程序的用戶可以在/etc/rc.d/rc.local中調(diào)用這個(gè)腳本,或是在需要模塊時(shí)手工調(diào)用。此外還有另一種方法:使用kerneld。這個(gè)方法和其他模塊的高級(jí)功能將在第11章“Kerneld和高級(jí)模塊化”中介紹。(代碼)這個(gè)腳本同樣可以適用于其他驅(qū)動(dòng)程序,只要重新定義變量和調(diào)整mknod那幾行就可以了。上面那個(gè)腳本創(chuàng)建4個(gè)設(shè)備,4是scull源碼中的默認(rèn)值。腳本的最后兩行看起來有點(diǎn)怪怪的:為什么要改變?cè)O(shè)備的組和權(quán)限呢?原因是這樣的,由ro

12、ot創(chuàng)建的節(jié)點(diǎn)自然也屬于root。默認(rèn)權(quán)限位只允許root對(duì)其有寫訪問權(quán),而其他只有讀權(quán)限。正常情況下,設(shè)備節(jié)點(diǎn)需要不同的策略,因此需要進(jìn)行某些修改。通常允許一組用戶訪問對(duì)設(shè)備,但實(shí)現(xiàn)細(xì)節(jié)卻依賴于設(shè)備和系統(tǒng)管理員。安全是個(gè)大問題,這超出了本書的范圍。scull_load中的chmod和chgrp那兩行僅僅是最為處理權(quán)限問題的一點(diǎn)提示。稍后,在第5章的“設(shè)備文件的訪問控制”一節(jié)中將介紹sculluid源碼,展示設(shè)備驅(qū)動(dòng)程序如何實(shí)現(xiàn)自己的設(shè)備訪問授權(quán)。如果重復(fù)地創(chuàng)建和刪除/dev節(jié)點(diǎn)似乎有點(diǎn)過分的話,有一個(gè)解決的方法。如果你看了內(nèi)核源碼fs/devices.c的話,你可以看到動(dòng)態(tài)設(shè)備號(hào)是從127(

13、或63)之后開始的,你可以用127做為主設(shè)備號(hào)創(chuàng)建一個(gè)長(zhǎng)命節(jié)點(diǎn),同時(shí)可以避免在每次相關(guān)設(shè)備加載時(shí)調(diào)用腳本。如果你使用了幾個(gè)動(dòng)態(tài)設(shè)備,或是新版本的內(nèi)核改變了動(dòng)態(tài)分配的特性,這個(gè)技巧就不能用了。(如果內(nèi)核發(fā)生了修改,基于內(nèi)核內(nèi)部結(jié)構(gòu)編寫的代碼并不能保證繼續(xù)可以工作。)不管怎樣,由于開發(fā)期間模塊要不斷地加載和卸載,你會(huì)發(fā)現(xiàn)這一技術(shù)在開發(fā)期間還是很有用的。就我看來,分配主設(shè)備號(hào)的最佳方式是,默認(rèn)采用動(dòng)態(tài)分配,同時(shí)留給你在加載時(shí),甚至是編譯時(shí),指定主設(shè)備號(hào)的余地。使用我建議的代碼將與自動(dòng)端口探測(cè)的代碼十分相似。scull的實(shí)現(xiàn)使用了一個(gè)全局變量,scull_major,來保存所選擇的設(shè)備號(hào)。該變量的默認(rèn)

14、值是SCULL_MAJOR,在所發(fā)行的源碼中為0,即“選擇動(dòng)態(tài)分配”。用戶可以使用這個(gè)默認(rèn)值或選擇某個(gè)特定的主設(shè)備號(hào),既可以在編譯前修改宏定義,也可以在ins_mod命令行中指定。最后,通過使用scull_load腳本,用戶可以在scull_load中命令行中將參數(shù)傳遞給insmod。這里是我在scull.c中使用的獲取主設(shè)備號(hào)的代碼:(代碼)從系統(tǒng)中刪除設(shè)備驅(qū)動(dòng)程序當(dāng)從系統(tǒng)中卸載一個(gè)模塊時(shí),應(yīng)該釋放主設(shè)備號(hào)。這一操作可以在cleanup_module中調(diào)用如下函數(shù)完成:(代碼)參數(shù)是要釋放的主設(shè)備號(hào)和相應(yīng)的設(shè)備名。內(nèi)核對(duì)這個(gè)名字和設(shè)備號(hào)對(duì)應(yīng)的名字進(jìn)行比較:如果不同,返回-ENINVAL。如果

15、主設(shè)備號(hào)超出了所允許的范圍或是并未分配給這個(gè)設(shè)備,內(nèi)核一樣返回-EINVAL。在cleanup_module中注銷資源失敗會(huì)有非常不號(hào)的后果。下次讀取/proc/devices時(shí),由于其中一個(gè)name字串仍然指向模塊內(nèi)存,而那片內(nèi)存已經(jīng)不存在了,系統(tǒng)將產(chǎn)生一次失效。這種失效稱為Oops* 怪里怪氣的Linux愛好者將“Oops”這個(gè)詞既當(dāng)成名詞也當(dāng)成動(dòng)詞使用。,內(nèi)核在訪問無效地址時(shí)將打印這樣的消息。當(dāng)你卸載驅(qū)動(dòng)程序而又無法注銷主設(shè)備號(hào)時(shí),這種情況是無法恢復(fù)的,即便為此專門寫一個(gè)“補(bǔ)救”模塊也無濟(jì)于事,因?yàn)閡nregister_chrdev中調(diào)用了strcmp,而strcmp將使用未映射的nam

16、e字串,當(dāng)釋放設(shè)備時(shí)就會(huì)使系統(tǒng)Oops。無需說明,任何視圖打開這個(gè)異常的設(shè)備號(hào)對(duì)應(yīng)的設(shè)備的操作都會(huì)Oops。除了卸載模塊,你還經(jīng)常需要在卸載驅(qū)動(dòng)程序時(shí)刪除設(shè)備節(jié)點(diǎn)。如果設(shè)備節(jié)點(diǎn)是在加載時(shí)創(chuàng)建的,可以寫一個(gè)簡(jiǎn)單的腳本在卸載時(shí)刪除它們。對(duì)于我們的樣例設(shè)備,腳本scull_unload完成這個(gè)工作。如果動(dòng)態(tài)節(jié)點(diǎn)沒有從/dev中刪除,就會(huì)有可能造成不可預(yù)期的錯(cuò)誤:如果動(dòng)態(tài)分配的主設(shè)備號(hào)相同,開發(fā)者計(jì)算機(jī)上的一個(gè)空閑/dev/framegrabber就有可能在一個(gè)月后引用一個(gè)火警設(shè)備?!皼]有這個(gè)文件或目錄”要比這個(gè)新設(shè)備所產(chǎn)生的后果要好得多。dev_t和kdev_t到目前為止,我們已經(jīng)談?wù)摿酥髟O(shè)備號(hào)?,F(xiàn)

17、在是討論次設(shè)備號(hào)和驅(qū)動(dòng)程序如何使用次設(shè)備號(hào)來區(qū)分設(shè)備的時(shí)候了。每次內(nèi)核調(diào)用一個(gè)設(shè)備驅(qū)動(dòng)程序時(shí),它都告訴驅(qū)動(dòng)程序它正在操作哪個(gè)設(shè)備。主設(shè)備號(hào)和次設(shè)備號(hào)合在一起構(gòu)成一個(gè)數(shù)據(jù)類型并用來標(biāo)別某個(gè)設(shè)備。設(shè)備號(hào)的組合(主設(shè)備號(hào)和次設(shè)備號(hào)合在一起)駐留在稍后介紹的“inode”結(jié)構(gòu)的i_rdev域中。每個(gè)驅(qū)動(dòng)程序接收一個(gè)指向struct inode的指針做為第一個(gè)參數(shù)。這個(gè)指針通常也稱為inode,函數(shù)可以通過查看inode->i_rdev分解出設(shè)備號(hào)。歷史上,Unix使用dev_t保存設(shè)備號(hào)。dev_t通常是<sys/types.h>中定義的一個(gè)16位整數(shù)。而現(xiàn)在有時(shí)需要超過256個(gè)次設(shè)

18、備號(hào),但是由于有許多應(yīng)用(包括C庫在內(nèi))都了解dev_t的內(nèi)部結(jié)構(gòu),改變dev_t是很困難的,如果改變dev_t的內(nèi)部結(jié)構(gòu)就會(huì)造成這些應(yīng)用無法運(yùn)行。因此,dev_t類型一直沒有改變;它仍是一個(gè)16位整數(shù),而且次設(shè)備號(hào)仍限制在0-255內(nèi)。然而,在Linux內(nèi)核內(nèi)部卻使用了一個(gè)新類型,kdev_t。對(duì)于每一個(gè)內(nèi)核函數(shù)來說,這個(gè)新類型被設(shè)計(jì)為一個(gè)黑箱。它的想法是讓用戶程序不能了解kdev_t。如果kdev_t一直是隱藏的,它可以在內(nèi)核的不同版本間任意變化,而不必修改每個(gè)人的設(shè)備驅(qū)動(dòng)程序。有關(guān)kdev_t的信息被禁閉在<linux/kdev_t.h>中,其中大部分是注釋。如果你對(duì)代碼后的

19、哲學(xué)感興趣的話,這個(gè)頭文件是一段很有指導(dǎo)性的代碼。因?yàn)?lt;linux/fs.h>已經(jīng)包含了這個(gè)頭文件,沒有必要顯式地包含這個(gè)文件。不幸的是,kdev_t類型是一個(gè)“現(xiàn)代”概念,在內(nèi)核版本1.2中沒有這個(gè)類型。在較新的內(nèi)核中,所有的引用設(shè)備的內(nèi)核變量和結(jié)構(gòu)字段都是kdev_t的,但是在1.2.13中同樣的變量卻是dev_t的。如果你的驅(qū)動(dòng)程序只使用它接收的結(jié)構(gòu)字段,而不聲明自己的變量的話,這不會(huì)有什么問題的。如果你需要聲明自己的設(shè)備類型變量,為了可移植性你應(yīng)該在你的頭文件中加入如下幾行:(代碼)這段代碼是樣例源碼中的sysdep.h頭文件的一部分。我不會(huì)在源碼中在引用dev_t,但是要

20、假設(shè)前一個(gè)條件語句已經(jīng)執(zhí)行了。如下這些宏和函數(shù)是你可以對(duì)kdev_t執(zhí)行的操作:MAJOR(kdev_t dev);從kdev_t結(jié)構(gòu)中分解出主設(shè)備號(hào)。MINOR(kdev_t dev);分解出次設(shè)備號(hào)。MKDEV(int ma, int mi);通過主設(shè)備號(hào)和次設(shè)備號(hào)返回kdev_t。kdev_t_to_nr(kdev_t dev);將kdev_t轉(zhuǎn)換為一個(gè)整數(shù)(dev_t)。to_kdev_t(int dev);將一個(gè)整數(shù)轉(zhuǎn)換為kdev_t。注意,核心態(tài)中沒有定義dev_t,因此使用了int。與Linux 1.2相關(guān)的頭文件定義了同樣的操作dev_t的函數(shù),但沒有那兩個(gè)轉(zhuǎn)換函數(shù),這也就是為

21、什么上面那個(gè)條件代碼簡(jiǎn)單地將它們定義返回它們的參數(shù)值。文件操作在接下來的幾節(jié)中,我們將看看驅(qū)動(dòng)程序能夠?qū)λ芾淼脑O(shè)備能夠完成哪些不同的操作。在內(nèi)核內(nèi)部用一個(gè)file結(jié)構(gòu)標(biāo)別設(shè)備,而且內(nèi)核使用file_operations結(jié)構(gòu)訪問驅(qū)動(dòng)程序的函數(shù)。這一設(shè)計(jì)是我們所看到的Linux內(nèi)核面向?qū)ο笤O(shè)計(jì)的第一個(gè)例證。我們將在以后看到更多的面向?qū)ο笤O(shè)計(jì)的例證。file_operations結(jié)構(gòu)是一個(gè)定義在<linux/fs.h>中的數(shù)指針表。結(jié)構(gòu)struct file將在以后介紹。我們已經(jīng)register_chrdev調(diào)用中有一個(gè)參數(shù)是fops,它是一個(gè)指向一組操作(open,read等等)表的

22、指針。這個(gè)表的每一個(gè)項(xiàng)都指向由驅(qū)動(dòng)程序定義的處理相應(yīng)請(qǐng)求的函數(shù)。對(duì)于你不支持的操作,該表可以包含NULL指針。對(duì)于不同函數(shù)的NULL指針,內(nèi)核具體的處理行為是不同的,下一節(jié)將逐一介紹。隨著新功能不斷加入內(nèi)核,file_operations結(jié)構(gòu)已逐漸變得越來越大(盡管從1.2.0到2.0.x并沒有增加新字段)。這種增長(zhǎng)應(yīng)該不會(huì)有什么副作用,因?yàn)樵诎l(fā)現(xiàn)任何尺寸不匹配時(shí),C編譯器會(huì)將全局或靜態(tài)struct變量中的未初始化字段填0。新的字段都加到結(jié)構(gòu)的末尾* 例如,版本2.1.31增加一個(gè)稱為lock的新字段。,所以在編譯時(shí)會(huì)插入一個(gè)NULL指針,系統(tǒng)會(huì)選擇默認(rèn)行為(記住,對(duì)于所有模塊需要加載的新內(nèi)核

23、,都要重新編譯一次模塊)。在2.1開發(fā)用內(nèi)核中,有些與fops字段相關(guān)的函數(shù)原型發(fā)生了變化。這些變化將在第17章“近期發(fā)展”的“文件操作”一節(jié)中介紹??v覽不同操作下面的列表將介紹應(yīng)用程序能夠?qū)υO(shè)備調(diào)用的所有操作。這些操作通常稱為“方法”,用面向?qū)ο蟮木幊绦g(shù)語來說就是說明一個(gè)對(duì)象聲明可以操作在自身的動(dòng)作。為了使這張列表可以用來當(dāng)作索引,我盡量使它簡(jiǎn)潔,僅僅介紹每個(gè)操作的梗概以及當(dāng)使用NULL時(shí)的內(nèi)核默認(rèn)行為。你可以在初次閱讀時(shí)跳過這張列表,以后再來查閱。在介紹完另一個(gè)重要數(shù)據(jù)結(jié)構(gòu)(file)后,本章的其余部分將講解最重要的一些操作并提供一些提示,告誡和真實(shí)的代碼樣例。由于我們尚不能深入探討內(nèi)存管

24、理和異步觸發(fā)機(jī)制,我們將在以后的章節(jié)中介紹這些更為復(fù)雜操作。struct file_operations中的操作按如下順序出現(xiàn),除非注明,它們的返回0時(shí)表示成功,發(fā)生錯(cuò)誤時(shí)返回一個(gè)負(fù)的錯(cuò)誤編碼:int (*lseek)(struct inode *, struct file *, off_t, int);方法lseek用來修改一個(gè)文件的當(dāng)前讀寫位置,并將新位置做為(正的)返回值返回。出錯(cuò)時(shí)返回一個(gè)負(fù)的返回值。如果驅(qū)動(dòng)程序沒有設(shè)置這個(gè)函數(shù),相對(duì)與文件尾的定位操作失敗,其他定位操作修改file結(jié)構(gòu)(在“file結(jié)構(gòu)”中介紹)中的位置計(jì)數(shù)器,并成功返回。2.1.0中該函數(shù)的原型發(fā)生了變化,第17章“

25、原型變化”將講解這些內(nèi)容。int (*read)(struct inode *, struct file *, char *, int);用來從設(shè)備中讀取數(shù)據(jù)。當(dāng)其為NULL指針時(shí)將引起read系統(tǒng)調(diào)用返回-EINVAL(“非法參數(shù)”)。函數(shù)返回一個(gè)非負(fù)值表示成功的讀取了多少字節(jié)。int (*write)(struct inode *, struct file *, const char *, int);向設(shè)備發(fā)送數(shù)據(jù)。如果沒有這個(gè)函數(shù),write系統(tǒng)調(diào)用向調(diào)用程序返回一個(gè)-EINVAL。注意,版本1.2的頭文件中沒有const這個(gè)說明符。如果你自己在write方法中加入了const,當(dāng)與舊頭

26、文件編譯時(shí)會(huì)產(chǎn)生一個(gè)警告。如果你沒有包含const,新版本的內(nèi)核也會(huì)產(chǎn)生一個(gè)警告;在這兩種情況你都可以簡(jiǎn)單地忽略這些警告。如果返回值非負(fù),它就表示成功地寫入的字節(jié)數(shù)。int (*readdir)(struct inode *, struct file *, void *, filldir_t);對(duì)于設(shè)備節(jié)點(diǎn)來說,這個(gè)字段應(yīng)該為NULL;它僅用于目錄。int (*select)(struct inode *, struct file *, int, select_table *);select一般用于程序詢問設(shè)備是否可讀和可寫,或是否一個(gè)“異?!睏l件發(fā)生了。如果指針為NULL,系統(tǒng)假設(shè)設(shè)備總是可

27、讀和可寫的,而且沒有異常需要處理。“異?!钡木唧w含義是和設(shè)備相關(guān)的。在當(dāng)前的2.1開發(fā)用內(nèi)核中,select的實(shí)現(xiàn)方法完全不同。(見第17章的“poll方法”)。返回值告訴系統(tǒng)條件滿足(1)或不滿足(0)。int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);系統(tǒng)調(diào)用ioctl提供一中調(diào)用設(shè)備相關(guān)命令的方法(如軟盤的格式化一個(gè)磁道,這既不是讀操作也不是寫操作)。另外,內(nèi)核還識(shí)別一部分ioctl命令,而不必調(diào)用fops表中的ioctl。如果設(shè)備不提供ioctl入口點(diǎn),對(duì)于任何內(nèi)核沒有定義的請(qǐng)求,ioctl

28、系統(tǒng)調(diào)用將返回-EINVAL。當(dāng)調(diào)用成功時(shí),返回給調(diào)用程序一個(gè)非負(fù)返回值。int (*mmap)(struct inode *, struct file *, struct vm_area_struct *);mmap用來將設(shè)備內(nèi)存映射到進(jìn)程內(nèi)存中。如果設(shè)備不支持這個(gè)方法,mmap系統(tǒng)調(diào)用將返回-ENODEV。int (*open)(struct inode *, struct file *);盡管這總是操作在設(shè)備節(jié)點(diǎn)上的第一個(gè)操作,然而并不要求驅(qū)動(dòng)程序一定要聲明這個(gè)方法。如果該項(xiàng)為NULL,設(shè)備的打開操作永遠(yuǎn)成功,但系統(tǒng)不會(huì)通知你的驅(qū)動(dòng)程序。void (*release)(struct in

29、ode *, struct file *);當(dāng)節(jié)點(diǎn)被關(guān)閉時(shí)調(diào)用這個(gè)操作。與open相仿,release也可以沒有。在2.0和更早的核心中,close系統(tǒng)調(diào)用從不失??;這種情況在版本2.1.31中有所變化(見第17章)。int (*fsync)(struct inode *, struct file *);刷新設(shè)備。如果驅(qū)動(dòng)程序不支持,fsync系統(tǒng)調(diào)用返回-EINVAL。int (*fasync)(struct inode *, struct file *, int);這個(gè)操作用來通知設(shè)備它的FASYNC標(biāo)志的變化。異步觸發(fā)是比較先進(jìn)的話題,將在第5章的“異步觸發(fā)”一節(jié)中介紹。如果設(shè)備不支持異

30、步觸發(fā),該字段可以是NULL。int (*check_media_change)(kdev_t dev);check_media_change只用于塊設(shè)備,尤其是象軟盤這類可移動(dòng)介質(zhì)。內(nèi)核調(diào)用這個(gè)方法判斷設(shè)備中的物理介質(zhì)(如軟盤)自最近一次操作以來發(fā)生了變化(返回1)或是沒有(0)。字符設(shè)備無需實(shí)現(xiàn)這個(gè)函數(shù)。int (*revalidate)(kdev_t dev);這是最后一項(xiàng),與前面提到的那個(gè)方法一樣,也只適用于塊設(shè)備。revalidate與緩沖區(qū)高速。緩存有關(guān)。我們將在第12章“加載塊設(shè)備驅(qū)動(dòng)程序”的“可移動(dòng)設(shè)備”中介紹revalidate。scull驅(qū)動(dòng)程序中適用的file_opera

31、tions結(jié)構(gòu)如下:(代碼)在最新的開發(fā)用內(nèi)核中,某些原型已經(jīng)發(fā)生了變化。該列表是從2.0.x的頭文件中提煉出來的,這里給出的原型對(duì)于大多數(shù)內(nèi)核而言都是正確的。內(nèi)核2.1引入的變化(以及為了使我們的模塊可移植所進(jìn)行的修改)在針對(duì)不同操作的每一節(jié)和第17章的“文件操作”中詳細(xì)介紹。file結(jié)構(gòu)在<linux/fs.h>中定義的struct file是設(shè)備驅(qū)動(dòng)程序所適用的又一個(gè)最重要的數(shù)據(jù)結(jié)構(gòu)。注意,file與用戶程序中的FILE沒有任何關(guān)聯(lián)。FILE是在C庫中定義且從不出現(xiàn)在內(nèi)核代碼中。而struct file是一個(gè)內(nèi)核結(jié)構(gòu),從不出現(xiàn)在用戶程序中。file結(jié)構(gòu)代表一個(gè)“打開的文件”。

32、它有內(nèi)核在open時(shí)創(chuàng)建而且在close前做為參數(shù)傳遞給如何操作在設(shè)備上的函數(shù)。在文件關(guān)閉后,內(nèi)核釋放這個(gè)數(shù)據(jù)結(jié)構(gòu)。一個(gè)“打開的文件”與由struct inode表示的“磁盤文件”有所不同。在內(nèi)核源碼中,指向struct file的指針通常稱為file或filp(“文件指針”)。為了與這個(gè)結(jié)構(gòu)相混淆,我將一直稱指針為filpflip是一個(gè)指針(同樣,它也是設(shè)備方法的參數(shù)之一),而file是結(jié)構(gòu)本身。struct file中的最重要的字段羅列如下。與上節(jié)相似,這張列表在首次閱讀時(shí)可以略過。在下一節(jié)中,我們將看到一些真正的C代碼,我將討論某些字段,到時(shí)你可以反過來查閱這張列表。mode_t f_m

33、ode;文件模式由FMODE_READ和FMODE_WRITE標(biāo)別。你可能需要在你的ioctl函數(shù)中查看這個(gè)域來來檢查讀/寫權(quán)限,但由于內(nèi)核在調(diào)用你的驅(qū)動(dòng)程序的read和write前已經(jīng)檢查了權(quán)限,你無需檢查在這兩個(gè)方法中檢查權(quán)限。例如,一個(gè)不允許的寫操作在驅(qū)動(dòng)程序還不知道的情況下就被已經(jīng)內(nèi)核拒絕了。loff_t f_ops;當(dāng)然讀/寫位置。loff_t是一個(gè)64位數(shù)值(用gcc的術(shù)語就是long long)。如果驅(qū)動(dòng)程序需要知道這個(gè)值,可以直接讀取這個(gè)字段。如果定義了lseek方法,應(yīng)該更新f_pos的值。當(dāng)傳輸數(shù)據(jù)時(shí),read和write也應(yīng)該更新這個(gè)值。unsigned short f_

34、flags;文件標(biāo)志,如O_RDONLY,O_NONBLOCK和O_SYNC。驅(qū)動(dòng)程序?yàn)榱酥С址亲枞筒僮餍枰獧z查這個(gè)標(biāo)志,而其他標(biāo)志很少用到。注意,檢查讀/寫權(quán)限應(yīng)該查看f_mode而不是f_flags。所有這些標(biāo)志都定義在<linux/fcntl.h>中。struct inode *f_inode;打開文件所對(duì)應(yīng)的i節(jié)點(diǎn)。inode指針是內(nèi)核傳遞給所有文件操作的第一個(gè)參數(shù),所以你一般不需要訪問file結(jié)構(gòu)的這個(gè)字段。在某些特殊情況下你只能訪問struct file時(shí),你可以通過這個(gè)字段找到相應(yīng)的i節(jié)點(diǎn)。struct file_operations *f_op;與文件對(duì)應(yīng)的操作。

35、內(nèi)核在完成open時(shí)對(duì)這個(gè)指針賦值,以后需要分派操作時(shí)就讀這些數(shù)據(jù)。filp->f_op中的值從不保存供以后引用;這也就是說你可以在需要的事后修改你的文件所對(duì)應(yīng)的操作,下一次再操作那個(gè)打開文件的相應(yīng)操作時(shí)就會(huì)調(diào)用新方法。例如,主設(shè)備號(hào)為1的設(shè)備(/dev/null,/dev/zero等等)的open代碼根據(jù)要打開的次設(shè)備號(hào)替換filp->f_op中的操作。這種技巧有助于在不增加系統(tǒng)調(diào)用負(fù)擔(dān)的情況下方便識(shí)別主設(shè)備號(hào)相同的設(shè)備。能夠替換文件操作的能力在面向?qū)ο缶幊碳夹g(shù)中稱為“方法重載”。void *private_data;系統(tǒng)調(diào)用open在調(diào)用驅(qū)動(dòng)程序的open方法前將這個(gè)指針置為N

36、ULL。驅(qū)動(dòng)程序可以將這個(gè)字段用于任意目的或者忽略簡(jiǎn)單忽略這個(gè)字段。驅(qū)動(dòng)程序可以用這個(gè)字段指向已分配的數(shù)據(jù),但是一定要在內(nèi)核釋放file結(jié)構(gòu)前的release方法中清除它。private_data是跨系統(tǒng)調(diào)用保存狀態(tài)信息的非常有用的資源,在我們的大部分樣例都使用了這個(gè)資源。實(shí)際的結(jié)構(gòu)里還有其他一些字段,但它們對(duì)于驅(qū)動(dòng)程序并不是特別有用。由于驅(qū)動(dòng)程序從不填寫file結(jié)構(gòu);它們只是簡(jiǎn)單地訪問別處創(chuàng)建的結(jié)構(gòu),我們可以大膽地忽略這些字段。Open和Close現(xiàn)在讓我們已經(jīng)走馬觀花地看了一遍這些字段,下面我們將開始在實(shí)際的scull函數(shù)中使用這些字段。Open方法open方法是驅(qū)動(dòng)程序用來為以后的操作完

37、成初始化準(zhǔn)備工作的。此外,open還會(huì)增加設(shè)備計(jì)數(shù),以便防止文件在關(guān)閉前模塊被卸載出內(nèi)核。在大部分驅(qū)動(dòng)程序中,open完成如下工作:l 檢查設(shè)備相關(guān)錯(cuò)誤(諸如設(shè)備未就緒或相似的硬件問題)。l 如果是首次打開,初始化設(shè)備。l 標(biāo)別次設(shè)備號(hào),如有必要更新f_op指針。l 分配和填寫要放在filp->private_data里的數(shù)據(jù)結(jié)構(gòu)。l 增加使用計(jì)數(shù)。在scull中,上面的大部分操作都要依賴于被打開設(shè)備的次設(shè)備號(hào)。因此,首先要做的事就是標(biāo)別要操作的是哪個(gè)設(shè)備。我們可以通過查看inode->i_rdev完成。我們已經(jīng)談到內(nèi)核是如何不使用次設(shè)備號(hào)的了,因此驅(qū)動(dòng)程序可以隨意使用次設(shè)備號(hào)。事

38、實(shí)上,利用不同的次設(shè)備號(hào)訪問不同的設(shè)備,或以不同的方式打開同一個(gè)設(shè)備。例如,/dev/ttyS0和/dev/ttyS1是兩個(gè)不同的串口,而/dev/cua0的物理設(shè)備與/dev/ttyS0相同,僅僅是操作行為不同。cua是“調(diào)出”設(shè)備;它們不是終端,而且它們也沒有終端所需要的所有軟件支持(即,它們沒有加入行律* “行律”是用來處理終端I/O策略的軟件模塊。)。所有的串口設(shè)備都有許多不同的次設(shè)備號(hào),這樣驅(qū)動(dòng)程序就區(qū)分它們了:ttyS與cua不一樣。驅(qū)動(dòng)程序從來都不知道被打開的設(shè)備的名字,它僅僅知道設(shè)備號(hào)而且用戶可以按照自己的規(guī)范給用設(shè)備起別名,而完全不用原有的名字。如果你看看/dev目錄就會(huì)知道

39、,你將發(fā)現(xiàn)對(duì)應(yīng)相同主/次設(shè)備號(hào)的不同名字;設(shè)備只有一個(gè)而且是相同的,而且沒有方法區(qū)分它們。例如,在許多系統(tǒng)中,/dev/psaux和/dev/bmouseps2都存在,而且它們有同樣的設(shè)備號(hào);它們可以互換使用。后者是“歷史遺跡”,你的系統(tǒng)里可以沒有。scull驅(qū)動(dòng)程序是這樣使用次設(shè)備號(hào)的:最高4位標(biāo)別設(shè)備類型個(gè)體(personality),如果該類型可以支持多實(shí)例(scull0-3和scullpipe0-3),低4位可以供你標(biāo)別這些設(shè)備。因此,scull0的高4位與scullpipe0不同,而scull0的低4位與scull1不同* 位切分一種典型的使用次設(shè)備號(hào)的方式。例如,IDE驅(qū)動(dòng)程序就使

40、用高2位表示磁盤號(hào),低6位表示分區(qū)號(hào)。源碼中定義了兩個(gè)宏(TYPE和NUM)從設(shè)備號(hào)中分解出這些位,我們馬上就看到這些宏。對(duì)于每一設(shè)備類型,scull定義了一個(gè)相關(guān)的file_operations結(jié)構(gòu),并在open時(shí)替換filp->f_op。下面的代碼就是位切分和多fops是如何實(shí)現(xiàn)的:(代碼)內(nèi)核根據(jù)主設(shè)備號(hào)調(diào)用open;scull用上面給出的宏處理次設(shè)備號(hào)。接著用TYPE索引scull_fop_array數(shù)組,從中分解出被打開設(shè)備的方法集。我在scull中所做的就是根據(jù)次設(shè)備號(hào)的類型給filp->f_op賦上正確的值。然后調(diào)用新的fops中定義的open方法。通常,驅(qū)動(dòng)程序不必

41、調(diào)用自己的fops,它只有內(nèi)核分配正確的驅(qū)動(dòng)程序方法時(shí)調(diào)用。但當(dāng)你的open方法不得不處理不同設(shè)備類型時(shí),在根據(jù)被打開設(shè)備次設(shè)備號(hào)修改fops指針后就需要調(diào)用fops->open了。scull_open的實(shí)際代碼如下。它使用了前面那段代碼中定義的TYPE和NUM兩個(gè)宏來切分次設(shè)備號(hào):(代碼)這里給一點(diǎn)解釋。用來保存內(nèi)存區(qū)的數(shù)據(jù)結(jié)構(gòu)是Scull_Dev,這里簡(jiǎn)要介紹一下。Scull_Dev和scull_trim(“Scull的內(nèi)存使用”一節(jié)中討論)的內(nèi)部結(jié)構(gòu)這里并沒有使用。全局變量scull_nr_devs和scull_devices(全部小寫)分別是可用設(shè)備數(shù)和指向Scull_Dev的指

42、針數(shù)組。這段代碼看起來工作很少,這是因?yàn)楫?dāng)調(diào)用open時(shí)它沒做任何針對(duì)某個(gè)設(shè)備的處理。由于scull0-3設(shè)備被設(shè)計(jì)為全局的和永久性的,這段代碼無需做什么。特別是,由于我們無法維護(hù)scull的打開計(jì)數(shù),也就是模塊的使用計(jì)數(shù),因此沒有類似于“首次打開時(shí)初始化設(shè)備”這類動(dòng)作。唯一實(shí)際操作在設(shè)備上的操作是,當(dāng)設(shè)備寫打開時(shí)將設(shè)備截?cái)酁殚L(zhǎng)度0。截?cái)嗍莝cull設(shè)計(jì)的一部分:用一個(gè)較短的文件覆蓋設(shè)備,以便縮小設(shè)備數(shù)據(jù)區(qū),這與普通文件寫打開截?cái)酁?很相似。但“打開是截?cái)唷庇幸粋€(gè)嚴(yán)重的缺點(diǎn):若干因?yàn)槟承┰蛟O(shè)備內(nèi)存正在使用,釋放這些內(nèi)存會(huì)導(dǎo)致系統(tǒng)失效。盡管可能性不大,這種情況總會(huì)發(fā)生:如果read和write

43、方法在數(shù)據(jù)傳輸時(shí)睡眠了,另一個(gè)進(jìn)程可能寫打開這個(gè)設(shè)備,這時(shí)麻煩就來了。處理競(jìng)爭(zhēng)條件是一個(gè)相當(dāng)高級(jí)的主題,我將在第9章的“競(jìng)爭(zhēng)條件”中講解。scull處理這個(gè)問題的簡(jiǎn)單方法就是在內(nèi)存還是使用時(shí)不釋放內(nèi)存,“Scull的內(nèi)存使用”一節(jié)中將說明。以后我們看到其他scull個(gè)體(personality)時(shí)將會(huì)看到一個(gè)真正的初始化工作如何完成。release方法release方法的作用正好與open相反。這個(gè)設(shè)備方法有時(shí)也稱為close。它應(yīng)該:l 使用計(jì)數(shù)減1。l 釋放open分配在filp->private_data中的內(nèi)存。l 在最后一次關(guān)閉操作時(shí)關(guān)閉設(shè)備。scull的基本模型無需進(jìn)行關(guān)閉設(shè)

44、備動(dòng)作,所以所需代碼是很少的* 由于scull_open用不同的fops替換了filp->f_ops,不同種類的設(shè)備會(huì)使用不同的函數(shù)完成關(guān)閉操作,這一點(diǎn)我們將在后面看到。:(代碼)使用計(jì)數(shù)減1是非常重要的,因?yàn)槿绻褂糜?jì)數(shù)不歸0,內(nèi)核是不會(huì)卸載模塊的。如果某個(gè)時(shí)刻一個(gè)從沒被打開的文件被關(guān)閉了計(jì)數(shù)將如何保證一致呢?我們都知道,dup和fork都會(huì)在不調(diào)用open的情況下,將一個(gè)打開文件復(fù)制為2個(gè),但每一個(gè)都會(huì)在程序終止時(shí)關(guān)閉。例如,大多數(shù)程序從來不打開它們的stdin文件(或設(shè)備),但它們都會(huì)在終止關(guān)閉它。答案很簡(jiǎn)單。如果open沒有調(diào)用,release也不會(huì)調(diào)。內(nèi)核維護(hù)一個(gè)file結(jié)構(gòu)被

45、使用了多少次的使用計(jì)數(shù)。無論是fork還是dup都不創(chuàng)建新的數(shù)據(jù)結(jié)構(gòu);它們僅是增加已有結(jié)構(gòu)的計(jì)數(shù)。新的struct file僅由open創(chuàng)建。只有在該結(jié)構(gòu)的計(jì)數(shù)歸0時(shí)close系統(tǒng)調(diào)用才會(huì)執(zhí)行close方法,這只有在刪除這個(gè)結(jié)構(gòu)時(shí)才會(huì)進(jìn)行。close方法與close系統(tǒng)調(diào)用間的關(guān)系保證了模塊使用計(jì)數(shù)永遠(yuǎn)是一致的。Scull的內(nèi)存使用在介紹讀寫操作以前,我們最好先看看scull如何完成內(nèi)存分配以及為什么要完成內(nèi)存分配。為了全面理解代碼我們需要知道“如何分配”,而“為什么”則反映了驅(qū)動(dòng)程序編寫者需要做出的選擇,盡管scull絕不是一個(gè)典型設(shè)備,但同樣需要。本節(jié)只講解scull中的內(nèi)存分配策略,而不

46、會(huì)講解你寫實(shí)際驅(qū)動(dòng)程序時(shí)需要的硬件管理技巧。這些技巧將在第8章“硬件管理”和第9章中介紹。因此,如果你對(duì)針對(duì)內(nèi)存操作的scull驅(qū)動(dòng)程序的內(nèi)部工作原理不感興趣的話,你可以跳過這一節(jié)。scull使用的內(nèi)存,這里也稱為“設(shè)備”,是變長(zhǎng)的。你寫的越多,它就增長(zhǎng)得越多;消減的過程只在用短文件覆蓋設(shè)備時(shí)發(fā)生。所選的實(shí)現(xiàn)scull的方法不是很聰明。實(shí)現(xiàn)較聰明的源碼會(huì)更難讀,而且本節(jié)的目的只是講解read和write,而不是內(nèi)存管理。這也就是為什么雖然整個(gè)頁面分配會(huì)更有效,但代碼只使用了kmalloc和kfree,而沒有涉及整個(gè)頁面的分配的操作。而另一面,從理論和實(shí)際角度考慮,我又不想限制“設(shè)備”區(qū)的尺寸。

47、理論上將,給所管理的數(shù)據(jù)項(xiàng)強(qiáng)加任何限制總是很糟糕的想法。從實(shí)際出發(fā),為了測(cè)試系統(tǒng)在內(nèi)存短缺時(shí)的性能,scull可以幫助將系統(tǒng)的剩余內(nèi)存用光。進(jìn)行這樣的測(cè)試有助于你理解系統(tǒng)的內(nèi)部行為。你可以使用命令cp /dev/zero /dev/scull用光所有的物理內(nèi)存,而且你也可以用工具dd選擇復(fù)制到scull設(shè)備中多少數(shù)據(jù)。在scull中,每個(gè)設(shè)備都是一組指針的鏈表,而每一個(gè)指針又指向一個(gè)Scull_Dev結(jié)構(gòu)。每一個(gè)這樣的結(jié)構(gòu)通過一個(gè)中間級(jí)指針數(shù)組最多可引用4,000,000個(gè)字節(jié)。發(fā)行的源碼中使用了一個(gè)有1000個(gè)指針的數(shù)組,每個(gè)指針指向4000個(gè)字節(jié)。我把每一個(gè)內(nèi)存區(qū)稱為一個(gè)“量子”,數(shù)組(或

48、它的長(zhǎng)度)稱為“量子集”。scull設(shè)備和它的內(nèi)存區(qū)如圖3-1所示。所選擇的數(shù)字是這樣的,向scull寫一個(gè)字節(jié)就會(huì)消耗內(nèi)存8000了字節(jié):每個(gè)量子4個(gè),量子集4個(gè)(在大多數(shù)平臺(tái)上,一個(gè)指針是4個(gè)字節(jié);當(dāng)在Alpha平臺(tái)編譯時(shí)量子集本身就會(huì)耗費(fèi)8000個(gè)字節(jié),在Alpha平臺(tái)上指針是8個(gè)字節(jié))。但另一方面,如果你向scull寫大量的數(shù)據(jù),由于每4MB數(shù)據(jù)只對(duì)應(yīng)一個(gè)表項(xiàng),而且設(shè)備的最大尺寸只限于若干MB,不可能超出計(jì)算機(jī)內(nèi)存的大小,遍歷這張鏈表的代價(jià)不是很大。為量子和量子集選擇合適的數(shù)值是一個(gè)策略問題,而非機(jī)制問題,而且最優(yōu)數(shù)值依賴于如何使用設(shè)備。源碼中為處理這些問題允許用戶修改這些值:l 在編

49、譯時(shí),可以修改scull.h中的SCULL_QUANTUM和SCULL_QSET。l 在加載時(shí),可以利用insmod修改scull_quantum和scull_qset整數(shù)值。l 在運(yùn)行時(shí),用ioctl方法改變默認(rèn)值和當(dāng)前值。ioctl將在第5章的“ioctl”一節(jié)中介紹。使用宏和整數(shù)值進(jìn)行編譯時(shí)和加載時(shí)配置讓人想起前面提到的如何選擇主設(shè)備號(hào)。無論何時(shí)驅(qū)動(dòng)程序需要一個(gè)隨意的數(shù)值或這個(gè)數(shù)值與策略相關(guān),我都使用這種技術(shù)。留下來的唯一問題就是如何選擇默認(rèn)數(shù)值。盡管有時(shí)驅(qū)動(dòng)程序編寫者也需要事先調(diào)整配置參數(shù),但他們?cè)诰帉懽约旱哪K時(shí)不會(huì)碰到同樣的問題。在這個(gè)特殊的例子里,問題的關(guān)鍵在于尋找因未填滿的量子

50、和量子集導(dǎo)致的內(nèi)存浪費(fèi)和量子和量子集太小帶來的分配、釋放和指針連接等操作的代價(jià)之間的平衡。此外,還必須考慮kmalloc的內(nèi)部設(shè)計(jì)。現(xiàn)在我們還無法講述太多的細(xì)節(jié),只能簡(jiǎn)單規(guī)定“比2次冪稍小一點(diǎn)是最佳尺寸”比較好。kmalloc的內(nèi)部結(jié)構(gòu)將在第7章“Getting Hold of Memory”的“The Real Story of kmalloc”一節(jié)中探討。默認(rèn)數(shù)值的選擇基于這樣的假設(shè),大部分程序員不會(huì)受限與4MB的物理內(nèi)存,那樣大的數(shù)據(jù)量有可能會(huì)寫到scull中。一臺(tái)內(nèi)存很多的計(jì)算機(jī)的屬主可能因測(cè)試向設(shè)備寫數(shù)十MB的數(shù)據(jù)。因此,所選的默認(rèn)值是為了優(yōu)化中等規(guī)模的系統(tǒng)和大數(shù)據(jù)量的使用。保存設(shè)備

51、信息的數(shù)據(jù)機(jī)構(gòu)如下:(代碼)下面的代碼給出了實(shí)際工作時(shí)是如何利用Scull_Dev保存數(shù)據(jù)的。其中給出的函數(shù)負(fù)責(zé)釋放整個(gè)數(shù)據(jù)區(qū),并且在文件寫打開時(shí)由scull_open調(diào)用。如果當(dāng)前設(shè)備內(nèi)存正在使用,該函數(shù)就不釋放這些內(nèi)存(象“open方法”中所說那樣);否則,它簡(jiǎn)單地遍歷鏈表,釋放所有找到的量子和量子集。(代碼)讀和寫讀寫scull設(shè)備也就意味著要完成內(nèi)核空間和用戶進(jìn)程空間的數(shù)據(jù)傳輸。由于指針只能在當(dāng)前地址空間操作,而驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,數(shù)據(jù)緩沖區(qū)則在用戶空間,這一操作不能通過通常利用指針或memcpy完成。由于驅(qū)動(dòng)程序不過怎樣都要在內(nèi)核空間和用戶緩沖區(qū)間復(fù)制數(shù)據(jù),如果目標(biāo)設(shè)備不是RAM

52、而是擴(kuò)展卡,也有同樣的問題。事實(shí)上,設(shè)備驅(qū)動(dòng)程序的主要作用就是管理設(shè)備(內(nèi)核空間)和應(yīng)用(用戶空間)間的數(shù)據(jù)傳輸。在Linux里,跨空間復(fù)制是通過定義在<asm/segment.h>里的特殊函數(shù)實(shí)現(xiàn)的。完成這種操作函數(shù)針對(duì)不同數(shù)據(jù)尺寸(char,short,int,long)進(jìn)行了優(yōu)化;它們中的大部分將在第5章的“使用ioctl參數(shù)”一節(jié)中介紹。scull中read和write的驅(qū)動(dòng)程序代碼需要完成到用戶空間和來自用戶空間的整個(gè)數(shù)據(jù)段的復(fù)制。下面這些提供這些功能,它們可以傳輸任意字節(jié):(代碼)這兩個(gè)函數(shù)的名字可以追溯到第1版Linux,那時(shí)唯一支持的體系結(jié)構(gòu)是i386,而且C代碼中

53、還可以窺見許多匯編碼。在Intel平臺(tái)上,Linux通過FS段寄存器訪問用戶空間,到Linux 2.0時(shí)仍沿用了以前的名字。在Linux 2.1中它們改變了,但是2.0是本書的主要目標(biāo)。詳情可見第17章的“訪問用戶空間”。盡管上面介紹的函數(shù)看起來很象正常的memcpy函數(shù),但當(dāng)在內(nèi)核代碼中訪問用戶空間時(shí)必須額外注意一些問題;正在被訪問的用戶頁面現(xiàn)在可能不在內(nèi)存中,而且頁面失效處理函數(shù)有可能在傳送頁面的時(shí)候讓進(jìn)程進(jìn)入睡眠狀態(tài)。例如,必須從交換區(qū)讀取頁面時(shí)會(huì)發(fā)生這種情況。對(duì)驅(qū)動(dòng)程序編寫者來說,靜效果就是對(duì)于任何訪問用戶空間的函數(shù)都必須是可重入的,而且能夠與其他驅(qū)動(dòng)程序函數(shù)并發(fā)執(zhí)行。這就是為什么sc

54、ull實(shí)現(xiàn)中不允許在dev->usage不為0時(shí)釋放設(shè)備:read和write方法在它們使用memcpy函數(shù)前先完成usage計(jì)數(shù)加1?,F(xiàn)在談?wù)剬?shí)際的設(shè)備方法,讀方法的任務(wù)是將數(shù)據(jù)從設(shè)備復(fù)制到用戶空間(使用memcpy_tofs),而寫方法必須將數(shù)據(jù)從用戶空間復(fù)制到設(shè)備(使用memcpy_tofs)。每一個(gè)read或write系統(tǒng)調(diào)用請(qǐng)求傳輸一定量的字節(jié),但驅(qū)動(dòng)程序可以隨意傳送其中一部分?jǐn)?shù)據(jù)讀與寫的具體規(guī)則稍有不同。如果有錯(cuò)誤發(fā)生,read和write都返回一個(gè)負(fù)值。返回給調(diào)用程序一個(gè)大于等于0的數(shù)值,告訴它成功傳輸了多少字節(jié)。如果某個(gè)數(shù)據(jù)成功地傳輸了,隨后發(fā)生了錯(cuò)誤,返回值必須是成功傳

55、輸?shù)淖止?jié)計(jì)數(shù),而錯(cuò)誤直到下次函數(shù)被調(diào)用時(shí)才返回給程序。read的不同參數(shù)的相應(yīng)功能如圖3-2所示。(圖3-2)內(nèi)核函數(shù)返回一個(gè)負(fù)值通知錯(cuò)誤,該數(shù)的數(shù)值表示已經(jīng)發(fā)生的錯(cuò)誤種類(如第2章“編寫和運(yùn)行模塊”的“init_module中的錯(cuò)誤處理”所述),運(yùn)行在用戶空間的程序訪問變量errno獲知發(fā)生什么錯(cuò)誤。這兩方面的不同行為,一方面壞是靠庫規(guī)范強(qiáng)加的,另一方面是內(nèi)核不處理errno的優(yōu)點(diǎn)導(dǎo)致的?,F(xiàn)在再來談?wù)効梢浦残?,你一定?huì)注意到read和write方法中參數(shù)count的類型總是int,但在內(nèi)核2.1.0中卻變?yōu)閡nsigned long。而且,由于long既可以表示count也可以表示負(fù)的錯(cuò)誤編

56、碼,方法的返回值也從int變?yōu)榱薼ong。類型的修改是有益的:對(duì)于count來說,unsigned long與int相比是一個(gè)更好的選擇,它有更寬廣的值域。這種選擇非常好,Alpha小組在2.1尚為發(fā)行時(shí)就修改了類型(主要是因?yàn)镚NU C庫在它們的系統(tǒng)調(diào)用定義中使用的是unsigned long)。盡管有好處,這一修改卻帶來了驅(qū)動(dòng)程序代碼的平臺(tái)相關(guān)性。為了繞過這個(gè)問題,所有的OReilly FTP站點(diǎn)上的樣例模塊都使用了如下定義(來自sysdep.h):(代碼)在宏計(jì)算后,read和write的count計(jì)數(shù)就永遠(yuǎn)聲明為count_t,而返回值則是read_write_t的。我為沒有使用typ

57、edef,而是使用了宏定義,這是因?yàn)榧尤雝ypedef后會(huì)引起更多的編譯告警(見第10章“明智使用數(shù)據(jù)類型”中的“接口相關(guān)類型”一節(jié))。另一方面,函數(shù)原型中出現(xiàn)大寫的名字很難看,所以我使用標(biāo)準(zhǔn)typedef規(guī)范命名新“類型”。第17章將更詳細(xì)地介紹版本2.1的可移植性。read方法調(diào)用程序?qū)ead返回值的解釋如下:l 如果返回值等于最為count參數(shù)傳遞給read系統(tǒng)調(diào)用的值,所請(qǐng)求的字節(jié)數(shù)傳輸就成功完成了。這是最好的情況。l 如果返回值是正的,但是比count小,只有部分?jǐn)?shù)據(jù)成功傳送。這種情況因設(shè)備的不同可能有許多原因。大部分情況下,程序會(huì)重新讀數(shù)據(jù)。例如,如果你用fread函數(shù)讀數(shù)據(jù),這

58、個(gè)庫庫函數(shù)會(huì)不斷調(diào)用系統(tǒng)調(diào)用直至所請(qǐng)求的數(shù)據(jù)傳輸完成。l 如果返回值為0,它表示已經(jīng)到達(dá)了文件尾。l 負(fù)值意味著發(fā)生了錯(cuò)誤。值就是錯(cuò)誤編碼,錯(cuò)誤編碼在<linux/errno.h>中定義。上面的表格中遺漏了一種情況,就是“沒有數(shù)據(jù),但以后會(huì)有”。在這種情況下,read系統(tǒng)調(diào)用應(yīng)該阻塞。我們將在第5章的“阻塞型I/O”一節(jié)中處理阻塞出入。scull代碼利用了這些規(guī)則。特別是,它利用了部分讀規(guī)則。每一次調(diào)用scull_read只處理一個(gè)數(shù)據(jù)量子,而不必實(shí)現(xiàn)循環(huán)收集所有數(shù)據(jù);這樣一來代碼就更短更易讀了。如果讀程序確實(shí)需要更多的數(shù)據(jù),它可以重新調(diào)用這個(gè)調(diào)用。如果用標(biāo)準(zhǔn)庫讀取設(shè)備,應(yīng)用程序都不會(huì)注意到數(shù)據(jù)傳送的量子化過程。如果當(dāng)

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論