畢業(yè)后一直在學(xué)操作系統(tǒng), 有時候覺得什么都懂了,有時候又覺得好像什么都不懂,但總體來說自認為對操作系統(tǒng)實現(xiàn)機制的了解比周圍的人還是要多一些。去年曾花了幾個星期的晚上時間斷斷續(xù)續(xù)翻譯了這篇對Linux和Windows驅(qū)動架構(gòu)進行比較的論文。原文在這里。
1. 概述
這篇論文中,我們將考查目前最為廣泛使用的兩種操作系統(tǒng),即Linux和Windows系統(tǒng)的設(shè)備驅(qū)動架構(gòu)。為每種操作系統(tǒng)實現(xiàn)設(shè)備驅(qū)動所需要的驅(qū)動組件將被展示并進行比較,同時也展示每種操作系統(tǒng)中執(zhí)行I/O到內(nèi)核緩沖的驅(qū)動的實現(xiàn)過程。最后將以對每種操作系統(tǒng)為開發(fā)者所提供的開發(fā)環(huán)境和輔助設(shè)施的考查收尾。
2. 引言
現(xiàn)代操作系統(tǒng)中包含多個模塊,諸如內(nèi)存管理器、進程調(diào)度器、硬件抽象層和安全管理器。欲了解Windows內(nèi)核的細節(jié),可參考[Russinovich, 98],Linux內(nèi)核則參考[Rusling, 99], [Beck et al, 98]。內(nèi)核可被看作一個黑盒,并且它應(yīng)該知道如何與現(xiàn)存的不同種類的以及還沒出現(xiàn)的更多硬件設(shè)備進行交互。實現(xiàn)一個內(nèi)核,使其能夠與所有被熟知的硬件設(shè)備進行交互是可能的,但并不現(xiàn)實,因為這會消耗太多的系統(tǒng)資源,沒有必要。
在內(nèi)核被創(chuàng)建的時候,并不期望它能知道如何與未出現(xiàn)的設(shè)備進行交互?,F(xiàn)代操作系統(tǒng)內(nèi)核允許通過在運行時添加設(shè)備驅(qū)動模塊的方式來擴展系統(tǒng)功能,這個模塊的功能使得內(nèi)核可以與某種特定的新設(shè)備進行交互。每個模塊都提供一個例程供內(nèi)核在模塊被加載時進行調(diào)用,還有一個例程在模塊被移除時調(diào)用。每個模塊還實現(xiàn)各種不同的例程以便實現(xiàn)將數(shù)據(jù)傳送到設(shè)備或從設(shè)備接收數(shù)據(jù)的I/O功能,同時還有一個例程供發(fā)送I/O控制指令到設(shè)備。以上所述對Linux和Windows兩種驅(qū)動架構(gòu)均適用。
本論文分成下面幾節(jié):
l 兩種操作系統(tǒng)大致的驅(qū)動架構(gòu)(第二節(jié))
l 每種操作系統(tǒng)驅(qū)動架構(gòu)組件(第三節(jié))
l 實現(xiàn)一個驅(qū)動執(zhí)行I/O到內(nèi)核緩沖(第四節(jié))
l 兩種操作系統(tǒng)為開發(fā)者提供的驅(qū)動開發(fā)環(huán)境和輔助設(shè)施(第五節(jié))
Windows設(shè)備驅(qū)動架構(gòu)相關(guān)文檔可在Windows驅(qū)動開發(fā)包中找到,更有甚者,Walter Oney [Oney, 99] 和Chris Cant [Cant, 99] 對Windows驅(qū)動架構(gòu)進行了詳細的展示。Linux設(shè)備驅(qū)動架構(gòu)則由Rubini et al [Rubini et al,01]作了很好的描述,可免費獲取。
3. 設(shè)備驅(qū)動架構(gòu)
設(shè)備驅(qū)動通過暴露編程接口的方式使得應(yīng)用程序和操作系統(tǒng)可以對設(shè)備進行控制來達到對硬件的操作。這一節(jié)將展示當前最常用的兩個操作系統(tǒng),即Windows和Linux的驅(qū)動架構(gòu),以及這些架構(gòu)的起源。
Linux可以說是Unix操作系統(tǒng)的一個克隆,首先由Linus Travolds創(chuàng)造 [Linus FAQ, 02], [LinuxHQ,02]。Linux沿用了類似于Unix的系統(tǒng)架構(gòu)。Unix系統(tǒng)將設(shè)備看作是文件系統(tǒng)的節(jié)點。設(shè)備以特殊文件節(jié)點的方式呈現(xiàn)在目錄中,該目錄通常包含設(shè)備文件系統(tǒng)的節(jié)點入口[Deitel, 90]。用文件系統(tǒng)節(jié)點來表示設(shè)備的目的是使得應(yīng)用程序能以設(shè)備無關(guān)的方式訪問各種設(shè)備[Massie, 86],[Flynn et al, 97]。應(yīng)用程序仍然可以通過I/O控制操作進行特定于設(shè)備的操作。設(shè)備由主設(shè)備號和次社保號進行標識。主設(shè)備號用來作為驅(qū)動數(shù)組的索引下標,而次設(shè)備號將相似的物理設(shè)備歸組[Deitel, 90]。Unix有兩種類型的設(shè)備,即字符設(shè)備和塊設(shè)備。字符設(shè)備驅(qū)動管理沒有緩沖并需要順序訪問的設(shè)備,塊設(shè)備驅(qū)動管理那些可隨機訪問的設(shè)備,數(shù)據(jù)以塊的方式被訪問。另外,塊設(shè)備驅(qū)動還用到緩沖區(qū)。塊設(shè)備必須以文件系統(tǒng)節(jié)點的方式掛載之后才能被訪問[Beck et al, 98]。Linux保留了Unix的很多架構(gòu)設(shè)計,區(qū)別在于,Unix系統(tǒng)中,每個塊設(shè)備需要創(chuàng)建一個對應(yīng)的字符設(shè)備,而在Linux中,虛擬文件系統(tǒng)(VFS)接口使得字符設(shè)備和塊設(shè)備的區(qū)分變得模糊[Beck et al, 98]。Linux還引入了第三種設(shè)備,叫網(wǎng)絡(luò)設(shè)備。訪問網(wǎng)絡(luò)設(shè)備驅(qū)動的方式和訪問字符設(shè)備、塊設(shè)備的方式不同,它使用了不同于文件系統(tǒng)I/O接口的一個接口集。比如socket接口,就是用來訪問網(wǎng)絡(luò)設(shè)備的。
1980年,微軟從貝爾實驗室獲取Unix操作系統(tǒng)的許可,之后作為XENIX操作系統(tǒng)發(fā)布。1981年,MS DOS第一版隨著IBM PC發(fā)布,并具有和基于XENIX的Unix系統(tǒng)類似的驅(qū)動架構(gòu)[Deitel, 90]。和Unix操作系統(tǒng)不同的是,這個系統(tǒng)內(nèi)嵌了常規(guī)設(shè)備所需的驅(qū)動程序。設(shè)備入口不以文件系統(tǒng)節(jié)點的形式呈現(xiàn),而是給設(shè)備賦予預(yù)留的名稱,如CON表示鍵盤或屏幕,PRN表示打印機,AUX表示串口。應(yīng)用程序不能像對待文件系統(tǒng)節(jié)點那樣通過打開設(shè)備獲取和驅(qū)動程序關(guān)聯(lián)的設(shè)備句柄從而對設(shè)備進行I/O操作。操作系統(tǒng)透明地將預(yù)留的設(shè)備名稱對應(yīng)到驅(qū)動程序所管理的設(shè)備。MS DOS第二版引進了可加載驅(qū)動的概念。由于Microsoft公開了驅(qū)動架構(gòu)的接口,這也促進了第三方設(shè)備制造商生產(chǎn)更多設(shè)備 [Davis, 83]。硬件制造商可以為這些新設(shè)備提供運行時可加載到內(nèi)核或從內(nèi)核移除的驅(qū)動程序。
之后,Microsoft又發(fā)布了Windows 3.1,它支持更多的設(shè)備并使用基于MS DOS的架構(gòu)。之后的Windows 95、98和NT,Microsoft引入了WDM(Windows Driver Mode)。WDM的出現(xiàn)是因為Microsoft想要驅(qū)動程序代碼和后面所有新的操作系統(tǒng)兼容[Microsoft WDM, 02]。因此,驅(qū)動遵守WDM規(guī)范的好處是,驅(qū)動程序只需編寫一次,在Microsoft之后所有新版操作系統(tǒng)上使用時只需要重新編譯該驅(qū)動即可。
Windows驅(qū)動分兩類,分別為遺留驅(qū)動和即插即用驅(qū)動。這里我們只將重點放在PnP驅(qū)動上,所有提及的驅(qū)動都大可認為是PnP驅(qū)動。PnP驅(qū)動不需費什么力氣就能安裝好,因此它對用戶是友好的。另外一個使驅(qū)動程序支持PnP的好處是它們只會在需要的時候被操作系統(tǒng)加載,因此它們不會無端的耗盡系統(tǒng)資源。遺留驅(qū)動是為Microsoft早期的操作系統(tǒng)實現(xiàn)的,它們的架構(gòu)已經(jīng)過時。WDM是Microsoft指定的標準驅(qū)動模型[Microsoft DDK, 02]。WDM驅(qū)動適用于Microsoft近期所有的操作系統(tǒng)(Windows 95和之后的)。
WDM驅(qū)動分三類,分別為過濾驅(qū)動、功能驅(qū)動和總線驅(qū)動[Oney, 01]。它們形成了圖2.3所示的棧式結(jié)構(gòu)。另外,WDM驅(qū)動必須是具有PnP感知的,支持電源管理和Windows管理規(guī)范(Windows Management Instrumentation)。圖2.3顯示了各個驅(qū)動如何交互數(shù)據(jù)和消息。一個叫I/O請求包(IRP,I/O Request Package)的標準結(jié)構(gòu)被用來進行通信。任何時候,應(yīng)用程序向驅(qū)動程序發(fā)送請求時,I/O管理將創(chuàng)建IRP并下傳到驅(qū)動程序,驅(qū)動程序處理完畢后,“完成”這個IRP [Cant, 99]。不是所有的IRP都被下發(fā)到總線驅(qū)動,有些IRP被上層的驅(qū)動處理后直接返回到I/O管理器。對設(shè)備硬件的訪問需要通過硬件抽象層。
Figure 2.3 The WDM Driver Architecture
Linux下的驅(qū)動以模塊的形式呈現(xiàn),這些模塊就是擴展了Linux內(nèi)核功能的一個個代碼塊[Rubini et al, 01]。模塊可形成圖2.4那樣的層次結(jié)構(gòu)。模塊之間的通信通過函數(shù)調(diào)用實現(xiàn)。模塊在加載時,將導(dǎo)出模塊中所有對Linux內(nèi)核所維護的符號表公開的函數(shù),之后這些函數(shù)對所有的內(nèi)核模塊都是可見的。對設(shè)備的訪問需要通過硬件抽象層,硬件抽象層的實現(xiàn)依賴于內(nèi)核編譯時所針對的硬件平臺,如x86或SPARC。
Figure 2.4 The Linux Driver Architecture
如圖2.3和2.4所示,兩個操作系統(tǒng)有很多的相似之處。兩個系統(tǒng)中,驅(qū)動程序都是作為擴展內(nèi)核功能的模塊化組件。在Windows系統(tǒng)中,驅(qū)動層級之間的通信是通過將IRP作為標準系統(tǒng)函數(shù)或驅(qū)動程序自定義函數(shù)的參數(shù)來實現(xiàn),Linux下函數(shù)調(diào)用的參數(shù)則是根據(jù)具體的驅(qū)動而不同。Windows有單獨的內(nèi)核模塊來管理PnP、I/O和電源,這些組件在適當?shù)臅r候?qū)?/span>IRP發(fā)送到驅(qū)動程序。
Linux系統(tǒng)中,模塊沒有明顯的層級關(guān)系,比如沒有區(qū)分總線、功能、過濾驅(qū)動。內(nèi)核沒有明確定義的PnP、電源管理器以便在適當?shù)臅r候?qū)⑻囟ǖ男畔l(fā)送給內(nèi)核模塊。內(nèi)核可能會加載具有PnP、電源管理功能的內(nèi)核模塊,但內(nèi)核模塊暴露給驅(qū)動程序的接口并沒有規(guī)定。
這些功能一般會合并到新版的Linux內(nèi)核中,因為Linux內(nèi)核總是處于發(fā)展狀態(tài)。每當內(nèi)核將數(shù)據(jù)發(fā)送給棧式模塊中的某個驅(qū)動程序時,通過這些驅(qū)動所指定的某個接口,該數(shù)據(jù)可被分享給棧式模塊中的其他驅(qū)動程序。
在這兩種系統(tǒng)環(huán)境中,對硬件的訪問都會通過硬件抽象層接口,硬件抽象層接口根據(jù)內(nèi)核編譯時所針對的特定平臺(如X86,SPARC等)而實現(xiàn)。兩種架構(gòu)的相同之處在于,驅(qū)動程序都是運行時可加載的模塊,每個模塊都包含一個入口點使內(nèi)核知道從哪里開始執(zhí)行模塊的代碼。一個模塊還包含這樣一些例程,即該模塊所管理的設(shè)備接收到I/O操作請求時供內(nèi)核調(diào)用的例程。這使得內(nèi)核可以向應(yīng)用層提供設(shè)備無關(guān)的接口。在后面的第3.3節(jié)中,將對兩種架構(gòu)中的驅(qū)動組件作更深入的比較。
編寫驅(qū)動程序時需要對硬件設(shè)備如何被操作有了解。比如說,幾乎所有的設(shè)備都允許用戶讀取和寫入數(shù)據(jù)。這一節(jié)中將展示所有驅(qū)動程序都應(yīng)該包含的驅(qū)動組件,同時對兩種操作系統(tǒng)的驅(qū)動組件進行比較,并展示如何實現(xiàn)一個對內(nèi)核緩沖區(qū)進行I/O操作的驅(qū)動程序。本節(jié)將以對每種操作系統(tǒng)為驅(qū)動程序開發(fā)所提供的環(huán)境和輔助設(shè)施的考究來收尾。
Windows驅(qū)動程序由各種不同的例程組成,其中有一些是必須的,其它的則是可選的。這一節(jié)展示所有驅(qū)動程序都必須實現(xiàn)的例程。Windows中的設(shè)備驅(qū)動以一個叫DriverObject的結(jié)構(gòu)體表示。用一個結(jié)構(gòu)體諸如驅(qū)動對象來表示一個驅(qū)動是有必要的,因為內(nèi)核實現(xiàn)了可被所有驅(qū)動對象使用的各種例程。這些例程對一個驅(qū)動對象進行操作,這部分內(nèi)容將在下一節(jié)進行討論。
Windows中每個設(shè)備驅(qū)動程序都包含一個叫DriverEntry的例程。顧名思義,這個例程在驅(qū)動程序被加載時執(zhí)行,驅(qū)動所管理的設(shè)備對象的初始化也在這個例程中進行。Microsoft’s DDK [Microsoft DDK, 02] 是這樣描述的:驅(qū)動對象表示當前被加載的驅(qū)動程序,設(shè)備對象則表示一個物理、邏輯或虛擬的設(shè)備。一個被加載的驅(qū)動程序(用驅(qū)動對象表示)可以管理多個設(shè)備(用設(shè)備對象表示)。初始化過程中,設(shè)備對象中用以指定驅(qū)動程序的卸載例程、添加設(shè)備例程和分發(fā)例程都將被設(shè)置。卸載例程用于驅(qū)動程序被卸載時做一些清除操作,例如釋放從內(nèi)核堆中的分配的內(nèi)存。添加設(shè)備例程僅在當驅(qū)動程序作為PnP驅(qū)動加載時,在DriverEntry例程之后被調(diào)用,而分發(fā)例程用于實現(xiàn)I/O操作。
PnP驅(qū)動程序需要實現(xiàn)AddDevice例程。在這個例程中,一個設(shè)備對象被創(chuàng)建,為該設(shè)備保存全局數(shù)據(jù)的空間被分配。設(shè)備資源的分配和初始化也在這里進行。設(shè)備對象根據(jù)其被創(chuàng)建的位置而擁有不同的名稱。如果一個設(shè)備在當前加載的驅(qū)動程序中創(chuàng)建并用于管理該驅(qū)動,則該設(shè)備叫功能設(shè)備對象(FDO)。如果一個設(shè)備對象是由驅(qū)動棧中位于下方的驅(qū)動程序創(chuàng)建,則該設(shè)備叫物理設(shè)備對象(PDO)。如果一個設(shè)備對象是由位于上方的驅(qū)動程序所創(chuàng)建,則叫過濾驅(qū)動對象(FIDO)。
一個設(shè)備對象對應(yīng)于在AddDevice例程中調(diào)用I/O管理器中名為IoCreateDevice的例程所創(chuàng)建的設(shè)備。對IoCreateDevice來說,最重要的是設(shè)備對象的名稱和設(shè)備類型。這個名稱使得應(yīng)用程序和其他的內(nèi)核驅(qū)動可獲取到該驅(qū)動的句柄,從而可進行I/O操作。設(shè)備類型指定了驅(qū)動程序管理的設(shè)備的類型,如存儲設(shè)備。
當一個設(shè)備對象被創(chuàng)建時,可以將一個內(nèi)存塊與之關(guān)聯(lián),該內(nèi)存塊叫DeviceExtension,也即在Windows中驅(qū)動程序數(shù)據(jù)保存的地方。這是一個挺重要的東東,它使得在驅(qū)動程序代碼中使用難于維護的全局數(shù)據(jù)結(jié)構(gòu)變得沒有必要。例如,要是錯誤的聲明了一個和全局變量具有相同名稱的局部變量,驅(qū)動程序編寫者會發(fā)現(xiàn)難以跟蹤這樣的bug。這也使得維護特定于設(shè)備對象的數(shù)據(jù)變得簡單,尤其是當多個設(shè)備對象存在于一個驅(qū)動程序中的時候,比如總線驅(qū)動程序在管理總線上出現(xiàn)的多個設(shè)備的物理設(shè)備對象時。
設(shè)備的名稱可在設(shè)備對象被創(chuàng)建的時候賦予,這個名稱可以用來訪問驅(qū)動的句柄,句柄又被用來進行I/O操作。Microsoft建議不要給在過濾驅(qū)動和功能驅(qū)動中創(chuàng)建的功能設(shè)備對象命名。Oney [Oney, 99]指出,若一個設(shè)備對象具有名稱,則任意用戶都能打開設(shè)備對象并對其進行I/O操作,即使是對非磁盤設(shè)備驅(qū)動。這是因為Windows默認就給了非磁盤設(shè)備對象毫無限制的訪問狀態(tài)。另外一個問題是這些名稱不需要遵循任何命名規(guī)范,指定的名稱往往不是經(jīng)過挑選的。例如兩個驅(qū)動程序開發(fā)者可能給他們的設(shè)備對象賦予相同的名稱,這樣就會引起沖突。Windows還支持另外一種設(shè)備對象命名方式,即設(shè)備接口。設(shè)備接口是由128比特位構(gòu)成的全局唯一標識符[Open Group, 97]。GUID可用Microsoft DDK中提供的工具生成,生成之后可對外發(fā)布。驅(qū)動程序通過在AddDevice例程中調(diào)用I/O管理器的名為IoRegisterDeviceInterface的例程注冊設(shè)備接口。一旦注冊,驅(qū)動程序必須調(diào)用I/O管理器的IoSetDeviceInterfaceState例程來使能設(shè)備接口。注冊過程中一個接口數(shù)據(jù)入口被添加到Windows注冊表中,應(yīng)用程序接著就可以訪問到。
應(yīng)用程序欲對設(shè)備驅(qū)動執(zhí)行I/O操作前,必須先通過調(diào)Win32 API CreateFile獲取到設(shè)備驅(qū)動的一個句柄,這個API需要設(shè)備的路徑作為參數(shù),如\device\devicex。具有名稱的設(shè)備將出現(xiàn)在命名空間“\\device”中,因此先前的路徑表示設(shè)備devicex。CreateFile同時需要指定對設(shè)備的訪問標志,如讀、寫和共享方式。對注冊了設(shè)備接口而沒有名稱的設(shè)備的訪問則不同于圖3.1.2.4展示的例程,它需要使用驅(qū)動程序的GUID,調(diào)用Win32 API SetupDiGetClassDevs獲取一個指向設(shè)備信息結(jié)構(gòu)的句柄。這種方式只適用于驅(qū)動程序已經(jīng)注冊了設(shè)備接口、應(yīng)用程序需要訪問設(shè)備(叫設(shè)備接口類)的情況。每次驅(qū)動程序調(diào)用I/O管理器例程IoRegisterDeviceInterface時,一個新的設(shè)備接口類的實例就被創(chuàng)建。一旦應(yīng)用程序獲取到了設(shè)備信息句柄,對Win32 API SetupDiEnumDeviceInterfaces的多個調(diào)用將會為每個設(shè)備接口類實例返回設(shè)備接口數(shù)據(jù)。最后,通過調(diào)用Win32 API SetupGetDeviceInterfaceDetail,并根據(jù)之前返回的接口數(shù)據(jù),可為每個設(shè)備接口類實例獲取到一個設(shè)備路徑。接著,對感興趣的設(shè)備,使用設(shè)備路徑為參數(shù)調(diào)用CreateFile來獲取句柄以便執(zhí)行I/O操作。
Figure 3.1.2.4 Obtaining a handle an application can use for I/O from a device GUID.
當PnP管理器調(diào)用AddDevice例程時,它其中的一個參數(shù)是來自下層驅(qū)動的一個設(shè)備對象(PDO)。設(shè)備對象在AddDevice例程中完成堆疊,因為發(fā)往下層驅(qū)動的IRP可被當前加載的驅(qū)動獲取到。如圖 3.1.2.5所示,設(shè)備堆疊是通過調(diào)用I/O管理器例程IoAttachDeviceToDeviceStack 來完成。在調(diào)用IoAttachDeviceToDeviceStack時,需要一個位于棧中新創(chuàng)建設(shè)備對象下方的物理設(shè)備對象。這個例程將新創(chuàng)建的設(shè)備附加到設(shè)備棧的頂層,并將當前位于其下方的設(shè)備對象返回,圖 3.1.2.5中下方設(shè)備為設(shè)備對象X。下層的物理設(shè)備可位于新設(shè)備下方的任何位置,而IoAttachDeviceToStack 返回的是緊鄰著當前設(shè)備的下層設(shè)備。
Figure 3.1.2.5 Attaching a device object to the top of a device object stack.
從內(nèi)核空間到用戶空間以及從用戶空間到內(nèi)核空間傳送數(shù)據(jù)的模式是在設(shè)備對象的flag域中設(shè)置。共有三種模式,分別為buffer I/O,direct I/O和neither I/O。圖3.1.2.6 闡述了這三種模式。在buffer I/O模式中,操作系統(tǒng)分配了一個內(nèi)核緩沖區(qū)來處理請求。在寫操作中,操作系統(tǒng)首先驗證用戶空間提供的緩沖區(qū),然后從用戶空間將數(shù)據(jù)拷貝到新分配的內(nèi)核緩沖區(qū),接著講內(nèi)核緩沖區(qū)傳送給驅(qū)動程序。讀操作時,操作系統(tǒng)驗證用戶緩沖區(qū)然后將數(shù)據(jù)從新分配的內(nèi)核緩沖區(qū)中拷貝到用戶緩沖區(qū)。驅(qū)動程序可通過IRP的AssociatedIrp.SystemBuffer域訪問到內(nèi)核緩沖區(qū)。當使用buffer I/O模式時,驅(qū)動程序通過讀取或?qū)懭雰?nèi)核緩沖區(qū)來實現(xiàn)與應(yīng)用層的通信。
Direct I/O是用于應(yīng)用層和驅(qū)動程序交換數(shù)據(jù)的第二種模式。應(yīng)用層提供的緩沖區(qū)被操作系統(tǒng)在內(nèi)存中鎖定,這樣它就不會被交換出去,并將被鎖定內(nèi)存的內(nèi)存描述列表(Memory Description List,MDL)傳送給驅(qū)動程序。內(nèi)存描述列表是一個不透明的結(jié)構(gòu)體,它的實現(xiàn)對驅(qū)動程序是不可見的。驅(qū)動程序之后通過MDL對用戶空間緩沖區(qū)進行DMA操作。驅(qū)動程序通過IRP的MdlAddress域訪問MDL。使用direct I/O的好處是它比buffer I/O速度要快,因為不需要在用戶層和內(nèi)核層之間拷貝任何數(shù)據(jù),而是直接對用戶緩沖區(qū)進行I/O操作。
第三種I/O模式既不使用buffer也不使用MDLs,操作系統(tǒng)直接將用戶空間緩沖區(qū)的虛擬地址傳送給驅(qū)動程序。驅(qū)動程序在使用之前負責(zé)檢查緩沖區(qū)的有效性。此外,只有在當前線程上下文環(huán)境和應(yīng)用程序的上下文環(huán)境一致時,用戶空間緩沖區(qū)才能被訪問,否則會出現(xiàn)頁錯誤,因為虛擬地址只有在應(yīng)用程序所對應(yīng)的進程處于激活狀態(tài)時才有效。
Figure 3.1.2.6 The three ways in which data from kernel to user and user to kernel
space is exchanged.
分發(fā)例程用來處理接收到的I/O請求包,即IRPs(I/O request packets)。當一個IRP到來(如當一個應(yīng)用程序發(fā)起I/O操作)時,一個適當?shù)睦瘫粡尿?qū)動對象的MajorFunction域中指定的例程數(shù)組中選出來,如圖3.1.3。這些例程在驅(qū)動程序的入口函數(shù)中被初始化。每個IRP在創(chuàng)建時就與一個I/O stack location結(jié)構(gòu)體(用于存儲IRP的參數(shù))關(guān)聯(lián)。這個結(jié)構(gòu)體有一個域,指定了IRP需要執(zhí)行的分發(fā)例程和分發(fā)例程需要的相關(guān)參數(shù)。I/O管理器根據(jù)IRP決定將IRP發(fā)往哪個分發(fā)例程。
Figure 3.1.3 dispatching IRP’s to dispatch routines.
因此,IRPs被路由到適當?shù)尿?qū)動例程進而得到處理。分發(fā)例程ID如表3.1.3所示,它們作為例程數(shù)組的索引,這個數(shù)組是在驅(qū)動對象的MajorFunction域中指定。分發(fā)例程的名稱是驅(qū)動程序所實現(xiàn)的例程的名稱,這些例程都將一個IRP和該IRP被發(fā)送到的設(shè)備對象作為參數(shù)。
Table 3.1.3 Required Windows driver dispatch routines
Windows根據(jù)一個INF文件中的安裝信息來安裝驅(qū)動。驅(qū)動程序的編寫者負責(zé)為驅(qū)動程序提供一個INF文件。Windows DDK提供一個叫GenInf的GUI應(yīng)用程序,來為驅(qū)動程序生成INF文件。這個工具需要提供一個公司名稱和一個Windows設(shè)備類,驅(qū)動程序?qū)⒈话惭b到該設(shè)備類下。Windows為驅(qū)動程序預(yù)定義了各種不同的設(shè)備類。從系統(tǒng)控制面板進入到設(shè)備管理器面板,可看到顯示的所有按設(shè)備類分類的已安裝驅(qū)動程序。已有的設(shè)備類如1394和PCMCIA設(shè)備類??稍?/span>INF文件中添加一個ClassInstall32節(jié)來添加一個自定義的設(shè)備類。對PnP感知的設(shè)備,還需要在INF文件中指定一個硬件ID,在該設(shè)備被添加到系統(tǒng)中時,系統(tǒng)將用該ID來標識設(shè)備。硬件ID是一個標識字符串,PnP管理器在設(shè)備添加到系統(tǒng)中用硬件ID來標識設(shè)備。Microsoft為Windows系統(tǒng)會用到的各種設(shè)備發(fā)布了硬件ID。硬件ID保存在硬件設(shè)備中,操作系統(tǒng)在設(shè)備添加到系統(tǒng)中時從設(shè)備讀取。一旦新設(shè)備的INF文件成功安裝到系統(tǒng)中,每當具有指定硬件ID的設(shè)備被添加到系統(tǒng)中,為該設(shè)備編寫的驅(qū)動程序都被加載,并在設(shè)備移除時被卸載。
系統(tǒng)控制面板上的設(shè)備管理器給用戶提供驅(qū)動的相關(guān)信息。它列出了所有當前已加載的驅(qū)動,每個驅(qū)動提供者的相關(guān)信息和驅(qū)動資源使用情況。同時還顯示驅(qū)動無法加載時的失敗信息以及錯誤碼。
Linux下的設(shè)備驅(qū)動和Windows設(shè)備驅(qū)動的相似之處在于它們都是由一些執(zhí)行I/O及控制操作的例程組成。驅(qū)動程序沒有對應(yīng)的驅(qū)動對象,而是由內(nèi)核直接管理。
Linux下的美國各驅(qū)動程序包含一個驅(qū)動注冊例程和反注冊例程。驅(qū)動注冊例程類似于Windows的驅(qū)動入口例程。驅(qū)動程序編寫者使用內(nèi)核定義的兩個宏module_init和module_exit來指定自定義的例程作為注冊和反注冊例程。
3.2.1.1. 驅(qū)動注冊和反注冊
module_init聲明的注冊例程是驅(qū)動程序被加載時第一個執(zhí)行的例程。在這個例程中,用一個內(nèi)核字符設(shè)備注冊例程register_chrdev注冊驅(qū)動。這個例程需要一個驅(qū)動名稱、主驅(qū)動編號(將在3.2.2節(jié)中討論)和一系列執(zhí)行文件操作的例程。其它特定于驅(qū)動的初始化也必須在這個例程中完成。反注冊函數(shù)在驅(qū)動程序被卸載時執(zhí)行,它的主要功能是做一些清除操作。反注冊之前使用register_chrdev注冊的驅(qū)動程旭時會調(diào)用內(nèi)核例程unregister_chrdev,并需以設(shè)備名和主編號為參數(shù)。
Linux下,設(shè)備命名使用0到255的數(shù)字,叫主設(shè)備編號。這意味著最多只能有256個可用的設(shè)備,也即應(yīng)用程序可獲取到句柄的設(shè)備。但這樣一個主設(shè)備的每個驅(qū)動程序可以管理額外的256個設(shè)備。這些驅(qū)動程序管理的設(shè)備也使用0到255的數(shù)字標識,叫次設(shè)備編號。因此,應(yīng)用程序可訪問多達65535(256*256)個設(shè)備。主設(shè)備編號賦給一些熟知的設(shè)備,如IEEE1394的編號為171。Linux內(nèi)核源碼樹中的文件Documentation/devices.txt包含了所有主設(shè)備編號的分配情況和編號注冊中心的聯(lián)系地址。當前,主設(shè)備編號240-254為實驗所用。一個驅(qū)動程序通過指定0作為主設(shè)備編號來請求一個自動分配的主編號(若當前還有可用的主設(shè)備編號的話)。這種指定0為主設(shè)備編號的方式并不會有什么問題,因為它是為null設(shè)備預(yù)留的,而沒有一個新的驅(qū)動程序會將自己注冊為null設(shè)備驅(qū)動。
應(yīng)用程序通過文件系統(tǒng)入口(nodes)訪問驅(qū)動。按照慣例,驅(qū)動程序目錄為/dev。需要對驅(qū)動執(zhí)行I/O操作的應(yīng)用程序使用open系統(tǒng)調(diào)用來獲取某個特定驅(qū)動的句柄。Open系統(tǒng)調(diào)用需要一個設(shè)備節(jié)點名稱如/dev/tty和訪問標識(flags)。獲取句柄之后,應(yīng)用程序使用該句柄來調(diào)用其他的I/O系統(tǒng)調(diào)用如read、write和IOCTL。
Windows下,分發(fā)例程是在驅(qū)動入口例程中設(shè)置。Linux下,這些分發(fā)例程就是所謂的文件操作并使用結(jié)構(gòu)體file_operations來表示。一個典型的驅(qū)動程序會實現(xiàn)如表3.2.3列出的文件操作例程。
Table 3.2.3 Most commonly defined driver file operations in Linux
這些文件操作在驅(qū)動程序注冊時指定。每當應(yīng)用程序請求一個設(shè)備句柄時,內(nèi)核會創(chuàng)建一個叫file的結(jié)構(gòu)體,并在某個驅(qū)動例程被調(diào)用時將其傳遞給驅(qū)動程序。文件操作例程被多個用戶調(diào)用,每個都對應(yīng)一個file結(jié)構(gòu)體。File結(jié)構(gòu)體有一個f_op域,這個域是一個指針,指向驅(qū)動注冊時指定的文件操作例程集。因此,在調(diào)用任何一個文件操作例程時,都可以通過改變f_op域的值來指向新的文件操作例程集。
每當應(yīng)用程序?qū)?span>/dev下的設(shè)備文件節(jié)點發(fā)起一個open系統(tǒng)調(diào)用時,應(yīng)用程序從操作系統(tǒng)獲得設(shè)備的一個句柄。這個時候驅(qū)動程序的open函數(shù)被調(diào)用,并給它傳遞為open系統(tǒng)調(diào)用所創(chuàng)建的file結(jié)構(gòu)體。任何一個文件操作例程執(zhí)行時,內(nèi)核都將file結(jié)構(gòu)體傳遞給驅(qū)動程序。File結(jié)構(gòu)體的private_data域可以是驅(qū)動程序指定的任意自定義結(jié)構(gòu)體。驅(qū)動程序的私有數(shù)據(jù)通常在文件open操作函數(shù)中被設(shè)置,即為它分配內(nèi)存,之后在文件的release操作函數(shù)中釋放該內(nèi)存。File結(jié)構(gòu)體的私有數(shù)據(jù)域可用來指向驅(qū)動程序的全局數(shù)據(jù),避免了使用全局變量。
3.2.4.1. 問題
Linux下只有一個驅(qū)動程序可通過注冊一個特定的主編號來管理一個設(shè)備,也就是說,驅(qū)動程序的注冊只能使用一個主編號。舉個例子,存在兩個設(shè)備節(jié)點/dev/device1(主編號4次編號1)和設(shè)備/dev/device2(主編號4次編號2),只有一個驅(qū)動程序能夠處理應(yīng)用程序?qū)蓚€節(jié)點的請求。這種限制的存在是因為Linux沒有提供一種注冊機制使得驅(qū)動程序能自注冊一個主編號和一個次編號以便能管理一個設(shè)備。
3.2.4.2.解決辦法
· 加載一個驅(qū)動程序來管理主編號為4的設(shè)備。這個驅(qū)動程序?qū)⒆约涸趦?nèi)核中注冊(節(jié)3.2.1.1會看到這是如何完成的)。
· 分別加載兩個驅(qū)動,一個管理主編號4次編號1的設(shè)備,另一個管理主編號4次編號2的設(shè)備。這兩個驅(qū)動程序沒有在內(nèi)核中注冊,而是向管理主設(shè)備編號4的另外一個驅(qū)動程序注冊。這個驅(qū)動程序負責(zé)實現(xiàn)注冊機制并跟蹤管理向它注冊的所有驅(qū)動程序。
· 應(yīng)用程序打開任意一個設(shè)備節(jié)點(/dev/device1或/dev/device2)時,注冊為管理主設(shè)備4的驅(qū)動程序的open例程將被內(nèi)核調(diào)用。一個用來表示被打開設(shè)備的file結(jié)構(gòu)體作為參數(shù)傳遞給這個open例程。
· 這時候,管理設(shè)備主編號4的驅(qū)動程序修改文件操作函數(shù)指針(file結(jié)構(gòu)體的f_op成員)來指向管理被打開設(shè)備的驅(qū)動程序所實現(xiàn)的I/O例程。應(yīng)用程序打開次設(shè)備時,管理主編號4的驅(qū)動程序以下列方式區(qū)分:
o 一個叫inode的結(jié)構(gòu)體被傳遞給open例程。這個結(jié)構(gòu)體包含一個叫i_rdev的域,該域指定了open操作的目標設(shè)備對應(yīng)的主編號和次編號。內(nèi)核的MINOR和MAJOR宏可用來從i_rdev域提取住次編號。這個例子中,主編號為4,次編號為1或2。管理主編號4的驅(qū)動程序就可以通過這個信息從它的注冊數(shù)據(jù)庫中定位到次設(shè)備驅(qū)動程序。
Linux下,用戶到內(nèi)核和內(nèi)核到用戶空間的數(shù)據(jù)交換方式有三種,分別是buffer I/O,direct I/O和mmap。在buffer I/O模式中,內(nèi)核將數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間供驅(qū)動程序使用。和Windows不同,Linux沒有自動對I/O進行緩沖,而是提供訪問用戶和內(nèi)核空間的例程,驅(qū)動程序使用這些例程來完成用戶和內(nèi)核空間之間數(shù)據(jù)的拷貝。Direct I/O模式中,驅(qū)動程序可對用戶空間緩沖區(qū)直接讀和寫。這是通過kiobuf接口完成,它將用戶空間緩沖區(qū)映射到調(diào)用系統(tǒng)調(diào)用kiobuf時定義的結(jié)構(gòu)體。這個操作會鎖定用戶空間緩沖區(qū),這樣該空間不會被換出以便滿足設(shè)備的I/O操作。第三種方式是mmap,它是由驅(qū)動程序使用mmap內(nèi)核調(diào)用將內(nèi)核的空間塊映射到用戶空間,應(yīng)用程序因而可以對映射的內(nèi)核內(nèi)存進行I/O操作[Rubini et al, 01].。
Linux下驅(qū)動程序的安裝是將驅(qū)動文件放置到特定的系統(tǒng)目錄下。在RedHat發(fā)行版中[Redhat, 02],模塊位于目錄/lib/modules/kernel_version,kernel_version指定當前內(nèi)核版本,如2.4.19。一個叫modules.conf的配置文件位于系統(tǒng)配置文件目錄如/etc中,這個文件在加載模塊時被內(nèi)核使用。通過修改該文件,可對某個驅(qū)動程序放置位置進行覆蓋。還可以定義其它一些模塊加載選項,如驅(qū)動被加載時給它傳遞的參數(shù)。模塊加載和卸載使用系統(tǒng)自帶的內(nèi)核模塊工具包,叫insmod,modprobe和rmmod。Insmod和modprobe將驅(qū)動程序二進制鏡像加載到內(nèi)核,rmmod則移除模塊。另一個叫lsmod的程序列出當前所有已加載的模塊。Insmod嘗試加載一個模塊,若該模塊依賴于其他模塊,則返回一個錯誤碼。Modprobe則嘗試著滿足模塊依賴關(guān)系,它試圖將當前模塊所依賴的其它模塊先進行加載。模塊依賴關(guān)系信息可從一個叫modules.dep的文件獲取,該文件位于系統(tǒng)模塊目錄(system’s modules directory)中。在驅(qū)動程序可被應(yīng)用程序訪問前,這個驅(qū)動的一個附有主次設(shè)備編號的設(shè)備節(jié)點(見2.1和3.2.2.1節(jié),Linux如何在系統(tǒng)中表示設(shè)備)首先要在設(shè)備目錄/dev中被創(chuàng)建。系統(tǒng)程序mknod就是為這個目的準備的。在創(chuàng)建一個設(shè)備節(jié)點時,指定節(jié)點為字符設(shè)備還是塊設(shè)備是有必要的。
我們時常需要獲取系統(tǒng)已加載驅(qū)動的狀態(tài)信息。Linux下,proc文件系統(tǒng)是用來將內(nèi)核信息向應(yīng)用程序發(fā)布。Proc文件系統(tǒng)和其他的文件一樣,它也包含目錄和文件節(jié)點供應(yīng)用程序訪問和執(zhí)行I/O操作。Proc文件系統(tǒng)中的文件和普通文件的區(qū)別在于,對proc文件執(zhí)行I/O操作的數(shù)據(jù)是被傳遞到內(nèi)核內(nèi)存而不是磁盤存儲。Proc文件系統(tǒng)是應(yīng)用程序和內(nèi)核組件之間的通信媒介。例如,讀取/proc/modules將返回當前所有已加載模塊和它們的依賴關(guān)系。在獲取驅(qū)動狀態(tài)信息和發(fā)布驅(qū)動程序數(shù)據(jù)到應(yīng)用程序時,proc文件系統(tǒng)就尤為有用。
Windows和Linux的驅(qū)動程序都是由一系列執(zhí)行I/O操作的例程組成的可動態(tài)加載的模塊。當加載一個模塊時,內(nèi)核將定位到被系統(tǒng)標記為驅(qū)動程序入口的例程作為驅(qū)動代碼執(zhí)行的起點。
兩個系統(tǒng)中驅(qū)動程序都具有初始化和反初始化例程。在Linux中,這兩個例程的名稱可自定義,在Windows中,初始化例程的名稱固定(叫DriverEntry)但反初始化例程可自定義。Windows為每個驅(qū)動程序維護一個驅(qū)動對象,驅(qū)動程序的多個實例用多個驅(qū)動對象表示。Linux下,內(nèi)核為每個管理一個設(shè)備主編號的驅(qū)動維護信息,即每個主設(shè)備驅(qū)動。兩個操作系統(tǒng)都要求驅(qū)動程序?qū)崿F(xiàn)標準的I/O例程,Windows中叫分發(fā)例程,Linux下叫文件操作。Linux下,可為每個應(yīng)用程序獲取到的設(shè)備句柄設(shè)置一個不同的文件操作例程集。Windows下,分發(fā)例程在驅(qū)動對象的一部分,并且是一次性地在DriverEntry例程中定義。由于每個被加載的驅(qū)動都有一個驅(qū)動對象,因此不建議在應(yīng)用程序使用系統(tǒng)調(diào)用請求一個句柄時修改驅(qū)動對象的分發(fā)例程。Windows有個叫AddDevice的例程,在PnP感知的設(shè)備添加到系統(tǒng)時被PnP管理器調(diào)用。Linux沒有PnP管理器,也就不存在這樣一個例程。
Windows的分發(fā)例程對設(shè)備對象和IRPs進行操作,Linux下,文件操作針對file結(jié)構(gòu)體。自定義的驅(qū)動全局數(shù)據(jù)保存在Windows的設(shè)備對象中,而Linux下則保存在file結(jié)構(gòu)體中。Windows下,設(shè)備對象在驅(qū)動加載時被創(chuàng)建,Linux下,file結(jié)構(gòu)體是應(yīng)用程序通過系統(tǒng)調(diào)用open向驅(qū)動請求句柄時被創(chuàng)建。這就意味著Linux下每個應(yīng)用程序的全局數(shù)據(jù)可保存在file操作結(jié)構(gòu)體中。Windows下,全局數(shù)據(jù)只能出現(xiàn)在驅(qū)動管理的功能設(shè)備對象(FDO)中。Windows下每個應(yīng)用程序的全局數(shù)據(jù)必須保存在功能設(shè)備對象(FDO)自定義結(jié)構(gòu)體的列表結(jié)構(gòu)中。
Windows下的驅(qū)動使用驅(qū)動自定義的字符串命名并顯示在\\device命名空間下。Linux下,驅(qū)動被賦予文本形式的名稱,但應(yīng)用程序并不需要知道這些名稱,驅(qū)動是通過主-次編號對來標識。主-次編號的范圍是0-255,因為是用16比特位來表示主-次編號對,所以最大允許65535個設(shè)備安裝到系統(tǒng)中。Linux下的設(shè)備通過文件系統(tǒng)節(jié)點供應(yīng)用程序訪問。在大部分的Linux發(fā)行版中,目錄/dev包含設(shè)備文件系統(tǒng)節(jié)點。每個節(jié)點創(chuàng)建時帶有驅(qū)動的主編號和次編號。應(yīng)用程序獲得驅(qū)動的一個句柄,用來對系統(tǒng)調(diào)用open的目標設(shè)備節(jié)點進行I/O操作。Windows還有另一種驅(qū)動命名方式,是給每個驅(qū)動注冊的128位GUID。應(yīng)用程序訪問注冊表,通過GUID獲得\\device命名空間下的文本形式的名稱。這個名稱通過使用Win32 API CreateFile來獲取驅(qū)動的一個句柄以便進行I/O操作。
用戶-內(nèi)核空間數(shù)據(jù)交換
兩種操作系統(tǒng)中,數(shù)據(jù)來自或去往用戶空間的方式是類似的,都允許緩沖區(qū)數(shù)據(jù)傳送,在Windows下是有I/O管理器執(zhí)行,Linux下則由驅(qū)動執(zhí)行。兩種操作系統(tǒng)都可以進行direct I/O到用戶空間緩沖區(qū),通過鎖定用戶空間緩沖區(qū)以使得該緩沖區(qū)一直存在于物理內(nèi)存中。這個起因是驅(qū)動程序并不總能直接訪問用戶空間緩沖區(qū),因為它不能保證一直運行在和擁有該用戶空間緩沖區(qū)的應(yīng)用程序一致的進程上下文中。應(yīng)用程序有它自己的虛擬地址空間,該地址空間只在它自己的進程上下文中有效。因此,當驅(qū)動程序訪問某些應(yīng)用程序的一個虛擬地址但不在該應(yīng)用程序的進程上下文中時,就會訪問了無效的地址。
驅(qū)動安裝和管理
Windows驅(qū)動的安裝是通過一個叫INF文件的文本文件。一旦安裝之后,一個設(shè)備的驅(qū)動程序在設(shè)備出現(xiàn)在系統(tǒng)中時會自動被PnP管理器加載。Linux系統(tǒng)中,使用程序工具來加載驅(qū)動二進制鏡像到內(nèi)核。需要手動將一些條目添加到系統(tǒng)啟動文件中,這樣驅(qū)動加載程序如modprobe就以驅(qū)動程序鏡像路徑或驅(qū)動程序的別名為參數(shù)執(zhí)行。驅(qū)動程序的別名在文件/etc/modules.conf中定義,modprobe等類似程序在加載驅(qū)動之前會查看該文件。Modules.conf中一個定義別名的條目的例子可類似于“alias sounddriver testdriver”,這是將sounddriver作為testdriver驅(qū)動二進制鏡像的別名。這樣一來,用戶可通過使用標準的更簡單的名稱如sounddriver來加載音頻驅(qū)動程序而不需要知道音頻卡的某個特定驅(qū)動程序的名稱。Windows下驅(qū)動程序的狀態(tài)信息可在設(shè)備管理面板中看到,也可以直接從系統(tǒng)注冊表中讀取相關(guān)數(shù)據(jù)。Linux下,驅(qū)動信息可通過proc文件系統(tǒng)節(jié)點獲取,如文件/proc/module包含了已加載模塊的一個列表。
一個內(nèi)核緩沖驅(qū)動
這一節(jié)展示一個執(zhí)行I/O操作到內(nèi)核內(nèi)存塊(虛擬磁盤)的簡單驅(qū)動程序的實現(xiàn)。我們將討論為使驅(qū)動程序能夠同時在Windows和Linux下工作所需要的各種組件,這樣它們所需要驅(qū)動組件的相似和不同之處也得到了突顯。驅(qū)動程序所管理的虛擬設(shè)備如圖4.0.所示,它由若干內(nèi)核內(nèi)存塊組成。應(yīng)用程序可對虛擬設(shè)備進行I/O操作。驅(qū)動程序可以選擇某個內(nèi)存塊和內(nèi)存塊的偏移位置進行訪問。
Figure 4.0 A simple virtual device
需要的驅(qū)動組件
Windows和Linux驅(qū)動程序都將實現(xiàn)read,write和IOCTL驅(qū)動例程。每個操作系統(tǒng)所需要的例程如圖4.1.所示。驅(qū)動程序的名稱可隨意指定。圖4.1種不同操作系統(tǒng)的例程也可以被賦以相同的名稱,這里只是根據(jù)平臺的慣用法來命名。
Figure 4.1 The Windows and Linux basic driver routines
驅(qū)動加載和卸載例程
Windows下,驅(qū)動加載例程DriverEntry中所執(zhí)行的步驟是設(shè)置I/O分發(fā)例程,如圖4.1.1a所示。
Figure 4.1.1a Initialisation of a driver Object in the driver entry routine
Linux下,驅(qū)動加載例程RegisterDriver中所進行的是驅(qū)動主編號的注冊,如圖4.1.1b。Tagged文件操作的初始化,只針對GCC編譯器,如圖4.1.1b,是在對結(jié)構(gòu)體fops的聲明中,當然這不是ANSI C的有效語法。編譯器將使用驅(qū)動程序?qū)崿F(xiàn)的例程名稱初始化file_operation結(jié)構(gòu)體(fops)中的各個不同的域。如open是結(jié)構(gòu)體一個域的名稱而Open是驅(qū)動實現(xiàn)的一個例程,編譯器將Open函數(shù)指針賦給open域。
Figure 4.1.1b Registration of a driver major number in Linux
Linux驅(qū)動的卸載程序中,已注冊驅(qū)動必須進行反注冊,如圖4.1.1c。
Figure 4.1.1c Driver major number deregistration in Linux
驅(qū)動全局結(jié)構(gòu)
必須定義一個結(jié)構(gòu)體來保存驅(qū)動全局數(shù)據(jù),這些數(shù)據(jù)在驅(qū)動的各例程中被使用。對這個內(nèi)存設(shè)備,同樣的結(jié)構(gòu)使用在Windows和Linux驅(qū)動程序中,其定義如圖4.1.2。
Figure 4.1.2 Structure used to store global data for generic driver
memoryBank是包含4個內(nèi)存塊,每個塊為1K大小的數(shù)組。currentBank表示當前選中的內(nèi)存塊,offsets記錄了每個內(nèi)存塊內(nèi)部的偏移量。
添加設(shè)備例程
添加設(shè)備例程只針對Windows。Linux沒有添加設(shè)備例程,所有的初始化必須在驅(qū)動加載例程里完成。Windows下addDevice例程所執(zhí)行的操作如圖4.1.3所示。在調(diào)用I/O管理器例程IoCreateDevice例程時,一個設(shè)備對象被創(chuàng)建。供應(yīng)用程序使用來獲取驅(qū)動句柄的接口也被創(chuàng)建,這通過調(diào)用I/O管理器例程IoRegisterDeviceInterface。這個例程的一個參數(shù)是使用系統(tǒng)工具guidgen手動生成的GUID。Windows下,驅(qū)動和應(yīng)用程序之間的不同數(shù)據(jù)交換方式在3.1.2.6節(jié)中有說明。驅(qū)動程序通過設(shè)置設(shè)備對象的flags域(見3.1和3.2關(guān)于設(shè)備對象的討論)來表明其要使用的數(shù)據(jù)交換方法。這里例子中flags被設(shè)置成使驅(qū)動使用buffered I/O方式。內(nèi)存設(shè)備使用的每個內(nèi)存塊通過其中一個叫ExAllocatePool的內(nèi)核內(nèi)存分配例程來分配。這個內(nèi)存從內(nèi)核的非頁內(nèi)存池中分配,這樣設(shè)備的內(nèi)存總是存在于物理內(nèi)存中。
Figure 4.1.3 Operations performed in the Windows driver’s add device routine
打開和關(guān)閉例程
Windows驅(qū)動的大部分初始化操作都已經(jīng)在添加設(shè)備例程中完成,因此不需要在打開例程中做任何初始化。Linux下的打開例程如圖4.1.4a所示。首先,用于保存驅(qū)動全局數(shù)據(jù)的內(nèi)存被分配,然后將文件結(jié)構(gòu)體的private_data域指向該內(nèi)存。之后內(nèi)存設(shè)備所使用的內(nèi)存塊通過和Windows下完全一樣的方式分配,只是內(nèi)存分配函數(shù)的名字不同,Windows下是ExAllocatePool而Linux下是kmalloc。
Figure 4.1.4a Operations performed in Linux’s generic driver open routine
在Linux的關(guān)閉例程中,為驅(qū)動全局數(shù)據(jù)和內(nèi)存設(shè)備分配的內(nèi)存被釋放,如圖4.1.4b。Windows下,內(nèi)存的釋放是在響應(yīng)PnP移除消息的時候,這個在本節(jié)后面會有討論。
Figure 4.1.4b Operations performed in Linux’s generic driver close routine
讀和寫例程
Read和write例程將數(shù)據(jù)傳送到或取自當前選中的內(nèi)核內(nèi)存塊。Windows下,讀例程的執(zhí)行如圖4.1.5a所示。要讀取數(shù)據(jù)的長度值從IRP的I/O棧位置(見3.1.3節(jié)什么是I/O stack location)獲取,該域名稱為Parameters.Read.Length。所請求長度的數(shù)據(jù)將被從當前選中的內(nèi)存塊(后面會討論應(yīng)用程序通過驅(qū)動IOCTL例程選擇內(nèi)存塊)中讀取,使用的是內(nèi)核運行時例程RtlMoveMemory。RtlMoveMemory將數(shù)據(jù)從內(nèi)存設(shè)備的內(nèi)存空間搬移到I/O管理器為buffered I/O分配的緩沖區(qū),也就是IRP的AssociatedIrp.SystemBuffer域。這個IRP算完成了,就通知I/O管理器驅(qū)動程序已完成IRP的處理,I/O管理器將IRP返回給其發(fā)起者。
Figure 4.1.5a Performing a read operation in the Windows driver
寫例程對上述內(nèi)存搬移進行反操作,如圖4.1.5b。
Figure 4.1.5b Performing a write operation in the Windows driver
Linux下,讀例程如圖4.1.5c所示。對驅(qū)動全局數(shù)據(jù)的引用從文件結(jié)構(gòu)體的private_data域獲取,從全局數(shù)據(jù)中,又獲取到對memoryBank的引用。接著數(shù)據(jù)就從這個內(nèi)存區(qū)被傳送到用戶空間,使用內(nèi)核訪問用戶空間例程copy_to_user。
Figure 4.1.5c Performing a read operation in the Linux driver
寫例程執(zhí)行和上面同樣的操作,只是這一次數(shù)據(jù)是從用戶空間傳送到內(nèi)核空間,如圖4.1.5d。
Figure 4.1.5d Performing a write operation in the Linux driver
設(shè)備控制例程
設(shè)備控制例程用來設(shè)置設(shè)備的各種狀態(tài)。應(yīng)用程序使用win32例程DeviceIoControl來對驅(qū)動程序進行IOCTL調(diào)用。這個例程需要一個由驅(qū)動程序定義的IOCTL碼。一個IOCTL碼告訴驅(qū)動程序應(yīng)用程序要執(zhí)行的操作。在這個例子中,驅(qū)動實現(xiàn)IOCTL例程用來選擇當前內(nèi)存塊號(current bank number)。驅(qū)動的IOCTL碼使用之前必須先被定義。Windows下IOCTL碼的定義如圖4.1.6a。CTL_CODE宏用來定義一個設(shè)備的IOCTL碼[Oney, 99]。CTL_CODE的第一個參數(shù)是設(shè)備ID,ID數(shù)值范圍為0-65535,其中0-32767為系統(tǒng)預(yù)留,32768-65535的使用可自定義。所選的IOCTL碼必須和驅(qū)動的addDevice例程中為例程IoCreateDevice指定的設(shè)備編碼一致(參見節(jié)4.1.3addDevice例程所做的事情)。第二個參數(shù)為表示功能碼的12比特位長數(shù)值。0到2047為Microsoft預(yù)留,因此功能碼應(yīng)為大于2047而小于2^12。這個通常用來表示哪個控制碼被定義,即將兩個IOCTL碼區(qū)分開,如圖4.1.6a。第三個參數(shù)值指定用于從用戶空間傳送參數(shù)到內(nèi)核空間的方法,第四個參數(shù)表示應(yīng)用程序?qū)υO(shè)備的訪問權(quán)限。
Figure 4.1.6a IOCTL code definition in Windows
Linux下,應(yīng)用程序使用系統(tǒng)例程ioctl來對驅(qū)動進行IOCTL調(diào)用。IOCTL碼在文件Documentation/ioctl-numbers.txt中指定,可在Linux內(nèi)核源碼樹中找到。用于試驗的驅(qū)動選擇一個未使用的號碼,目前是大于0xFF的值。這個驅(qū)動的IOCTL碼的定義如圖4.1.6b。_IOWR表示數(shù)據(jù)將被傳送到或取自內(nèi)核空間。其它的宏如_IO表示沒有任何參數(shù),_IOW表示數(shù)據(jù)僅將從用戶空間被傳送到內(nèi)核空間,最后的_IOR表示數(shù)據(jù)僅將從內(nèi)核空間被傳送到用戶空間。以上的宏需要一個表示內(nèi)核和用戶空間所需要交換數(shù)據(jù)的大小的值。據(jù)Rubini et al [Rubini et al, 01]建議,為使驅(qū)動程序可移植性更好,這個值應(yīng)被設(shè)置為255(8比特),雖然依賴于當前架構(gòu)的數(shù)值為8-14比特位。第二個參數(shù)和Windows下的函數(shù)編號類似,8位寬,從0-255。
Figure 4.1.6b IOCTL code definition in Windows
一旦IOCTL碼被選定,IOCTL例程就可以被定義。Windows下,IOCTL例程的定義如圖4.1.6c。兩個IOCTL碼被處理。第一個IOCTL_SELECT_BANK,設(shè)置當前內(nèi)存塊號,第二個IOCTL_GET_VERSION_STRING,返回驅(qū)動版本字符串。從IOCTL例程返回的數(shù)據(jù)和read、write請求返回的數(shù)據(jù)一樣被調(diào)用者處理。
Figure 4.1.6c IOCTL routine definition in Windows
Linux下IOCTL例程的定義如圖4.1.6d。對IOCTL碼的處理和Windows一樣,不同的只是語法上。數(shù)據(jù)的處理和read、write請求一樣。
Figure 4.1.6d IOCTL routine definition in Linux
PnP消息處理例程
Windows下,PnP消息在適當?shù)臅r候被分發(fā)給驅(qū)動程序,如當設(shè)備被添加到系統(tǒng)或被從系統(tǒng)移除時。這些消息被驅(qū)動程序?qū)崿F(xiàn)的PnP分發(fā)例程處理。Linux下,內(nèi)核并沒有發(fā)送PnP消息給驅(qū)動程序,因此也就沒有PnP例程。Windows下PnP消息處理例程如圖4.1.7所示。這個例子中,內(nèi)存設(shè)備驅(qū)動只處理其中一個PnP消息。移除設(shè)備的消息是在驅(qū)動程序被系統(tǒng)卸載時被發(fā)送。這個時候,通過調(diào)用I/O管理器例程IoSetDeviceInterface禁用驅(qū)動程序的接口,驅(qū)動程序的功能設(shè)備對象和驅(qū)動程序分配的那些內(nèi)存塊也一并被刪除。
Figure 4.1.7 PnP Message handler routine
驅(qū)動開發(fā)環(huán)境
為到此為止所討論的兩種操作系統(tǒng),即Microsoft Windows和Linux開發(fā)驅(qū)動需要使用特定于每個平臺的一些軟件開發(fā)工具。Windows和Linux操作系統(tǒng)的內(nèi)核都是使用C語言編寫,這使得為兩個系統(tǒng)編寫的驅(qū)動程序也跟著使用C語言編寫。Windows支持使用面向?qū)ο蟮木幊陶Z言C 來編寫驅(qū)動程序,Linux卻沒有支持。
Windows驅(qū)動開發(fā)環(huán)境
Microsoft Windows是一個具有所有權(quán)的商用的操作系統(tǒng),即它需要被購買來使用。針對Windows,存在若干商用的驅(qū)動程序開發(fā)環(huán)境。舉個例子,如NuMega DriverStudio? Suit [Compuware, 01],帶有類庫和驅(qū)動構(gòu)造向?qū)б暂o助驅(qū)動開發(fā),同時還集成了一個調(diào)試器允許驅(qū)動代碼的調(diào)試。
Windows設(shè)備驅(qū)動開發(fā)包
Windows下驅(qū)動開發(fā)的標準途徑是從Microsoft獲取設(shè)備驅(qū)動開發(fā)包(DDK)和利用一些輔助工具進行開發(fā)。最新版的DDK可從MSDN得到(The latest version of the DDK is available to Microsoft Software Development Network (MSDN) subscribers),DDK包含開發(fā)驅(qū)動所需要的程序。DDK安裝程序會安裝一些批處理文件,這些批處理文件會建立一個外殼窗口使得可以為Microsoft每個版本的操作系統(tǒng)開發(fā)驅(qū)動。這次對Windows驅(qū)動架構(gòu)考查所使用的DDK 3590,具有為Windows ME、2000、XP、.NET開發(fā)驅(qū)動的環(huán)境。每個平臺都有兩個版本的開發(fā)環(huán)境,一種叫checked版,調(diào)試符號被添加到驅(qū)動代碼中,另外一種叫發(fā)布版,這個版本的驅(qū)動程序沒有調(diào)試符號。發(fā)布版開發(fā)環(huán)境也是驅(qū)動產(chǎn)品最后使用的編譯環(huán)境。
Windows驅(qū)動的Makefile
DDK外殼窗口打開后,一個簡單的build命令就可以編譯驅(qū)動程序。Makefile定義了用來生成驅(qū)動程序的源代碼文件。Makefile的條目在一個叫sources的文件中指定,放在build命令所處的當前目錄下。圖5.1.2顯示了用來生成一個簡單驅(qū)動程序的Makefile的格式。環(huán)境變量TARGETNAME指定了生成驅(qū)動的名稱。 這個例子中,驅(qū)動程序?qū)⒔衜ydrivers.sys,Windows下的驅(qū)動都以.sys為后綴。TARGETPATH指定驅(qū)動程序賴以生成的目標代碼文件。 目錄obj下有一個文件_objects.mac,定義了額外的目標文件路徑。Windows 2000中,checked版默認的目標文件路徑為objchk_w2k,發(fā)布版默認的目標文件路徑為objfre_w2k。INCLUDES指定了編譯驅(qū)動所需要的包含文件路徑,SOURCES指定驅(qū)動賴以生成的驅(qū)動源碼文件。
Figure 5.1.2 A Makefile used for building a WDM driver with the Windows DDK
Windows DDK文檔和工具
Windows DDK包含組織良好的API文檔和驅(qū)動程序例子代碼。初學(xué)者可從這里學(xué)到如何創(chuàng)建驅(qū)動程序。DDK還包含一些輔助驅(qū)動開發(fā)的實用工具程序。其中一個是設(shè)備樹應(yīng)用程序,列出了當前所有已加載的在\\device命名空間以層級方式列出的驅(qū)動(見3.1.2.4關(guān)于設(shè)備命名空間的討論),顯示每個驅(qū)動棧、驅(qū)動所實現(xiàn)的例程和驅(qū)動對象內(nèi)存地址。Windows DDK提供的其它工具中有一個用來生成INF文件的叫geninf,它生成驅(qū)動程序安裝需要的INF文件,還有一個PnP驅(qū)動測試應(yīng)用程序用來測試驅(qū)動是否支持PnP。
Linux驅(qū)動開發(fā)環(huán)境
Linux驅(qū)動開發(fā)環(huán)境和Windows不同,Linux下沒有和Windows DDK對應(yīng)的東西,也就是說內(nèi)核創(chuàng)建者沒有提供Linux設(shè)備驅(qū)動開發(fā)包,而是將內(nèi)核源碼對所有人公開。內(nèi)核源碼的頭文件就是開發(fā)驅(qū)動所需要的所有東西。驅(qū)動程序使用GNU C編譯器,即GCC,它也被用來編譯應(yīng)用程序。和Windows類似,通過Makefile文件指定驅(qū)動如何被編譯生成。
Linux驅(qū)動開發(fā)Makefile
一旦定義了Makefile,使用簡單的make命令來生成驅(qū)動。圖5.2.1顯示了一個用來生成叫mydriver的驅(qū)動程序的Makefile文件示例,源代碼文件為mydriver.c。第一個條目是KERNELDIR,定義一個環(huán)境變量,指定了內(nèi)核頭文件的位置。后面一行包含了當前內(nèi)核的配置信息。在內(nèi)核和驅(qū)動程序被編譯生成之前,外部定義的內(nèi)核變量在.config文件中指定,該文件位于內(nèi)核源碼樹的根目錄,這樣內(nèi)核的頭文件可以使用這些信息。CFLAGS用來設(shè)置GCC編譯器額外的標志,‘-O’ 打開代碼優(yōu)化開關(guān),‘-Wall’打印所有的代碼警告?!產(chǎn)ll’節(jié)是make命令執(zhí)行時默認會去檢查的節(jié)。一個目標叫mydriver,依賴于目標文件mydriver.o,mydriver.o由GCC生成。環(huán)境變量LD指定用來生產(chǎn)最后的驅(qū)動模塊的GNU鏈接器。選項‘-r’指定輸出可被重定位,即里面的內(nèi)存內(nèi)置應(yīng)該是相對于某個基地址的偏移,這個基地址在編譯的時候是不知道的?!?^’ 是mydriver.o的別名,‘$@’ 是mydriver的別名,也就是說,它要求鏈接器從mydriver.o目標文件生成可重定位的代碼然后生成輸出文件mydriver。
Figure 5.2.1 Makefile used to build a driver in Linux
內(nèi)核模塊管理程序如insmod和lsmod分別用來將驅(qū)動程序加載到內(nèi)核和查看當前已加載的所有內(nèi)核模塊。
Linux驅(qū)動開發(fā)文檔
Linux內(nèi)核源碼樹下有個叫“Documentation”的目錄,這個目錄下有一些關(guān)于Linux內(nèi)核方面的文檔,但還是沒有Windows DDK文檔那樣完整和生動。Rubini et al[Rubini et al, 01]編寫Linux驅(qū)動書籍對設(shè)備驅(qū)動開發(fā)者來說是個更好的信息來源。Linux內(nèi)核沒有自帶任何驅(qū)動程序例子,但有在實際環(huán)境中被使用的驅(qū)動程序源碼,這些代碼可以作為開發(fā)新設(shè)備驅(qū)動的基礎(chǔ)。然而,這對設(shè)備驅(qū)動開發(fā)新手來說并不是一個好的引導(dǎo)素材。
驅(qū)動程序調(diào)試
每一個軟件部件在其開發(fā)周期內(nèi)總是不時地需要調(diào)試,因為總存在一些難以靠檢查源碼就能發(fā)現(xiàn)的晦澀的bug。對驅(qū)動程序來說更是如此。應(yīng)用程序的bugs最壞情況下會是應(yīng)用進程不穩(wěn)定,而驅(qū)動程序中嚴重的bug會使整個系統(tǒng)不穩(wěn)定。調(diào)試應(yīng)用程序很直觀,即在調(diào)試器的幫助下,在感興趣的源代碼位置設(shè)置一個中斷語句。這個因調(diào)試器而異。Windows下,使用Microsoft Visual Studio調(diào)試器,設(shè)置斷點只需要在源碼所在行點擊一下鼠標。DDD(Linux下的GUI調(diào)試器,使用了最流行的命令行調(diào)試器GDB)調(diào)試器也是一樣的操作。程序在調(diào)試模式運行時,遇到斷點程序會暫停執(zhí)行使得可以單步跟蹤,即從那個地方開始的指令逐條執(zhí)行并可觀察執(zhí)行的效果。調(diào)試器中一般可以看到被調(diào)試程序中變量的內(nèi)存地址和變量的值。到此為止我們所討論的調(diào)試方式也適用于驅(qū)動程序的調(diào)試,某種程度上還適用于每種操作系統(tǒng)。
Windows下驅(qū)動調(diào)試
Windows下驅(qū)動程序的調(diào)試有若干不同的方法。最簡單的就是使用DbgPrint調(diào)試例程將消息打印到Windows調(diào)試器緩沖區(qū)。比如使用Windbg調(diào)試器時,可從調(diào)試器界面看到那些消息,否則需要一個特定的程序從調(diào)試器緩沖區(qū)接收那些消息,如SysInternals公司免費提供的DebugView程序[Russinovich, 01]。DbgBreakPoint例程在程序中設(shè)置一個斷點,當被執(zhí)行時,系統(tǒng)停下來并將驅(qū)動執(zhí)行代碼傳遞給系統(tǒng)調(diào)試器。Assert宏基于條件測試的結(jié)果,將驅(qū)動執(zhí)行轉(zhuǎn)移到系統(tǒng)調(diào)試器。使用Microsoft內(nèi)核調(diào)試器Windbg,需要兩個PC。第一個PC是驅(qū)動代碼的開發(fā)和調(diào)試機器,第二個PC通過串口連到開發(fā)驅(qū)動的PC。開發(fā)者使用第二個PC,通過串口控制臺連接到第一個PC,就能和在第一個PC上的調(diào)試器交互。NuMega DriverStudio ? [Compuware, 01]提供的調(diào)試器允許驅(qū)動調(diào)試在單個PC內(nèi)部,這個PC可以作為驅(qū)動開發(fā)機器,并作為應(yīng)用調(diào)試器。它提供了一個console窗口,命令行可從這里輸入以便進行控制。
Linux下驅(qū)動調(diào)試
和Windows一樣,Linux驅(qū)動調(diào)試可使用內(nèi)核提供的調(diào)試例程printk,對應(yīng)于Windows的DbgPrint例程。它和C的標準I/O例程printf類似,只是需要一個額外的參數(shù)來指定消息將被打印到的位置。內(nèi)核調(diào)試器可作為內(nèi)核源碼的一個patch被獲取到。Linux內(nèi)核調(diào)試器(kdb)的patch可從KDB項目頁面獲取[KDB, 02]。它允許標準調(diào)試器一樣的操作,即設(shè)置斷點、單步執(zhí)行驅(qū)動代碼和觀察驅(qū)動內(nèi)存。
總結(jié)
Windows和Linux是當今最為普遍流行的操作系統(tǒng)。Windows的市場份額最大,Linux知名度則在不斷增長。硬件設(shè)備制造商每發(fā)布一種新設(shè)備,都配備有一個能使新設(shè)備在Windows下使用的驅(qū)動程序。兩種操作系統(tǒng)的驅(qū)動架構(gòu)有很多的不同,但也有一些類似的地方。
設(shè)備驅(qū)動架構(gòu)
通過對兩種操作系統(tǒng)驅(qū)動架構(gòu)的比較,可以看到Windows系統(tǒng)的架構(gòu)更為成熟。這并不意味著Windows架構(gòu)提供更好的功能,而是它有一個定義更為良好的的驅(qū)動模型讓驅(qū)動開發(fā)者去遵循。雖然驅(qū)動程序的編寫者可以忽視Windows驅(qū)動模型開發(fā)自己的驅(qū)動,但很少有驅(qū)動開發(fā)者這么做。Linux下沒有正式定義的驅(qū)動模型。Linux驅(qū)動程序編寫者基于他們個人的設(shè)計來開發(fā)驅(qū)動。除非兩個驅(qū)動開發(fā)組合作來開發(fā)能一起工作的驅(qū)動程序,不同開發(fā)者開發(fā)的驅(qū)動程序在Linux系統(tǒng)下不能協(xié)同工作。Windows下,兩個或多個驅(qū)動開發(fā)組開發(fā)的驅(qū)動可以協(xié)同工作,只要他們都遵循WDM來構(gòu)建驅(qū)動程序。Windows驅(qū)動架構(gòu)支持PnP和電源管理,是通過在適當?shù)臅r候?qū)⑦@些消息發(fā)送到實現(xiàn)了消息處理函數(shù)的驅(qū)動。目前的Linux驅(qū)動架構(gòu)沒有提供這樣的機制。
設(shè)備驅(qū)動程序設(shè)計
設(shè)計驅(qū)動程序時應(yīng)該對操作系統(tǒng)提供的輔助設(shè)施進行評估。Windows和Linux是兩個現(xiàn)代化的操作系統(tǒng)。它們提供了對數(shù)據(jù)結(jié)構(gòu)如棧(stack)、隊列(queue)和自旋鎖(spin locks),還有完成硬件無關(guān)操作的硬件抽象層(HAL,Hardware Abstraction Layer)例程的實現(xiàn)。這使得驅(qū)動程序可以在不同的處理器架構(gòu)下運行,如IA64 (Intel’s 64 bit platform) 和SPARC。兩種操作系統(tǒng)下的驅(qū)動程序都可以被分成多個模塊,然后以棧式結(jié)構(gòu)堆疊,使用標準化的數(shù)據(jù)結(jié)果進行通信。Windows這種標準化的數(shù)據(jù)結(jié)構(gòu)就是IRP,Linux下則可是任何驅(qū)動程序自定義的結(jié)構(gòu),因為操作系統(tǒng)沒有提供任何標準化的結(jié)構(gòu)。
設(shè)備驅(qū)動程序?qū)崿F(xiàn)
兩種操作系統(tǒng)下的驅(qū)動程序都由一系列各操作系統(tǒng)期望驅(qū)動程序?qū)崿F(xiàn)的例程組成。包括標準I/O如讀寫設(shè)備的例程、發(fā)送I/O控制命令到設(shè)備的例程。兩種系統(tǒng)中,每個驅(qū)動程序都要實現(xiàn)一個驅(qū)動被加載時執(zhí)行的例程和驅(qū)動被卸載時執(zhí)行的例程,不同驅(qū)動程序的例程可使用相同的名稱,但一般來說每種操作系統(tǒng)都使用慣用的命名方式。Windows下設(shè)備驅(qū)動的的命名方式(設(shè)備接口)比當前Linux下設(shè)備驅(qū)動的命名方式更便捷。Linux使用GUID為設(shè)備命名,相比Windows,驅(qū)動名稱沖突的現(xiàn)象在Linux下更易于出現(xiàn)。
驅(qū)動程序開發(fā)環(huán)境
Windows操作系統(tǒng)提供DDK,其中包含相關(guān)的文檔和開發(fā)工具,大大減少了開發(fā)驅(qū)動需要的學(xué)習(xí)時間。Linux下沒有DDK,因此剛開始時設(shè)備驅(qū)動開發(fā)者需要收集其他的一些資源來輔助驅(qū)動的開發(fā)。一旦花時間熟悉了兩種驅(qū)動開發(fā)環(huán)境后,開發(fā)者會發(fā)現(xiàn)創(chuàng)建Linux驅(qū)動程序比創(chuàng)建Windows驅(qū)動程序更容易,因為所有的Linux內(nèi)核源碼對他們是可見的。這使得驅(qū)動開發(fā)者能更深入到他們的驅(qū)動程序所依賴的內(nèi)核代碼中去跟蹤解決驅(qū)動程序的問題。Windows,只有debug版二進制組件可用,里面包含調(diào)試符號如函數(shù)名稱和變量名,但不如擁有操作系統(tǒng)源碼的用處那么大。
結(jié)束語
驅(qū)動程序應(yīng)該被設(shè)計成需要終端用戶很少的交換就能使用,并且應(yīng)用程序可以訪問驅(qū)動的所有功能。第一點是Windows的一個要點,它支持了PnP。Linux是一個開源項目,它還在不斷地改進中。以后Linux的驅(qū)動架構(gòu)很有可能像Windows驅(qū)動架構(gòu)那樣正式化,如具有一個WDM那樣的驅(qū)動模型。隨著越來越多的個人和組織采用了Linux,支持Linux的硬件廠商也會增加。
致謝
This research was made possible through the Andrew Mellon Foundation scholarship at Rhodes University,
Grahamstown, South Africa.
參考書目
[Beck et al, 98] Beck, Bohme, Dziadzka, Kunitz, Magnus, Verworner, Linux Kernel Internals,
Addison Wesley, 1998.
[Cant C, 99] Cant C, Writing Windows WDM Device Drivers, CMP Books, 1999.
[Compuware, 01] Compuware, NuMega DriverStudio Version 2.5, http://www.compuware.com, 2001.
[Compuware, 01] Compuware, Using Driver Works, Version 2.5, Compuware, 2001.
[Deitel, 90] Deitel HM, Operating Systems, 2nd Edition, Addison Wesley, 1990.
[Davis, 83] Davis W, Operating Systems, 2nd Edition, Addison Wesley, 1983.
[Flynn et al, 91] Flynn IM, McHoes AM, Understanding Operating Systems, Brooks/Cole, 1991.
[Katzan, 73] Katzan H, Operating Systems: A Pragmatic Approach, Reinhold, 1973.
[KDB, 02] KDB, The Linux Built in Kernel Debugger, http://oss.sgi.com/projects/kdb, 2002.
[Laywer, 01] Lawyer D S, Plug and Play HOWTO/Plug-and-Play-HOWTO-1.html,
http://www.tldp.org/HOWTO, 2001.
[Linus FAQ, 02] The Rampantly Unofficial Linus Torvalds FAQ,
http://www.tuxedo.org/~esr/faqs/linus/index.html, 2002.
[Linux HQ, 02] The Linux Headquarters, http://www.linuxhq.com, 2002.
[Lorin et al, 81] Lorin H, Deitel HM, Operating systems, Addison Wesley, 1981.
[Microsoft DDK, 02] Microsoft ,DDK- Kernel Mode Driver Architecture, Microsoft, 2002.
[Microsoft WDM, 02] Microsoft, Introduction to the Windows Driver Model,
http://www.microsoft.com/hwdev/driver/wdm, 2002.
[Oney, 99] Oney W, Programming the Microsoft Windows Driver Model, Microsoft, 1999.
[Open Group, 97] Open Group, Universal Unique Identifier,
http://www.opengroup.org/onlinepubs/9629399/apdxa.htm, 1997.
[Redhat,02] Redhat, http://www.redhat.com,2002.
[Rubini et al, 01] Rubini A, Corbet J, Linux Device Drivers, 2nd Edition, Oreilly, 2001.
[Russinovich, 98] Russinovich M, Windows NT Architecture,
http://www.winnetmag.com/Articles/Index.cfm?ArticleID=2984, 1998.
[Russinovich, 01] Russinovich M, SysInternals, http://www.sysinternals.com, 2001.
[Rusling, 99] Rusling D A, The Linux Kernel, http://www.tldp.org/LDP/tlk/tlk.html, 1999.
聯(lián)系客服