閱讀290 返回首頁    go 技術社區[雲棲]


Linux 與 Windows 的設備驅動模型對比:架構、API 和開發環境比較

名詞縮寫:

  • API 應用程序接口Application Program Interface
  • ABI 應用係統二進製接口Application Binary Interface

設備驅動是操作係統的一部分,它能夠通過一些特定的編程接口便於硬件設備的使用,這樣軟件就可以控製並且運行那些設備了。因為每個驅動都對應不同的操作係統,所以你就需要不同的 Linux、Windows 或 Unix 設備驅動,以便能夠在不同的計算機上使用你的設備。這就是為什麼當你雇傭一個驅動開發者或者選擇一個研發服務商提供者的時候,查看他們為各種操作係統平台開發驅動的經驗是非常重要的。

驅動開發的第一步是理解每個操作係統處理它的驅動的不同方式、底層驅動模型、它使用的架構、以及可用的開發工具。例如,Linux 驅動程序模型就與 Windows 非常不同。雖然 Windows 提倡驅動程序開發和操作係統開發分別進行,並通過一組 ABI 調用來結合驅動程序和操作係統,但是 Linux 設備驅動程序開發不依賴任何穩定的 ABI 或 API,所以它的驅動代碼並沒有被納入內核中。每一種模型都有自己的優點和缺點,但是如果你想為你的設備提供全麵支持,那麼重要的是要全麵的了解它們。

在本文中,我們將比較 Windows 和 Linux 設備驅動程序,探索不同的架構,API,構建開發和分發,希望讓您比較深入的理解如何開始為每一個操作係統編寫設備驅動程序。

1. 設備驅動架構

Windows 設備驅動程序的體係結構和 Linux 中使用的不同,它們各有優缺點。差異主要受以下原因的影響:Windows 是閉源操作係統,而 Linux 是開源操作係統。比較 Linux 和 Windows 設備驅動程序架構將幫助我們理解 Windows 和 Linux 驅動程序背後的核心差異。

1.1. Windows 驅動架構

雖然 Linux 內核分發時帶著 Linux 驅動,而 Windows 內核則不包括設備驅動程序。與之不同的是,現代 Windows 設備驅動程序編寫使用 Windows 驅動模型(WDM),這是一種完全支持即插即用和電源管理的模型,所以可以根據需要加載和卸載驅動程序。

處理來自應用的請求,是由 Windows 內核的中被稱為 I/O 管理器的部分來完成的。I/O 管理器的作用是是轉換這些請求到I/O 請求數據包IO Request Packets(IRP),IRP 可以被用來在驅動層識別請求並且傳輸數據。

Windows 驅動模型 WDM 提供三種驅動, 它們形成了三個層:

  • 過濾Filter驅動提供關於 IRP 的可選附加處理。
  • 功能Function驅動是實現接口和每個設備通信的主要驅動。
  • 總線Bus驅動服務不同的配適器和不同的總線控製器,來實現主機模式控製設備。

一個 IRP 通過這些層就像它們經過 I/O 管理器到達底層硬件那樣。每個層能夠獨立的處理一個 IRP 並且把它們送回 I/O 管理器。在硬件底層中有硬件抽象層(HAL),它提供一個通用的接口到物理設備。

1.2. Linux 驅動架構

相比於 Windows 設備驅動,Linux 設備驅動架構根本性的不同就是 Linux 沒有一個標準的驅動模型也沒有一個幹淨分隔的層。每一個設備驅動都被當做一個能夠自動的從內核中加載和卸載的模塊來實現。Linux 為即插即用設備和電源管理設備提供一些方式,以便那些驅動可以使用它們來正確地管理這些設備,但這並不是必須的。

模式輸出那些它們提供的函數,並通過調用這些函數和傳入隨意定義的數據結構來溝通。請求來自文件係統或網絡層的用戶應用,並被轉化為需要的數據結構。模塊能夠按層堆疊,在一個模塊進行處理之後,另外一個再處理,有些模塊提供了對一類設備的公共調用接口,例如 USB 設備。

