Linux和windows訪問設備的方式比較

LInux一直秉承着一切皆文件的理念,但是如何把設備當做文件來處理呢?Windows又是如何處理設備的呢?參照前輩的譯文




      畢業後一直在學操作系統, 有時候覺得什麼都懂了,有時候又覺得好像什麼都不懂,但總體來說自認爲對操作系統實現機制的瞭解比周圍的人還是要多一些。去年曾花了幾個星期的晚上時間斷斷續續翻譯了這篇對Linux和Windows驅動架構進行比較的論文。原文在這裏


LinuxWindows設備驅動架構比較

1. 概述

這篇論文中,我們將考查目前最爲廣泛使用的兩種操作系統,即LinuxWindows系統的設備驅動架構。爲每種操作系統實現設備驅動所需要的驅動組件將被展示並進行比較,同時也展示每種操作系統中執行I/O到內核緩衝的驅動的實現過程。最後將以對每種操作系統爲開發者所提供的開發環境和輔助設施的考查收尾。

 

2. 引言

現代操作系統中包含多個模塊,諸如內存管理器、進程調度器、硬件抽象層和安全管理器。欲瞭解Windows內核的細節,可參考[Russinovich, 98],Linux內核則參考[Rusling, 99], [Beck et al, 98]。內核可被看作一個黑盒,並且它應該知道如何與現存的不同種類的以及還沒出現的更多硬件設備進行交互。實現一個內核,使其能夠與所有被熟知的硬件設備進行交互是可能的,但並不現實,因爲這會消耗太多的系統資源,沒有必要。

內核模塊化

在內核被創建的時候,並不期望它能知道如何與未出現的設備進行交互。現代操作系統內核允許通過在運行時添加設備驅動模塊的方式來擴展系統功能,這個模塊的功能使得內核可以與某種特定的新設備進行交互。每個模塊都提供一個例程供內核在模塊被加載時進行調用,還有一個例程在模塊被移除時調用。每個模塊還實現各種不同的例程以便實現將數據傳送到設備或從設備接收數據的I/O功能,同時還有一個例程供發送I/O控制指令到設備。以上所述對LinuxWindows兩種驅動架構均適用。

本論文的組織結構

本論文分成下面幾節:

l 兩種操作系統大致的驅動架構(第二節)

l 每種操作系統驅動架構組件(第三節)

l 實現一個驅動執行I/O到內核緩衝(第四節)

l 兩種操作系統爲開發者提供的驅動開發環境和輔助設施(第五節)

相關工作

Windows設備驅動架構相關文檔可在Windows驅動開發包中找到,更有甚者,Walter Oney [Oney, 99] 和Chris Cant [Cant, 99] 對Windows驅動架構進行了詳細的展示。Linux設備驅動架構則由Rubini et al [Rubini et al,01]作了很好的描述,可免費獲取。

 

3. 設備驅動架構

設備驅動通過暴露編程接口的方式使得應用程序和操作系統可以對設備進行控制來達到對硬件的操作。這一節將展示當前最常用的兩個操作系統,即WindowsLinux的驅動架構,以及這些架構的起源。

Linux驅動架構的起源

Linux可以說是Unix操作系統的一個克隆,首先由Linus Travolds創造 [Linus FAQ, 02], [LinuxHQ,02]。Linux沿用了類似於Unix的系統架構。Unix系統將設備看作是文件系統的節點。設備以特殊文件節點的方式呈現在目錄中,該目錄通常包含設備文件系統的節點入口[Deitel, 90]。用文件系統節點來表示設備的目的是使得應用程序能以設備無關的方式訪問各種設備[Massie, 86],[Flynn et al, 97]。應用程序仍然可以通過I/O控制操作進行特定於設備的操作。設備由主設備號和次社保號進行標識。主設備號用來作爲驅動數組的索引下標,而次設備號將相似的物理設備歸組[Deitel, 90]。Unix有兩種類型的設備,即字符設備和塊設備。字符設備驅動管理沒有緩衝並需要順序訪問的設備,塊設備驅動管理那些可隨機訪問的設備,數據以塊的方式被訪問。另外,塊設備驅動還用到緩衝區。塊設備必須以文件系統節點的方式掛載之後才能被訪問[Beck et al, 98]。Linux保留了Unix的很多架構設計,區別在於,Unix系統中,每個塊設備需要創建一個對應的字符設備,而在Linux中,虛擬文件系統(VFS)接口使得字符設備和塊設備的區分變得模糊[Beck et al, 98]。Linux還引入了第三種設備,叫網絡設備。訪問網絡設備驅動的方式和訪問字符設備、塊設備的方式不同,它使用了不同於文件系統I/O接口的一個接口集。比如socket接口,就是用來訪問網絡設備的。

Windows驅動架構的起源

1980年,微軟從貝爾實驗室獲取Unix操作系統的許可,之後作爲XENIX操作系統發佈。1981年,MS DOS第一版隨着IBM PC發佈,並具有和基於XENIXUnix系統類似的驅動架構[Deitel, 90]。和Unix操作系統不同的是,這個系統內嵌了常規設備所需的驅動程序。設備入口不以文件系統節點的形式呈現,而是給設備賦予預留的名稱,如CON表示鍵盤或屏幕,PRN表示打印機,AUX表示串口。應用程序不能像對待文件系統節點那樣通過打開設備獲取和驅動程序關聯的設備句柄從而對設備進行I/O操作。操作系統透明地將預留的設備名稱對應到驅動程序所管理的設備。MS DOS第二版引進了可加載驅動的概念。由於Microsoft公開了驅動架構的接口,這也促進了第三方設備製造商生產更多設備 [Davis, 83]。硬件製造商可以爲這些新設備提供運行時可加載到內核或從內核移除的驅動程序。

 

之後,Microsoft又發佈了Windows 3.1,它支持更多的設備並使用基於MS DOS的架構。之後的Windows 9598NTMicrosoft引入了WDM(Windows Driver Mode)WDM的出現是因爲Microsoft想要驅動程序代碼和後面所有新的操作系統兼容[Microsoft WDM, 02]。因此,驅動遵守WDM規範的好處是,驅動程序只需編寫一次,在Microsoft之後所有新版操作系統上使用時只需要重新編譯該驅動即可。

Windows驅動架構

Windows驅動分兩類,分別爲遺留驅動和即插即用驅動。這裏我們只將重點放在PnP驅動上,所有提及的驅動都大可認爲是PnP驅動。PnP驅動不需費什麼力氣就能安裝好,因此它對用戶是友好的。另外一個使驅動程序支持PnP的好處是它們只會在需要的時候被操作系統加載,因此它們不會無端的耗盡系統資源。遺留驅動是爲Microsoft早期的操作系統實現的,它們的架構已經過時。WDMMicrosoft指定的標準驅動模型[Microsoft DDK, 02]。WDM驅動適用於Microsoft近期所有的操作系統(Windows 95和之後的)

WDM驅動架構