Linux 設備驅動程序支持三種設備:

  • 實現一個字節流接口的字符Character設備。
  • 用於存放文件係統和處理多字節數據塊 IO 的塊Block設備。
  • 用於通過網絡傳輸數據包的網絡Network接口。

Linux 也有一個硬件抽象層(HAL),它實際扮演了物理硬件的設備驅動接口。

2. 設備驅動 API

Linux 和 Windows 驅動 API 都屬於事件驅動類型:隻有當某些事件發生的時候,驅動代碼才執行——當用戶的應用程序希望從設備獲取一些東西,或者當設備有某些請求需要告知操作係統。

2.1. 初始化

在 Windows 上,驅動被表示為 DriverObject 結構,它在 DriverEntry 函數的執行過程中被初始化。這些入口點也注冊一些回調函數,用來響應設備的添加和移除、驅動卸載和處理新進入的 IRP。當一個設備連接的時候,Windows 創建一個設備對象,這個設備對象在設備驅動後麵處理所有應用請求。

相比於 Windows,Linux 設備驅動生命周期由內核模塊的 module_init 和module_exit 函數負責管理,它們分別用於模塊的加載和卸載。它們負責注冊模塊來通過使用內核接口來處理設備的請求。這個模塊需要創建一個設備文件(或者一個網絡接口),為其所希望管理的設備指定一個數字識別號,並注冊一些當用戶與設備文件交互的時候所使用的回調函數。

2.2. 命名和聲明設備

在 Windows 上注冊設備

Windows 設備驅動在新連接設備時是由回調函數 AddDevice 通知的。它接下來就去創建一個設備對象device object,用於識別該設備的特定的驅動實例。取決於驅動的類型,設備對象可以是物理設備對象Physical Device Object(PDO),功能設備對象Function Device Object(FDO),或者過濾設備對象Filter Device Object(FIDO)。設備對象能夠堆疊,PDO 在底層。

設備對象在這個設備連接在計算機期間一直存在。DeviceExtension 結構能夠被用於關聯到一個設備對象的全局數據。

設備對象可以有如下形式的名字 \Device\DeviceName,這被係統用來識別和定位它們。應用可以使用 CreateFile API 函數來打開一個有上述名字的文件,獲得一個可以用於和設備交互的句柄。

然而,通常隻有 PDO 有自己的名字。未命名的設備能夠通過設備級接口來訪問。設備驅動注冊一個或多個接口,以 128 位全局唯一標識符(GUID)來標示它們。用戶應用能夠使用已知的 GUID 來獲取一個設備的句柄。

在 Linux 上注冊設備

在 Linux 平台上,用戶應用通過文件係統入口訪問設備,它通常位於 /dev 目錄。在模塊初始化的時候,它通過調用內核函數 register_chrdev 創建了所有需要的入口。應用可以發起 open 係統調用來獲取一個文件描述符來與設備進行交互。這個調用後來被發送到回調函數,這個調用(以及將來對該返回的文件描述符的進一步調用,例如readwrite 或close)會被分配到由該模塊安裝到 file_operations 或者block_device_operations這樣的數據結構中的回調函數。

設備驅動模塊負責分配和保持任何需要用於操作的數據結構。傳送進文件係統回調函數的file 結構有一個 private_data 字段,它可以被用來存放指向具體驅動數據的指針。塊設備和網絡接口 API 也提供類似的字段。

雖然應用使用文件係統的節點來定位設備,但是 Linux 在內部使用一個主設備號major numbers和次設備號minor numbers的概念來識別設備及其驅動。主設備號被用來識別設備驅動,而次設備號由驅動使用來識別它所管理的設備。驅動為了去管理一個或多個固定的主設備號,必須首先注冊自己或者讓係統來分配未使用的設備號給它。

目前,Linux 為主次設備對major-minor pairs使用一個 32 位的值,其中 12 位分配主設備號,並允許多達 4096 個不同的設備。主次設備對對於字符設備和塊設備是不同的,所以一個字符設備和一個塊設備能使用相同的設備對而不導致衝突。網絡接口是通過像 eth0 的符號名來識別,這些又是區別於主次設備的字符設備和塊設備的。