WDM驅動分三類,分別爲過濾驅動、功能驅動和總線驅動[Oney, 01]。它們形成了圖2.3所示的棧式結構。另外,WDM驅動必須是具有PnP感知的,支持電源管理和Windows管理規範(Windows Management Instrumentation)。圖2.3顯示了各個驅動如何交互數據和消息。一個叫I/O請求包(IRPI/O Request Package)的標準結構被用來進行通信。任何時候,應用程序向驅動程序發送請求時,I/O管理將創建IRP並下傳到驅動程序,驅動程序處理完畢後,“完成”這個IRP [Cant, 99]。不是所有的IRP都被下發到總線驅動,有些IRP被上層的驅動處理後直接返回到I/O管理器。對設備硬件的訪問需要通過硬件抽象層。

 

Figure 2.3 The WDM Driver Architecture

 

Linux驅動架構

Linux下的驅動以模塊的形式呈現,這些模塊就是擴展了Linux內核功能的一個個代碼塊[Rubini et al, 01]。模塊可形成圖2.4那樣的層次結構。模塊之間的通信通過函數調用實現。模塊在加載時,將導出模塊中所有對Linux內核所維護的符號表公開的函數,之後這些函數對所有的內核模塊都是可見的。對設備的訪問需要通過硬件抽象層,硬件抽象層的實現依賴於內核編譯時所針對的硬件平臺,如x86SPARC

 

Figure 2.4 The Linux Driver Architecture

 

Linux和Windows驅動架構的比較

如圖2.32.4所示,兩個操作系統有很多的相似之處。兩個系統中,驅動程序都是作爲擴展內核功能的模塊化組件。在Windows系統中,驅動層級之間的通信是通過將IRP作爲標準系統函數或驅動程序自定義函數的參數來實現,Linux下函數調用的參數則是根據具體的驅動而不同。Windows有單獨的內核模塊來管理PnPI/O和電源,這些組件在適當的時候將IRP發送到驅動程序。

Linux系統中,模塊沒有明顯的層級關係,比如沒有區分總線、功能、過濾驅動。內核沒有明確定義的PnP、電源管理器以便在適當的時候將特定的信息發送給內核模塊。內核可能會加載具有PnP、電源管理功能的內核模塊,但內核模塊暴露給驅動程序的接口並沒有規定。

這些功能一般會合併到新版的Linux內核中,因爲Linux內核總是處於發展狀態。每當內核將數據發送給棧式模塊中的某個驅動程序時,通過這些驅動所指定的某個接口,該數據可被分享給棧式模塊中的其他驅動程序。

在這兩種系統環境中,對硬件的訪問都會通過硬件抽象層接口,硬件抽象層接口根據內核編譯時所針對的特定平臺(X86SPARC)而實現。兩種架構的相同之處在於,驅動程序都是運行時可加載的模塊,每個模塊都包含一個入口點使內核知道從哪裏開始執行模塊的代碼。一個模塊還包含這樣一些例程,即該模塊所管理的設備接收到I/O操作請求時供內核調用的例程。這使得內核可以嚮應用層提供設備無關的接口。在後面的第3.3節中,將對兩種架構中的驅動組件作更深入的比較。

驅動組件

編寫驅動程序時需要對硬件設備如何被操作有了解。比如說,幾乎所有的設備都允許用戶讀取和寫入數據。這一節中將展示所有驅動程序都應該包含的驅動組件,同時對兩種操作系統的驅動組件進行比較,並展示如何實現一個對內核緩衝區進行I/O操作的驅動程序。本節將以對每種操作系統爲驅動程序開發所提供的環境和輔助設施的考究來收尾。

Windows驅動組件

Windows驅動程序由各種不同的例程組成,其中有一些是必須的,其它的則是可選的。這一節展示所有驅動程序都必須實現的例程。Windows中的設備驅動以一個叫DriverObject的結構體表示。用一個結構體諸如驅動對象來表示一個驅動是有必要的,因爲內核實現了可被所有驅動對象使用的各種例程。這些例程對一個驅動對象進行操作,這部分內容將在下一節進行討論。

驅動程序初始化

Windows中每個設備驅動程序都包含一個叫DriverEntry的例程。顧名思義,這個例程在驅動程序被加載時執行,驅動所管理的設備對象的初始化也在這個例程中進行。Microsoft’s DDK [Microsoft DDK, 02] 是這樣描述的:驅動對象表示當前被加載的驅動程序,設備對象則表示一個物理、邏輯或虛擬的設備。一個被加載的驅動程序(用驅動對象表示)可以管理多個設備(用設備對象表示)。初始化過程中,設備對象中用以指定驅動程序的卸載例程、添加設備例程和分發例程都將被設置。卸載例程用於驅動程序被卸載時做一些清除操作,例如釋放從內核堆中的分配的內存。添加設備例程僅在當驅動程序作爲PnP驅動加載時,在DriverEntry例程之後被調用,而分發例程用於實現I/O操作。

AddDevice例程

PnP驅動程序需要實現AddDevice例程。在這個例程中,一個設備對象被創建,爲該設備保存全局數據的空間被分配。設備資源的分配和初始化也在這裏進行。設備對象根據其被創建的位置而擁有不同的名稱。如果一個設備在當前加載的驅動程序中創建並用於管理該驅動,則該設備叫功能設備對象(FDO)。如果一個設備對象是由驅動棧中位於下方的驅動程序創建,則該設備叫物理設備對象(PDO)。如果一個設備對象是由位於上方的驅動程序所創建,則叫過濾驅動對象(FIDO)

創建一個設備對象

一個設備對象對應於在AddDevice例程中調用I/O管理器中名爲IoCreateDevice的例程所創建的設備。對IoCreateDevice來說,最重要的是設備對象的名稱和設備類型。這個名稱使得應用程序和其他的內核驅動可獲取到該驅動的句柄,從而可進行I/O操作。設備類型指定了驅動程序管理的設備的類型,如存儲設備。

全局驅動數據

當一個設備對象被創建時,可以將一個內存塊與之關聯,該內存塊叫DeviceExtension,也即在Windows中驅動程序數據保存的地方。這是一個挺重要的東東,它使得在驅動程序代碼中使用難於維護的全局數據結構變得沒有必要。例如,要是錯誤的聲明瞭一個和全局變量具有相同名稱的局部變量,驅動程序編寫者會發現難以跟蹤這樣的bug。這也使得維護特定於設備對象的數據變得簡單,尤其是當多個設備對象存在於一個驅動程序中的時候,比如總線驅動程序在管理總線上出現的多個設備的物理設備對象時。

設備命名

設備的名稱可在設備對象被創建的時候賦予,這個名稱可以用來訪問驅動的句柄,句柄又被用來進行I/O操作。Microsoft建議不要給在過濾驅動和功能驅動中創建的功能設備對象命名。Oney [Oney, 99]指出,若一個設備對象具有名稱,則任意用戶都能打開設備對象並對其進行I/O操作,即使是對非磁盤設備驅動。這是因爲Windows默認就給了非磁盤設備對象毫無限制的訪問狀態。另外一個問題是這些名稱不需要遵循任何命名規範,指定的名稱往往不是經過挑選的。例如兩個驅動程序開發者可能給他們的設備對象賦予相同的名稱,這樣就會引起衝突。Windows還支持另外一種設備對象命名方式,即設備接口。設備接口是由128比特位構成的全局唯一標識符[Open Group, 97]。GUID可用Microsoft DDK中提供的工具生成,生成之後可對外發布。驅動程序通過在AddDevice例程中調用I/O管理器的名爲IoRegisterDeviceInterface的例程註冊設備接口。一旦註冊,驅動程序必須調用I/O管理器的IoSetDeviceInterfaceState例程來使能設備接口。註冊過程中一個接口數據入口被添加到Windows註冊表中,應用程序接着就可以訪問到。

從應用程序訪問驅動

應用程序欲對設備驅動執行I/O操作前,必須先通過調Win32 API CreateFile獲取到設備驅動的一個句柄,這個API需要設備的路徑作爲參數,如\device\devicex。具有名稱的設備將出現在命名空間“\\device”中,因此先前的路徑表示設備devicexCreateFile同時需要指定對設備的訪問標誌,如讀、寫和共享方式。對註冊了設備接口而沒有名稱的設備的訪問則不同於圖3.1.2.4展示的例程,它需要使用驅動程序的GUID,調用Win32 API SetupDiGetClassDevs獲取一個指向設備信息結構的句柄。這種方式只適用於驅動程序已經註冊了設備接口、應用程序需要訪問設備(叫設備接口類)的情況。每次驅動程序調用I/O管理器例程IoRegisterDeviceInterface時,一個新的設備接口類的實例就被創建。一旦應用程序獲取到了設備信息句柄,對Win32 API SetupDiEnumDeviceInterfaces的多個調用將會爲每個設備接口類實例返回設備接口數據。最後,通過調用Win32 API SetupGetDeviceInterfaceDetail,並根據之前返回的接口數據,可爲每個設備接口類實例獲取到一個設備路徑。接着,對感興趣的設備,使用設備路徑爲參數調用CreateFile來獲取句柄以便執行I/O操作。

 

Figure 3.1.2.4 Obtaining a handle an application can use for I/O from a device GUID.

設備對象棧

當PnP管理器調用AddDevice例程時,它其中的一個參數是來自下層驅動的一個設備對象(PDO)。設備對象在AddDevice例程中完成堆疊,因爲發往下層驅動的IRP可被當前加載的驅動獲取到。如圖 3.1.2.5所示,設備堆疊是通過調用I/O管理器例程IoAttachDeviceToDeviceStack 來完成。在調用IoAttachDeviceToDeviceStack時,需要一個位於棧中新創建設備對象下方的物理設備對象。這個例程將新創建的設備附加到設備棧的頂層,並將當前位於其下方的設備對象返回,圖 3.1.2.5中下方設備爲設備對象X。下層的物理設備可位於新設備下方的任何位置,而IoAttachDeviceToStack 返回的是緊鄰着當前設備的下層設備。

 

Figure 3.1.2.5 Attaching a device object to the top of a device object stack.

Windows應用層到內核和內核到應用層數據傳輸模式

從內核空間到用戶空間以及從用戶空間到內核空間傳送數據的模式是在設備對象的flag域中設置。共有三種模式,分別爲buffer I/Odirect I/Oneither I/O。圖3.1.2.6 闡述了這三種模式。在buffer I/O模式中,操作系統分配了一個內核緩衝區來處理請求。在寫操作中,操作系統首先驗證用戶空間提供的緩衝區,然後從用戶空間將數據拷貝到新分配的內核緩衝區,接着講內核緩衝區傳送給驅動程序。讀操作時,操作系統驗證用戶緩衝區然後將數據從新分配的內核緩衝區中拷貝到用戶緩衝區。驅動程序可通過IRPAssociatedIrp.SystemBuffer域訪問到內核緩衝區。當使用buffer I/O模式時,驅動程序通過讀取或寫入內核緩衝區來實現與應用層的通信。

Direct I/O是用於應用層和驅動程序交換數據的第二種模式。應用層提供的緩衝區被操作系統在內存中鎖定,這樣它就不會被交換出去,並將被鎖定內存的內存描述列表(Memory Description ListMDL)傳送給驅動程序。內存描述列表是一個不透明的結構體,它的實現對驅動程序是不可見的。驅動程序之後通過MDL對用戶空間緩衝區進行DMA操作。驅動程序通過IRPMdlAddress域訪問MDL。使用direct I/O的好處是它比buffer I/O速度要快,因爲不需要在用戶層和內核層之間拷貝任何數據,而是直接對用戶緩衝區進行I/O操作。

第三種I/O模式既不使用buffer也不使用MDLs,操作系統直接將用戶空間緩衝區的虛擬地址傳送給驅動程序。驅動程序在使用之前負責檢查緩衝區的有效性。此外,只有在當前線程上下文環境和應用程序的上下文環境一致時,用戶空間緩衝區才能被訪問,否則會出現頁錯誤,因爲虛擬地址只有在應用程序所對應的進程處於激活狀態時纔有效。

 

 

Figure 3.1.2.6 The three ways in which data from kernel to user and user to kernel

space is exchanged.

分發例程

分發例程用來處理接收到的I/O請求包,即IRPs(I/O request packets)。當一個IRP到來(如當一個應用程序發起I/O操作)時,一個適當的例程被從驅動對象的MajorFunction域中指定的例程數組中選出來,如圖3.1.3。這些例程在驅動程序的入口函數中被初始化。每個IRP在創建時就與一個I/O stack location結構體(用於存儲IRP的參數)關聯。這個結構體有一個域,指定了IRP需要執行的分發例程和分發例程需要的相關參數。I/O管理器根據IRP決定將IRP發往哪個分發例程。

 

Figure 3.1.3 dispatching IRP’s to dispatch routines.

 

因此,IRPs被路由到適當的驅動例程進而得到處理。分發例程ID如表3.1.3所示,它們作爲例程數組的索引,這個數組是在驅動對象的MajorFunction域中指定。分發例程的名稱是驅動程序所實現的例程的名稱,這些例程都將一個IRP和該IRP被髮送到的設備對象作爲參數。

 

Table 3.1.3 Required Windows driver dispatch routines

Windows驅動程序安裝

Windows根據一個INF文件中的安裝信息來安裝驅動。驅動程序的編寫者負責爲驅動程序提供一個INF文件。Windows DDK提供一個叫GenInfGUI應用程序,來爲驅動程序生成INF文件。這個工具需要提供一個公司名稱和一個Windows設備類,驅動程序將被安裝到該設備類下。Windows爲驅動程序預定義了各種不同的設備類。從系統控制面板進入到設備管理器面板,可看到顯示的所有按設備類分類的已安裝驅動程序。已有的設備類如1394PCMCIA設備類。可在INF文件中添加一個ClassInstall32節來添加一個自定義的設備類。對PnP感知的設備,還需要在INF文件中指定一個硬件ID,在該設備被添加到系統中時,系統將用該ID來標識設備。硬件ID是一個標識字符串,PnP管理器在設備添加到系統中用硬件ID來標識設備。MicrosoftWindows系統會用到的各種設備發佈了硬件ID。硬件ID保存在硬件設備中,操作系統在設備添加到系統中時從設備讀取。一旦新設備的INF文件成功安裝到系統中,每當具有指定硬件ID的設備被添加到系統中,爲該設備編寫的驅動程序都被加載,並在設備移除時被卸載。

Windows獲取驅動程序使用信息