2.3. 交換數據

Linux 和 Windows 都支持在用戶級應用程序和內核級驅動程序之間傳輸數據的三種方式:

  • 緩衝型輸入輸出Buffered Input-Output它使用由內核管理的緩衝區。對於寫操作,內核從用戶空間緩衝區中拷貝數據到內核分配的緩衝區,並且把它傳送到設備驅動中。讀操作也一樣,由內核將數據從內核緩衝區中拷貝到應用提供的緩衝區中。
  • 直接型輸入輸出Direct Input-Output 它不使用拷貝功能。代替它的是,內核在物理內存中釘死一塊用戶分配的緩衝區以便它可以一直留在那裏,以便在數據傳輸過程中不被交換出去。
  • 內存映射Memory mapping 它也能夠由內核管理,這樣內核和用戶空間應用就能夠通過不同的地址訪問同樣的內存頁。
Windows 上的驅動程序 I/O 模式

支持緩衝型 I/O 是 WDM 的內置功能。緩衝區能夠被設備驅動通過在 IRP 結構中的AssociatedIrp.SystemBuffer 字段訪問。當需要和用戶空間通訊的時候,驅動隻需從這個緩衝區中進行讀寫操作。

Windows 上的直接 I/O 由內存描述符列表memory descriptor lists(MDL)介導。這種半透明的結構是通過在 IRP 中的 MdlAddress 字段來訪問的。它們被用來定位由用戶應用程序分配的緩衝區的物理地址,並在 I/O 請求期間釘死不動。

在 Windows 上進行數據傳輸的第三個選項稱為 METHOD_NEITHER。 在這種情況下,內核需要傳送用戶空間的輸入輸出緩衝區的虛擬地址給驅動,而不需要確定它們有效或者保證它們映射到一個可以由設備驅動訪問的物理內存地址。設備驅動負責處理這些數據傳輸的細節。

Linux 上的驅動程序 I/O 模式

Linux 提供許多函數例如,clear_usercopy_to_userstrncpy_from_user 和一些其它的用來在內核和用戶內存之間進行緩衝區數據傳輸的函數。這些函數保證了指向數據緩存區指針的有效,並且通過在內存區域之間安全地拷貝數據緩衝區來處理數據傳輸的所有細節。

然而,塊設備的驅動對已知大小的整個數據塊進行操作,它可以在內核和用戶地址區域之間被快速移動而不需要拷貝它們。這種情況是由 Linux 內核來自動處理所有的塊設備驅動。塊請求隊列處理傳送數據塊而不用多餘的拷貝,而 Linux 係統調用接口來轉換文件係統請求到塊請求中。

最終,設備驅動能夠從內核地址區域分配一些存儲頁麵(不可交換的)並且使用remap_pfn_range 函數來直接映射這些頁麵到用戶進程的地址空間。然後應用能獲取這些緩衝區的虛擬地址並且使用它來和設備驅動交流。

3. 設備驅動開發環境

3.1. 設備驅動框架

Windows 驅動程序工具包

Windows 是一個閉源操作係統。Microsoft 提供 Windows 驅動程序工具包以方便非 Microsoft 供應商開發 Windows 設備驅動。工具包中包含開發、調試、檢驗和打包 Windows 設備驅動等所需的所有內容。

Windows 驅動模型Windows Driver Model(WDM)為設備驅動定義了一個幹淨的接口框架。Windows 保持這些接口的源代碼和二進製的兼容性。編譯好的 WDM 驅動通常是前向兼容性:也就是說,一個較舊的驅動能夠在沒有重新編譯的情況下在較新的係統上運行,但是它當然不能夠訪問係統提供的新功能。但是,驅動不保證後向兼容性。

Linux 源代碼

和 Windows 相對比,Linux 是一個開源操作係統,因此 Linux 的整個源代碼是用於驅動開發的 SDK。沒有驅動設備的正式框架,但是 Linux 內核包含許多提供了如驅動注冊這樣的通用服務的子係統。這些子係統的接口在內核頭文件中描述。