系統控制面板上的設備管理器給用戶提供驅動的相關信息。它列出了所有當前已加載的驅動,每個驅動提供者的相關信息和驅動資源使用情況。同時還顯示驅動無法加載時的失敗信息以及錯誤碼。

Linux驅動架構組件

Linux下的設備驅動和Windows設備驅動的相似之處在於它們都是由一些執行I/O及控制操作的例程組成。驅動程序沒有對應的驅動對象,而是由內核直接管理。

驅動程序初始化

Linux下的美國各驅動程序包含一個驅動註冊例程和反註冊例程。驅動註冊例程類似於Windows的驅動入口例程。驅動程序編寫者使用內核定義的兩個宏module_initmodule_exit來指定自定義的例程作爲註冊和反註冊例程。

 

3.2.1.1. 驅動註冊和反註冊

module_init聲明的註冊例程是驅動程序被加載時第一個執行的例程。在這個例程中,用一個內核字符設備註冊例程register_chrdev註冊驅動。這個例程需要一個驅動名稱、主驅動編號(將在3.2.2節中討論)和一系列執行文件操作的例程。其它特定於驅動的初始化也必須在這個例程中完成。反註冊函數在驅動程序被卸載時執行,它的主要功能是做一些清除操作。反註冊之前使用register_chrdev註冊的驅動程旭時會調用內核例程unregister_chrdev,並需以設備名和主編號爲參數。

設備命名

Linux下,設備命名使用0255的數字,叫主設備編號。這意味着最多只能有256個可用的設備,也即應用程序可獲取到句柄的設備。但這樣一個主設備的每個驅動程序可以管理額外的256個設備。這些驅動程序管理的設備也使用0255的數字標識,叫次設備編號。因此,應用程序可訪問多達65535(256*256)個設備。主設備編號賦給一些熟知的設備,如IEEE1394的編號爲171Linux內核源碼樹中的文件Documentation/devices.txt包含了所有主設備編號的分配情況和編號註冊中心的聯繫地址。當前,主設備編號240-254爲實驗所用。一個驅動程序通過指定0作爲主設備編號來請求一個自動分配的主編號(若當前還有可用的主設備編號的話)。這種指定0爲主設備編號的方式並不會有什麼問題,因爲它是爲null設備預留的,而沒有一個新的驅動程序會將自己註冊爲null設備驅動。

應用程序訪問驅動

應用程序通過文件系統入口(nodes)訪問驅動。按照慣例,驅動程序目錄爲/dev。需要對驅動執行I/O操作的應用程序使用open系統調用來獲取某個特定驅動的句柄。Open系統調用需要一個設備節點名稱如/dev/tty和訪問標識(flags)。獲取句柄之後,應用程序使用該句柄來調用其他的I/O系統調用如readwriteIOCTL

文件操作

Windows下,分發例程是在驅動入口例程中設置。Linux下,這些分發例程就是所謂的文件操作並使用結構體file_operations來表示。一個典型的驅動程序會實現如表3.2.3列出的文件操作例程。

 

Table 3.2.3 Most commonly defined driver file operations in Linux

 

這些文件操作在驅動程序註冊時指定。每當應用程序請求一個設備句柄時,內核會創建一個叫file的結構體,並在某個驅動例程被調用時將其傳遞給驅動程序。文件操作例程被多個用戶調用,每個都對應一個file結構體。File結構體有一個f_op域,這個域是一個指針,指向驅動註冊時指定的文件操作例程集。因此,在調用任何一個文件操作例程時,都可以通過改變f_op域的值來指向新的文件操作例程集。

驅動程序全局數據 

每當應用程序對/dev下的設備文件節點發起一個open系統調用時,應用程序從操作系統獲得設備的一個句柄。這個時候驅動程序的open函數被調用,並給它傳遞爲open系統調用所創建的file結構體。任何一個文件操作例程執行時,內核都將file結構體傳遞給驅動程序。File結構體的private_data域可以是驅動程序指定的任意自定義結構體。驅動程序的私有數據通常在文件open操作函數中被設置,即爲它分配內存,之後在文件的release操作函數中釋放該內存。File結構體的私有數據域可用來指向驅動程序的全局數據,避免了使用全局變量。

驅動主編號和次編號如何工作

3.2.4.1. 問題

Linux下只有一個驅動程序可通過註冊一個特定的主編號來管理一個設備,也就是說,驅動程序的註冊只能使用一個主編號。舉個例子,存在兩個設備節點/dev/device1(主編號4次編號1)和設備/dev/device2(主編號4次編號2),只有一個驅動程序能夠處理應用程序對兩個節點的請求。這種限制的存在是因爲Linux沒有提供一種註冊機制使得驅動程序能自注冊一個主編號和一個次編號以便能管理一個設備。

 

3.2.4.2.解決辦法

· 加載一個驅動程序來管理主編號爲4的設備。這個驅動程序將自己在內核中註冊(3.2.1.1會看到這是如何完成的)

· 分別加載兩個驅動,一個管理主編號4次編號1的設備,另一個管理主編號4次編號2的設備。這兩個驅動程序沒有在內核中註冊,而是向管理主設備編號4的另外一個驅動程序註冊。這個驅動程序負責實現註冊機制並跟蹤管理向它註冊的所有驅動程序。

 

· 應用程序打開任意一個設備節點(/dev/device1/dev/device2)時,註冊爲管理主設備4的驅動程序的open例程將被內核調用。一個用來表示被打開設備的file結構體作爲參數傳遞給這個open例程。

· 這時候,管理設備主編號4的驅動程序修改文件操作函數指針(file結構體的f_op成員)來指向管理被打開設備的驅動程序所實現的I/O例程。應用程序打開次設備時,管理主編號4的驅動程序以下列方式區分:

o 一個叫inode的結構體被傳遞給open例程。這個結構體包含一個叫i_rdev的域,該域指定了open操作的目標設備對應的主編號和次編號。內核的MINORMAJOR宏可用來從i_rdev域提取住次編號。這個例子中,主編號爲4,次編號爲12。管理主編號4的驅動程序就可以通過這個信息從它的註冊數據庫中定位到次設備驅動程序。

用戶到內核和內核到用戶空間數據傳輸模式

Linux下,用戶到內核和內核到用戶空間的數據交換方式有三種,分別是buffer I/Odirect I/Ommap。在buffer I/O模式中,內核將數據從用戶空間拷貝到內核空間供驅動程序使用。和Windows不同,Linux沒有自動對I/O進行緩衝,而是提供訪問用戶和內核空間的例程,驅動程序使用這些例程來完成用戶和內核空間之間數據的拷貝。Direct I/O模式中,驅動程序可對用戶空間緩衝區直接讀和寫。這是通過kiobuf接口完成,它將用戶空間緩衝區映射到調用系統調用kiobuf時定義的結構體。這個操作會鎖定用戶空間緩衝區,這樣該空間不會被換出以便滿足設備的I/O操作。第三種方式是mmap,它是由驅動程序使用mmap內核調用將內核的空間塊映射到用戶空間,應用程序因而可以對映射的內核內存進行I/O操作[Rubini et al, 01].。

Linux驅動安裝

Linux下驅動程序的安裝是將驅動文件放置到特定的系統目錄下。在RedHat發行版中[Redhat, 02],模塊位於目錄/lib/modules/kernel_versionkernel_version指定當前內核版本,如2.4.19。一個叫modules.conf的配置文件位於系統配置文件目錄如/etc中,這個文件在加載模塊時被內核使用。通過修改該文件,可對某個驅動程序放置位置進行覆蓋。還可以定義其它一些模塊加載選項,如驅動被加載時給它傳遞的參數。模塊加載和卸載使用系統自帶的內核模塊工具包,叫insmodmodprobermmodInsmodmodprobe將驅動程序二進制鏡像加載到內核,rmmod則移除模塊。另一個叫lsmod的程序列出當前所有已加載的模塊。Insmod嘗試加載一個模塊,若該模塊依賴於其他模塊,則返回一個錯誤碼。Modprobe則嘗試着滿足模塊依賴關係,它試圖將當前模塊所依賴的其它模塊先進行加載。模塊依賴關係信息可從一個叫modules.dep的文件獲取,該文件位於系統模塊目錄(system’s modules directory)中。在驅動程序可被應用程序訪問前,這個驅動的一個附有主次設備編號的設備節點(2.13.2.2.1節,Linux如何在系統中表示設備)首先要在設備目錄/dev中被創建。系統程序mknod就是爲這個目的準備的。在創建一個設備節點時,指定節點爲字符設備還是塊設備是有必要的。

Linux獲取驅動使用信息

我們時常需要獲取系統已加載驅動的狀態信息。Linux下,proc文件系統是用來將內核信息嚮應用程序發佈。Proc文件系統和其他的文件一樣,它也包含目錄和文件節點供應用程序訪問和執行I/O操作。Proc文件系統中的文件和普通文件的區別在於,對proc文件執行I/O操作的數據是被傳遞到內核內存而不是磁盤存儲。Proc文件系統是應用程序和內核組件之間的通信媒介。例如,讀取/proc/modules將返回當前所有已加載模塊和它們的依賴關係。在獲取驅動狀態信息和發佈驅動程序數據到應用程序時,proc文件系統就尤爲有用。

WindowsLinux驅動架構組件比較

WindowsLinux的驅動程序都是由一系列執行I/O操作的例程組成的可動態加載的模塊。當加載一個模塊時,內核將定位到被系統標記爲驅動程序入口的例程作爲驅動代碼執行的起點。

驅動例程

兩個系統中驅動程序都具有初始化和反初始化例程。在Linux中,這兩個例程的名稱可自定義,在Windows中,初始化例程的名稱固定(DriverEntry)但反初始化例程可自定義。Windows爲每個驅動程序維護一個驅動對象,驅動程序的多個實例用多個驅動對象表示。Linux下,內核爲每個管理一個設備主編號的驅動維護信息,即每個主設備驅動。兩個操作系統都要求驅動程序實現標準的I/O例程,Windows中叫分發例程,Linux下叫文件操作。Linux下,可爲每個應用程序獲取到的設備句柄設置一個不同的文件操作例程集。Windows下,分發例程在驅動對象的一部分,並且是一次性地在DriverEntry例程中定義。由於每個被加載的驅動都有一個驅動對象,因此不建議在應用程序使用系統調用請求一個句柄時修改驅動對象的分發例程。Windows有個叫AddDevice的例程,在PnP感知的設備添加到系統時被PnP管理器調用。Linux沒有PnP管理器,也就不存在這樣一個例程。

Windows的分發例程對設備對象和IRPs進行操作,Linux下,文件操作針對file結構體。自定義的驅動全局數據保存在Windows的設備對象中,而Linux下則保存在file結構體中。Windows下,設備對象在驅動加載時被創建,Linux下,file結構體是應用程序通過系統調用open向驅動請求句柄時被創建。這就意味着Linux下每個應用程序的全局數據可保存在file操作結構體中。Windows下,全局數據只能出現在驅動管理的功能設備對象(FDO)中。Windows下每個應用程序的全局數據必須保存在功能設備對象(FDO)自定義結構體的列表結構中。

設備命名

Windows下的驅動使用驅動自定義的字符串命名並顯示在\\device命名空間下。Linux下,驅動被賦予文本形式的名稱,但應用程序並不需要知道這些名稱,驅動是通過主-次編號對來標識。主-次編號的範圍是0-255,因爲是用16比特位來表示主-次編號對,所以最大允許65535個設備安裝到系統中。Linux下的設備通過文件系統節點供應用程序訪問。在大部分的Linux發行版中,目錄/dev包含設備文件系統節點。每個節點創建時帶有驅動的主編號和次編號。應用程序獲得驅動的一個句柄,用來對系統調用open的目標設備節點進行I/O操作。Windows還有另一種驅動命名方式,是給每個驅動註冊的128GUID。應用程序訪問註冊表,通過GUID獲得\\device命名空間下的文本形式的名稱。這個名稱通過使用Win32 API CreateFile來獲取驅動的一個句柄以便進行I/O操作。

 

用戶-內核空間數據交換

兩種操作系統中,數據來自或去往用戶空間的方式是類似的,都允許緩衝區數據傳送,在Windows下是有I/O管理器執行,Linux下則由驅動執行。兩種操作系統都可以進行direct I/O到用戶空間緩衝區,通過鎖定用戶空間緩衝區以使得該緩衝區一直存在於物理內存中。這個起因是驅動程序並不總能直接訪問用戶空間緩衝區,因爲它不能保證一直運行在和擁有該用戶空間緩衝區的應用程序一致的進程上下文中。應用程序有它自己的虛擬地址空間,該地址空間只在它自己的進程上下文中有效。因此,當驅動程序訪問某些應用程序的一個虛擬地址但不在該應用程序的進程上下文中時,就會訪問了無效的地址。

 

驅動安裝和管理

Windows驅動的安裝是通過一個叫INF文件的文本文件。一旦安裝之後,一個設備的驅動程序在設備出現在系統中時會自動被PnP管理器加載。Linux系統中,使用程序工具來加載驅動二進制鏡像到內核。需要手動將一些條目添加到系統啓動文件中,這樣驅動加載程序如modprobe就以驅動程序鏡像路徑或驅動程序的別名爲參數執行。驅動程序的別名在文件/etc/modules.conf中定義,modprobe等類似程序在加載驅動之前會查看該文件。Modules.conf中一個定義別名的條目的例子可類似於“alias sounddriver testdriver”,這是將sounddriver作爲testdriver驅動二進制鏡像的別名。這樣一來,用戶可通過使用標準的更簡單的名稱如sounddriver來加載音頻驅動程序而不需要知道音頻卡的某個特定驅動程序的名稱。Windows下驅動程序的狀態信息可在設備管理面板中看到,也可以直接從系統註冊表中讀取相關數據。Linux下,驅動信息可通過proc文件系統節點獲取,如文件/proc/module包含了已加載模塊的一個列表。

 

一個內核緩衝驅動

這一節展示一個執行I/O操作到內核內存塊(虛擬磁盤)的簡單驅動程序的實現。我們將討論爲使驅動程序能夠同時在WindowsLinux下工作所需要的各種組件,這樣它們所需要驅動組件的相似和不同之處也得到了突顯。驅動程序所管理的虛擬設備如圖4.0.所示,它由若干內核內存塊組成。應用程序可對虛擬設備進行I/O操作。驅動程序可以選擇某個內存塊和內存塊的偏移位置進行訪問。

 