盡管 Linux 有定義接口,但這些接口在設計上並不穩定。Linux 不提供有關前向和後向兼容的任何保證。設備驅動對於不同的內核版本需要重新編譯。沒有穩定性的保證可以讓 Linux 內核進行快速開發,因為開發人員不必去支持舊的接口,並且能夠使用最好的方法解決手頭的這些問題。

當為 Linux 寫樹內in-tree(指當前 Linux 內核開發主幹)驅動程序時,這種不斷變化的環境不會造成任何問題,因為它們作為內核源代碼的一部分,與內核本身同步更新。然而,閉源驅動必須單獨開發,並且在樹外out-of-tree,必須維護它們以支持不同的內核版本。因此,Linux 鼓勵設備驅動程序開發人員在樹內維護他們的驅動。

3.2. 為設備驅動構建係統

Windows 驅動程序工具包為 Microsoft Visual Studio 添加了驅動開發支持,並包括用來構建驅動程序代碼的編譯器。開發 Windows 設備驅動程序與在 IDE 中開發用戶空間應用程序沒有太大的區別。Microsoft 提供了一個企業 Windows 驅動程序工具包,提供了類似於 Linux 命令行的構建環境。

Linux 使用 Makefile 作為樹內和樹外係統設備驅動程序的構建係統。Linux 構建係統非常發達,通常是一個設備驅動程序隻需要少數行就產生一個可工作的二進製代碼。開發人員可以使用任何 IDE,隻要它可以處理 Linux 源代碼庫和運行 make ,他們也可以很容易地從終端手動編譯驅動程序。

3.3. 文檔支持

Windows 對於驅動程序的開發有良好的文檔支持。Windows 驅動程序工具包包括文檔和示例驅動程序代碼,通過 MSDN 可獲得關於內核接口的大量信息,並存在大量的有關驅動程序開發和 Windows 底層的參考和指南。

Linux 文檔不是描述性的,但整個 Linux 源代碼可供驅動開發人員使用緩解了這一問題。源代碼樹中的 Documentation 目錄描述了一些 Linux 的子係統,但是有幾本書介紹了關於 Linux 設備驅動程序開發和 Linux 內核概覽,它們更詳細。

Linux 沒有提供設備驅動程序的指定樣本,但現有生產級驅動程序的源代碼可用,可以用作開發新設備驅動程序的參考。

3.4. 調試支持

Linux 和 Windows 都有可用於追蹤調試驅動程序代碼的日誌機製。在 Windows 上將使用 DbgPrint 函數,而在 Linux 上使用的函數稱為 printk。然而,並不是每個問題都可以通過隻使用日誌記錄和源代碼來解決。有時斷點更有用,因為它們允許檢查驅動代碼的動態行為。交互式調試對於研究崩潰的原因也是必不可少的。

Windows 通過其內核級調試器 WinDbg 支持交互式調試。這需要通過一個串行端口連接兩台機器:一台計算機運行被調試的內核,另一台運行調試器和控製被調試的操作係統。Windows 驅動程序工具包包括 Windows 內核的調試符號,因此 Windows 的數據結構將在調試器中部分可見。

Linux 還支持通過 KDB 和 KGDB 進行的交互式調試。調試支持可以內置到內核,並可在啟動時啟用。之後,可以直接通過物理鍵盤調試係統,或通過串行端口從另一台計算機連接到它。KDB 提供了一個簡單的命令行界麵,這是唯一的在同一台機器上來調試內核的方法。然而,KDB 缺乏源代碼級調試支持。KGDB 通過串行端口提供了一個更複雜的接口。它允許使用像 GDB 這樣標準的應用程序調試器來調試 Linux 內核,就像任何其它用戶空間應用程序一樣。

4. 設備驅動分發

4.1. 安裝設備驅動