Figure 4.0 A simple virtual device

 

需要的驅動組件

WindowsLinux驅動程序都將實現readwriteIOCTL驅動例程。每個操作系統所需要的例程如圖4.1.所示。驅動程序的名稱可隨意指定。圖4.1種不同操作系統的例程也可以被賦以相同的名稱,這裏只是根據平臺的慣用法來命名。

 

Figure 4.1 The Windows and Linux basic driver routines

 

驅動加載和卸載例程

Windows下,驅動加載例程DriverEntry中所執行的步驟是設置I/O分發例程,如圖4.1.1a所示。

 

Figure 4.1.1a Initialisation of a driver Object in the driver entry routine

 

Linux下,驅動加載例程RegisterDriver中所進行的是驅動主編號的註冊,如圖4.1.1bTagged文件操作的初始化,只針對GCC編譯器,如圖4.1.1b,是在對結構體fops的聲明中,當然這不是ANSI C的有效語法。編譯器將使用驅動程序實現的例程名稱初始化file_operation結構體(fops)中的各個不同的域。如open是結構體一個域的名稱而Open是驅動實現的一個例程,編譯器將Open函數指針賦給open域。


Figure 4.1.1b Registration of a driver major number in Linux

 

Linux驅動的卸載程序中,已註冊驅動必須進行反註冊,如圖4.1.1c

 

Figure 4.1.1c Driver major number deregistration in Linux

 

驅動全局結構

必須定義一個結構體來保存驅動全局數據,這些數據在驅動的各例程中被使用。對這個內存設備,同樣的結構使用在WindowsLinux驅動程序中,其定義如圖4.1.2


Figure 4.1.2 Structure used to store global data for generic driver

 

memoryBank是包含4個內存塊,每個塊爲1K大小的數組。currentBank表示當前選中的內存塊,offsets記錄了每個內存塊內部的偏移量。

 

添加設備例程

 

添加設備例程只針對WindowsLinux沒有添加設備例程,所有的初始化必須在驅動加載例程裏完成。WindowsaddDevice例程所執行的操作如圖4.1.3所示。在調用I/O管理器例程IoCreateDevice例程時,一個設備對象被創建。供應用程序使用來獲取驅動句柄的接口也被創建,這通過調用I/O管理器例程IoRegisterDeviceInterface。這個例程的一個參數是使用系統工具guidgen手動生成的GUIDWindows下,驅動和應用程序之間的不同數據交換方式在3.1.2.6節中有說明。驅動程序通過設置設備對象的flags(3.13.2關於設備對象的討論)來表明其要使用的數據交換方法。這裏例子中flags被設置成使驅動使用buffered I/O方式。內存設備使用的每個內存塊通過其中一個叫ExAllocatePool的內核內存分配例程來分配。這個內存從內核的非頁內存池中分配,這樣設備的內存總是存在於物理內存中。

 

Figure 4.1.3 Operations performed in the Windows driver’s add device routine

 

打開和關閉例程

 

Windows驅動的大部分初始化操作都已經在添加設備例程中完成,因此不需要在打開例程中做任何初始化。Linux下的打開例程如圖4.1.4a所示。首先,用於保存驅動全局數據的內存被分配,然後將文件結構體的private_data域指向該內存。之後內存設備所使用的內存塊通過和Windows下完全一樣的方式分配,只是內存分配函數的名字不同,Windows下是ExAllocatePoolLinux下是kmalloc

 

Figure 4.1.4a Operations performed in Linux’s generic driver open routine

 

Linux的關閉例程中,爲驅動全局數據和內存設備分配的內存被釋放,如圖4.1.4bWindows下,內存的釋放是在響應PnP移除消息的時候,這個在本節後面會有討論。

 

Figure 4.1.4b Operations performed in Linux’s generic driver close routine

 

讀和寫例程

Readwrite例程將數據傳送到或取自當前選中的內核內存塊。Windows下,讀例程的執行如圖4.1.5a所示。要讀取數據的長度值從IRPI/O棧位置(3.1.3節什麼是I/O stack location)獲取,該域名稱爲Parameters.Read.Length。所請求長度的數據將被從當前選中的內存塊(後面會討論應用程序通過驅動IOCTL例程選擇內存塊)中讀取,使用的是內核運行時例程RtlMoveMemory。RtlMoveMemory將數據從內存設備的內存空間搬移到I/O管理器爲buffered I/O分配的緩衝區,也就是IRPAssociatedIrp.SystemBuffer域。這個IRP算完成了,就通知I/O管理器驅動程序已完成IRP的處理,I/O管理器將IRP返回給其發起者。

 

Figure 4.1.5a Performing a read operation in the Windows driver

寫例程對上述內存搬移進行反操作,如圖4.1.5b

 

Figure 4.1.5b Performing a write operation in the Windows driver

 

Linux下,讀例程如圖4.1.5c所示。對驅動全局數據的引用從文件結構體的private_data域獲取,從全局數據中,又獲取到對memoryBank的引用。接着數據就從這個內存區被傳送到用戶空間,使用內核訪問用戶空間例程copy_to_user。

 

Figure 4.1.5c Performing a read operation in the Linux driver

 

寫例程執行和上面同樣的操作,只是這一次數據是從用戶空間傳送到內核空間,如圖4.1.5d

 

Figure 4.1.5d Performing a write operation in the Linux driver

 

設備控制例程

設備控制例程用來設置設備的各種狀態。應用程序使用win32例程DeviceIoControl來對驅動程序進行IOCTL調用。這個例程需要一個由驅動程序定義的IOCTL碼。一個IOCTL碼告訴驅動程序應用程序要執行的操作。在這個例子中,驅動實現IOCTL例程用來選擇當前內存塊號(current bank number)。驅動的IOCTL碼使用之前必須先被定義。WindowsIOCTL碼的定義如圖4.1.6aCTL_CODE宏用來定義一個設備的IOCTL[Oney, 99]。CTL_CODE的第一個參數是設備IDID數值範圍爲0-65535,其中0-32767爲系統預留,32768-65535的使用可自定義。所選的IOCTL碼必須和驅動的addDevice例程中爲例程IoCreateDevice指定的設備編碼一致(參見節4.1.3addDevice例程所做的事情)。第二個參數爲表示功能碼的12比特位長數值。02047Microsoft預留,因此功能碼應爲大於2047而小於2^12。這個通常用來表示哪個控制碼被定義,即將兩個IOCTL碼區分開,如圖4.1.6a。第三個參數值指定用於從用戶空間傳送參數到內核空間的方法,第四個參數表示應用程序對設備的訪問權限。


Figure 4.1.6a IOCTL code definition in Windows

 

Linux下,應用程序使用系統例程ioctl來對驅動進行IOCTL調用。IOCTL碼在文件Documentation/ioctl-numbers.txt中指定,可在Linux內核源碼樹中找到。用於試驗的驅動選擇一個未使用的號碼,目前是大於0xFF的值。這個驅動的IOCTL碼的定義如圖4.1.6b_IOWR表示數據將被傳送到或取自內核空間。其它的宏如_IO表示沒有任何參數,_IOW表示數據僅將從用戶空間被傳送到內核空間,最後的_IOR表示數據僅將從內核空間被傳送到用戶空間。以上的宏需要一個表示內核和用戶空間所需要交換數據的大小的值。據Rubini et al [Rubini et al, 01]建議,爲使驅動程序可移植性更好,這個值應被設置爲255(8比特),雖然依賴於當前架構的數值爲8-14比特位。第二個參數和Windows下的函數編號類似,8位寬,從0-255

 

Figure 4.1.6b IOCTL code definition in Windows

 

一旦IOCTL碼被選定,IOCTL例程就可以被定義。Windows下,IOCTL例程的定義如圖4.1.6c。兩個IOCTL碼被處理。第一個IOCTL_SELECT_BANK,設置當前內存塊號,第二個IOCTL_GET_VERSION_STRING,返回驅動版本字符串。從IOCTL例程返回的數據和readwrite請求返回的數據一樣被調用者處理。

 

Figure 4.1.6c IOCTL routine definition in Windows

 

Linux下IOCTL例程的定義如圖4.1.6d。對IOCTL碼的處理和Windows一樣,不同的只是語法上。數據的處理和read、write請求一樣。

 

Figure 4.1.6d IOCTL routine definition in Linux

 

PnP消息處理例程

Windows下,PnP消息在適當的時候被分發給驅動程序,如當設備被添加到系統或被從系統移除時。這些消息被驅動程序實現的PnP分發例程處理。Linux下,內核並沒有發送PnP消息給驅動程序,因此也就沒有PnP例程。Windows下PnP消息處理例程如圖4.1.7所示。這個例子中,內存設備驅動只處理其中一個PnP消息。移除設備的消息是在驅動程序被系統卸載時被髮送。這個時候,通過調用I/O管理器例程IoSetDeviceInterface禁用驅動程序的接口,驅動程序的功能設備對象和驅動程序分配的那些內存塊也一併被刪除。

 

Figure 4.1.7 PnP Message handler routine

 

驅動開發環境

爲到此爲止所討論的兩種操作系統,即Microsoft Windows和Linux開發驅動需要使用特定於每個平臺的一些軟件開發工具。Windows和Linux操作系統的內核都是使用C語言編寫,這使得爲兩個系統編寫的驅動程序也跟着使用C語言編寫。Windows支持使用面向對象的編程語言C++來編寫驅動程序,Linux卻沒有支持。

 

Windows驅動開發環境

Microsoft Windows是一個具有所有權的商用的操作系統,即它需要被購買來使用。針對Windows,存在若干商用的驅動程序開發環境。舉個例子,如NuMega DriverStudio™ Suit [Compuware, 01],帶有類庫和驅動構造嚮導以輔助驅動開發,同時還集成了一個調試器允許驅動代碼的調試。

 

Windows設備驅動開發包

Windows下驅動開發的標準途徑是從Microsoft獲取設備驅動開發包(DDK)和利用一些輔助工具進行開發。最新版的DDK可從MSDN得到(The latest version of the DDK is available to Microsoft Software Development Network (MSDN) subscribers)DDK包含開發驅動所需要的程序。DDK安裝程序會安裝一些批處理文件,這些批處理文件會建立一個外殼窗口使得可以爲Microsoft每個版本的操作系統開發驅動。這次對Windows驅動架構考查所使用的DDK 3590,具有爲Windows ME2000XP.NET開發驅動的環境。每個平臺都有兩個版本的開發環境,一種叫checked版,調試符號被添加到驅動代碼中,另外一種叫發佈版,這個版本的驅動程序沒有調試符號。發佈版開發環境也是驅動產品最後使用的編譯環境。

 

Windows驅動的Makefile

DDK外殼窗口打開後,一個簡單的build命令就可以編譯驅動程序。Makefile定義了用來生成驅動程序的源代碼文件。Makefile的條目在一個叫sources的文件中指定,放在build命令所處的當前目錄下。圖5.1.2顯示了用來生成一個簡單驅動程序的Makefile的格式。環境變量TARGETNAME指定了生成驅動的名稱。 這個例子中,驅動程序將叫mydrivers.sysWindows下的驅動都以.sys爲後綴。TARGETPATH指定驅動程序賴以生成的目標代碼文件。 目錄obj下有一個文件_objects.mac,定義了額外的目標文件路徑。Windows 2000中,checked版默認的目標文件路徑爲objchk_w2k,發佈版默認的目標文件路徑爲objfre_w2k。INCLUDES指定了編譯驅動所需要的包含文件路徑,SOURCES指定驅動賴以生成的驅動源碼文件。

 

Figure 5.1.2 A Makefile used for building a WDM driver with the Windows DDK

 

Windows DDK文檔和工具

Windows DDK包含組織良好的API文檔和驅動程序例子代碼。初學者可從這裏學到如何創建驅動程序。DDK還包含一些輔助驅動開發的實用工具程序。其中一個是設備樹應用程序,列出了當前所有已加載的在\\device命名空間以層級方式列出的驅動(3.1.2.4關於設備命名空間的討論),顯示每個驅動棧、驅動所實現的例程和驅動對象內存地址。Windows DDK提供的其它工具中有一個用來生成INF文件的叫geninf,它生成驅動程序安裝需要的INF文件,還有一個PnP驅動測試應用程序用來測試驅動是否支持PnP

 

Linux驅動開發環境

Linux驅動開發環境和Windows不同,Linux下沒有和Windows DDK對應的東西,也就是說內核創建者沒有提供Linux設備驅動開發包,而是將內核源碼對所有人公開。內核源碼的頭文件就是開發驅動所需要的所有東西。驅動程序使用GNU C編譯器,即GCC,它也被用來編譯應用程序。和Windows類似,通過Makefile文件指定驅動如何被編譯生成。

 

Linux驅動開發Makefile

一旦定義了Makefile,使用簡單的make命令來生成驅動。圖5.2.1顯示了一個用來生成叫mydriver的驅動程序的Makefile文件示例,源代碼文件爲mydriver.c。第一個條目是KERNELDIR,定義一個環境變量,指定了內核頭文件的位置。後面一行包含了當前內核的配置信息。在內核和驅動程序被編譯生成之前,外部定義的內核變量在.config文件中指定,該文件位於內核源碼樹的根目錄,這樣內核的頭文件可以使用這些信息。CFLAGS用來設置GCC編譯器額外的標誌,‘-O’ 打開代碼優化開關,‘-Wall’打印所有的代碼警告。‘all’節是make命令執行時默認會去檢查的節。一個目標叫mydriver,依賴於目標文件mydriver.omydriver.oGCC生成。環境變量LD指定用來生產最後的驅動模塊的GNU鏈接器。選項‘-r’指定輸出可被重定位,即裏面的內存內置應該是相對於某個基地址的偏移,這個基地址在編譯的時候是不知道的。‘^’&nbsp;是mydriver.o<span style="font-family:'宋體';">的別名,</span>‘ @’ 是mydriver的別名,也就是說,它要求鏈接器從mydriver.o目標文件生成可重定位的代碼然後生成輸出文件mydriver

 

Figure 5.2.1 Makefile used to build a driver in Linux

 