在 Windows 上安裝的驅動程序,是由被稱為為 INF 的文本文件描述的,通常存儲在C:\Windows\INF 目錄中。這些文件由驅動供應商提供,並且定義哪些設備由該驅動程序服務,哪裏可以找到驅動程序的二進製文件,和驅動程序的版本等。

當一個新設備插入計算機時,Windows 通過查看已經安裝的驅動程序並且選擇適當的一個加載。當設備被移除的時候,驅動會自動卸載它。

在 Linux 上,一些驅動被構建到內核中並且保持永久的加載。非必要的驅動被構建為內核模塊,它們通常是存儲在 /lib/modules/kernel-version 目錄中。這個目錄還包含各種配置文件,如 modules.dep,用於描述內核模塊之間的依賴關係。

雖然 Linux 內核可以在自身啟動時加載一些模塊,但通常模塊加載由用戶空間應用程序監督。例如,init 進程可能在係統初始化期間加載一些模塊,udev 守護程序負責跟蹤新插入的設備並為它們加載適當的模塊。

4.2. 更新設備驅動

Windows 為設備驅動程序提供了穩定的二進製接口,因此在某些情況下,無需與係統一起更新驅動程序二進製文件。任何必要的更新由 Windows Update 服務處理,它負責定位、下載和安裝適用於係統的最新版本的驅動程序。

然而,Linux 不提供穩定的二進製接口,因此有必要在每次內核更新時重新編譯和更新所有必需的設備驅動程序。顯然,內置在內核中的設備驅動程序會自動更新,但是樹外模塊會產生輕微的問題。 維護最新的模塊二進製文件的任務通常用 DKMS 來解決:這是一個當安裝新的內核版本時自動重建所有注冊的內核模塊的服務。

4.3. 安全方麵的考慮

所有 Windows 設備驅動程序在 Windows 加載它們之前必須被數字簽名。在開發期間可以使用自簽名證書,但是分發給終端用戶的驅動程序包必須使用 Microsoft 信任的有效證書進行簽名。供應商可以從 Microsoft 授權的任何受信任的證書頒發機構獲取軟件出版商證書Software Publisher Certificate。然後,此證書由 Microsoft 交叉簽名,並且生成的交叉證書用於在發行之前簽署驅動程序包。

Linux 內核也能配置為在內核模塊被加載前校驗簽名,並禁止不可信的內核模塊。被內核所信任的公鑰集在構建時是固定的,並且是完全可配置的。由內核執行的檢查,這個檢查嚴格性在構建時也是可配置的,範圍從簡單地為不可信模塊發出警告,到拒絕加載有效性可疑的任何東西。

5. 結論

如上所示,Windows 和 Linux 設備驅動程序基礎設施有一些共同點,例如調用 API 的方法,但更多的細節是相當不同的。最突出的差異源於 Windows 是由商業公司開發的封閉源操作係統這個事實。這使得 Windows 上有好的、文檔化的、穩定的驅動 ABI 和正式框架,而在 Linux 上,更多的是源代碼做了一個有益的補充。文檔支持也在 Windows 環境中更加發達,因為 Microsoft 具有維護它所需的資源。

另一方麵,Linux 不會使用框架來限製設備驅動程序開發人員,並且內核和產品級設備驅動程序的源代碼可以在需要的時候有所幫助。缺乏接口穩定性也有其作用,因為它意味著最新的設備驅動程序總是使用最新的接口,內核本身承載較小的後向兼容性負擔,這帶來了更幹淨的代碼。

了解這些差異以及每個係統的具體情況是為您的設備提供有效的驅動程序開發和支持的關鍵的第一步。我們希望這篇文章對 Windows 和 Linux 設備驅動程序開發做的對比,有助於您理解它們,並在設備驅動程序開發過程的研究中,將此作為一個偉大的起點。


原文發布時間為:2017-11-09

本文來自雲棲社區合作夥伴“Linux中國”

最後更新:2017-06-06 07:35:04

  上一篇:go  《正則表達式經典實例(第2版)》——1.2 使用正則表達式進行查找和替換
  下一篇:go  怎樣用 Tar 和 OpenSSL 給文件和目錄加密及解密