內核模塊管理程序如insmodlsmod分別用來將驅動程序加載到內核和查看當前已加載的所有內核模塊。

 

Linux驅動開發文檔

Linux內核源碼樹下有個叫“Documentation”的目錄,這個目錄下有一些關於Linux內核方面的文檔,但還是沒有Windows DDK文檔那樣完整和生動。Rubini et al[Rubini et al, 01]編寫Linux驅動書籍對設備驅動開發者來說是個更好的信息來源。Linux內核沒有自帶任何驅動程序例子,但有在實際環境中被使用的驅動程序源碼,這些代碼可以作爲開發新設備驅動的基礎。然而,這對設備驅動開發新手來說並不是一個好的引導素材。

 

驅動程序調試

每一個軟件部件在其開發週期內總是不時地需要調試,因爲總存在一些難以靠檢查源碼就能發現的晦澀的bug。對驅動程序來說更是如此。應用程序的bugs最壞情況下會是應用進程不穩定,而驅動程序中嚴重的bug會使整個系統不穩定。調試應用程序很直觀,即在調試器的幫助下,在感興趣的源代碼位置設置一箇中斷語句。這個因調試器而異。Windows下,使用Microsoft Visual Studio調試器,設置斷點只需要在源碼所在行點擊一下鼠標。DDD(Linux下的GUI調試器,使用了最流行的命令行調試器GDB)調試器也是一樣的操作。程序在調試模式運行時,遇到斷點程序會暫停執行使得可以單步跟蹤,即從那個地方開始的指令逐條執行並可觀察執行的效果。調試器中一般可以看到被調試程序中變量的內存地址和變量的值。到此爲止我們所討論的調試方式也適用於驅動程序的調試,某種程度上還適用於每種操作系統。

 

Windows下驅動調試

Windows下驅動程序的調試有若干不同的方法。最簡單的就是使用DbgPrint調試例程將消息打印到Windows調試器緩衝區。比如使用Windbg調試器時,可從調試器界面看到那些消息,否則需要一個特定的程序從調試器緩衝區接收那些消息,如SysInternals公司免費提供的DebugView程序[Russinovich, 01]。DbgBreakPoint例程在程序中設置一個斷點,當被執行時,系統停下來並將驅動執行代碼傳遞給系統調試器。Assert宏基於條件測試的結果,將驅動執行轉移到系統調試器。使用Microsoft內核調試器Windbg,需要兩個PC。第一個PC是驅動代碼的開發和調試機器,第二個PC通過串口連到開發驅動的PC。開發者使用第二個PC,通過串口控制檯連接到第一個PC,就能和在第一個PC上的調試器交互。NuMega DriverStudio ™ [Compuware, 01]提供的調試器允許驅動調試在單個PC內部,這個PC可以作爲驅動開發機器,並作爲應用調試器。它提供了一個console窗口,命令行可從這裏輸入以便進行控制。

 

Linux下驅動調試

和Windows一樣,Linux驅動調試可使用內核提供的調試例程printk,對應於Windows的DbgPrint例程。它和C的標準I/O例程printf類似,只是需要一個額外的參數來指定消息將被打印到的位置。內核調試器可作爲內核源碼的一個patch被獲取到。Linux內核調試器(kdb)的patch可從KDB項目頁面獲取[KDB, 02]。它允許標準調試器一樣的操作,即設置斷點、單步執行驅動代碼和觀察驅動內存。

 

總結

WindowsLinux是當今最爲普遍流行的操作系統。Windows的市場份額最大,Linux知名度則在不斷增長。硬件設備製造商每發佈一種新設備,都配備有一個能使新設備在Windows下使用的驅動程序。兩種操作系統的驅動架構有很多的不同,但也有一些類似的地方。

 

設備驅動架構

通過對兩種操作系統驅動架構的比較,可以看到Windows系統的架構更爲成熟。這並不意味着Windows架構提供更好的功能,而是它有一個定義更爲良好的的驅動模型讓驅動開發者去遵循。雖然驅動程序的編寫者可以忽視Windows驅動模型開發自己的驅動,但很少有驅動開發者這麼做。Linux下沒有正式定義的驅動模型。Linux驅動程序編寫者基於他們個人的設計來開發驅動。除非兩個驅動開發組合作來開發能一起工作的驅動程序,不同開發者開發的驅動程序在Linux系統下不能協同工作。Windows下,兩個或多個驅動開發組開發的驅動可以協同工作,只要他們都遵循WDM來構建驅動程序。Windows驅動架構支持PnP和電源管理,是通過在適當的時候將這些消息發送到實現了消息處理函數的驅動。目前的Linux驅動架構沒有提供這樣的機制。

 

設備驅動程序設計

設計驅動程序時應該對操作系統提供的輔助設施進行評估。Windows和Linux是兩個現代化的操作系統。它們提供了對數據結構如棧(stack)、隊列(queue)和自旋鎖(spin locks),還有完成硬件無關操作的硬件抽象層(HAL,Hardware Abstraction Layer)例程的實現。這使得驅動程序可以在不同的處理器架構下運行,如IA64 (Intel’s 64 bit platform) 和SPARC。兩種操作系統下的驅動程序都可以被分成多個模塊,然後以棧式結構堆疊,使用標準化的數據結果進行通信。Windows這種標準化的數據結構就是IRPLinux下則可是任何驅動程序自定義的結構,因爲操作系統沒有提供任何標準化的結構。

 

設備驅動程序實現

兩種操作系統下的驅動程序都由一系列各操作系統期望驅動程序實現的例程組成。包括標準I/O如讀寫設備的例程、發送I/O控制命令到設備的例程。兩種系統中,每個驅動程序都要實現一個驅動被加載時執行的例程和驅動被卸載時執行的例程,不同驅動程序的例程可使用相同的名稱,但一般來說每種操作系統都使用慣用的命名方式。Windows下設備驅動的的命名方式(設備接口)比當前Linux下設備驅動的命名方式更便捷。Linux使用GUID爲設備命名,相比Windows,驅動名稱衝突的現象在Linux下更易於出現。

 

驅動程序開發環境

Windows操作系統提供DDK,其中包含相關的文檔和開發工具,大大減少了開發驅動需要的學習時間。Linux下沒有DDK,因此剛開始時設備驅動開發者需要收集其他的一些資源來輔助驅動的開發。一旦花時間熟悉了兩種驅動開發環境後,開發者會發現創建Linux驅動程序比創建Windows驅動程序更容易,因爲所有的Linux內核源碼對他們是可見的。這使得驅動開發者能更深入到他們的驅動程序所依賴的內核代碼中去跟蹤解決驅動程序的問題。Windows,只有debug版二進制組件可用,裏面包含調試符號如函數名稱和變量名,但不如擁有操作系統源碼的用處那麼大。

 

結束語

驅動程序應該被設計成需要終端用戶很少的交換就能使用,並且應用程序可以訪問驅動的所有功能。第一點是Windows的一個要點,它支持了PnP。Linux是一個開源項目,它還在不斷地改進中。以後Linux的驅動架構很有可能像Windows驅動架構那樣正式化,如具有一個WDM那樣的驅動模型。隨着越來越多的個人和組織採用了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.




        </div>
            </div>

        </article>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章