詳解Windows 2000/XP Class/Port/Miniport驅動模型
WebCrazy(http://webcrazy.yeah.net)
對於軟體複用,Microsoft Windows在使用者層提供了COM(Component Object Model),其實現了二進位層級的相容,使Windows開發進入了組件時代。COM的概念之一進程內組件,在使用者態使用動態聯結庫作為載體。所以不管在COM之前或是之後,DLL都是Windows下實現軟體複用的一個最主要的途徑。只是單純的DLL並未實現COM一樣的二進位相容的目的。但對於驅動開發人員而言,不同於使用者態的代碼,2000/XP並未有相應的像COM一樣的機制,實現核心態的二進位相容方式。實際上對於核心態也沒有必要提供一個像ole32.dll在使用者態實現的COM Library這樣的複雜機制(使用者態COM組件遵循的簡單的規則主要隱藏在COM Library的複雜之下)。
傳統的DLL複用模式同樣的適用於核心態代碼的開發,加上為了簡化同一類型裝置開發工作等目的,Windows 2000/XP形成了Class/Port/Miniport這樣的概念。我們知道對於Driver其提供的.sys檔案實際與dll沒有什麼實質上的變化。我們首先討論一下Class/Port/Miniport的概念與這種機制帶來的益處:
1、我們知道PC機硬體通常可分成許多大類,如儲存類裝置等等。各大類又分成許多小類。如儲存類裝置又可分為Cdrom、Tape、Harddisk等等。對於每一類裝置,提供一個class driver。這樣的driver實現某一類型裝置Driver與裝置無關的公用的功能集合。對於Driver開發人員而言,有了class driver的介入,僅僅需要實現一部分需要針對特定裝置的功能,IO管理器需要的功能要不由class driver直接完成,要不就由class driver通過調用底層的模組來實現。class driver通常由Microsoft提供,通常實現的是裝置無關的部分,這裡的與裝置無關主要是指不直接操作裝置硬體。而這底層的模組通常稱為miniport driver,後者由port driver提供服務(port driver類似於使用者態DLL,不過她還實現一些簡化裝置驅動編寫的功能,例如隱藏對IRP的繁瑣操作等等)。Class driver調用miniport driver的方式主要有Device IOCTL,class driver匯出的常式(就是DLL的概念)或是通過回呼函數等等。這樣分離出共有的部分,即可以實現所謂的複用,既減少了系統資源的佔用,又減輕了開發工作量。例如對於儲存類裝置Microsoft分別提供了供了cdrom.sys,tape.sys與disk.sys三個class driver。這三個driver之中當然也存在一定的共同點,畢竟他們都屬於儲存類裝置(底層硬體的區別通過miniport driver來區分)。Windows提供了classpnp.sys,三個driver將一些通用的常式置於這個sys中。這才更像是文章開頭介紹的DLL複用技術,而不是這裡介紹的class/port或是miniport driver。我在此段開頭定義class實際上特別提出了與裝置無關的共有功能的集合。即class driver實現的是與裝置硬體無關的部分,這樣做的好處是對於使用者態的客戶程式來說,不管對於scsi還是ide的裝置,我們都可以使用一樣的io操作來進行讀寫等操作,這裡面的主要不同由class driver分發不同的請求至相應的port/miniport driver來實現對使用者的透明。這才是最主要的class driver的目的。Microsoft還提供了一個miniclass的概念,在本文未加以敘述(具體請自行參考相關文檔)。
2、那麼class driver內部通常是如何工作的呢?class driver主要將系統定義的一些標準IO操作如IRP_MJ_READ等轉化成特定類別裝置的私人IOCTL,然後調用相應的miniport driver。如對於SCSI匯流排上的cdrom,cdrom會將IRP_MJ_READ轉化成一個SCSI命令調用系統提供的miniport driver,我底下會用一個具體的例子說明。既然class driver只是調用miniport driver,那為什麼又要引入port driver的概念呢?我們知道scsi匯流排控制器的類別很多,而如果每種控制器都從頭開始設計一個driver,那麼實現這樣的一個driver則將是一個多麼困難的事情。所以Microsoft引入了port driver的概念。例如由SCSI port driver來實現所有SCSI共同的內容,miniport只實現特定的SCSI控制器或是SCSI裝置等內容,共有的部分只要調用port driver提供的服務即可。如miniport driver aha154x.sys實現Adaptec AHA-154x系列SCSI控制器。而她的任務只是處理SRB((SCSI request block,含有Command descriptor block,即CDB,具體請參閱SCSI標準),其他的細節問題(如同步等等均由port driver scsiport.sys完成)。class/port/miniport模型在2000/XP中處處存在,如disk.sys是磁碟類driver,而pciidex.sys是IDE系統的port driver等等。miniport通過調用port driver匯出的函數等來實現其功能的,這兒其實又有dll複用的內容了。
需要指出的是class/port/miniport不是嚴格上的獨立體,也就是說並不都意味著這些有嚴格的區分,嚴格的由三個.sys來分別實現相應的功能。如atapi.sys則集合了port與miniport的功能,她用來服務基於ATAPI(使用SCSI命令)的IDE裝置。2000/XP中的網路裝置、網路通訊協定等網路驅動程式從嚴格的意義上講也可以說是class/port/miniport的驅動模型,雖然她們有一套自己的術語,如NDIS Intermediate Drivers、NDIS Protocol Drivers與NDIS Miniport Drivers等等(用於實現OSI的7層模型)。我更願意將ndis.sys理解成class/port的集合,而其餘NDIS Miniport Drivers則是miniport driver。
學習class/port/miniport最好的途徑就是通過學習原始碼。上面提到的disk.sys/cdrom.sys/tape.sys/classpnp.sys/aha154x.sys在DDK中Microsoft都提供了原始碼。class driver通常通過IOCTL調用miniport driver。port driver通常通過Driver相關的一塊記憶體地區來維護其對miniport driver的資訊的(如建立的DEVICE_OBJECT等等)。這塊記憶體地區是port driver相關的(也即同一子類的class裝置如disk.sys/cdrom.sys/tape.sys互不干擾),這兒我借用TLS(Thread Local Storage)的概念,TLS是線程相關的。在《 解讀Windows 2000/XP分層驅動模型》中我介紹了使客戶(這裡是miniport)可以使用IoAllocateDriverObjectExtension分配一塊記憶體,由目的driver(這裡是scsiport.sys)的DRIVER_OBJECT的DriverExtenion的ClientDriverExtension指向。不管是scsiport.sys或是ndis.sys都這樣來組織miniport專有資訊。
既然文頭提到COM,我這兒插入一個題外話,COM使用CoInitialize(Ex)來初始化COM Execute Context(Apartment),實際上CoInitialize(Ex)也使用TEB(線程環境塊)的ReservedForOle欄位指向在堆中分配的一塊記憶體用於支援COM執行環境。在XP SP1中這個欄位的位移值為0xf80。這樣的概念與IoAllocateDriverObjectExtension分配driver相關的記憶體應該是一樣的,只不過前者使用FS寄存器查當然線程的TEB中的Ole資訊,而後者則使用IoGetDriverObjectExtension來擷取DRIVER_OBJECT中的DriverExtenion資訊的ClientDriverExtension而已。
說了這麼多class/port/miniport的概念,實在仍是非常的抽象。我們通過一個例子來說明可能會更好的理解。這裡要用虛擬光碟機這一類別的工具來說明,我們知道虛擬光碟機軟體通常使用SCSI Miniport來實現,我們這兒通過cdrom.sys/scsiport.sys/fakecd.sys這樣一個class/port/miniport模型來學習。其中class driver與port driver都是由OS提供的。fakecd.sys是我實現的一個SCSI miniport driver。具體請參閱《FakeCD For Windows 2000/XP》。《FakeCD For Windows 2000/XP》介紹的Copystar FANTOM DVDROM、 Daemon Tools、Virtual CD與fakecd.sys都一樣,都是SCSI miniport port,都通過port driver scsiport.sys提供服務的。
下面我以我機子上(同時裝有一個物理光碟機、FakeCD與Daemon Tools)的情況來觀察:
kd> !devstack cdrom0
!DevObj !DrvObj !DevExt ObjectName
ffaa5020 /Driver/redbook ffaa50d8
> ffaa69e0 /Driver/Cdrom ffaa6a98 CdRom0
81340d78 /Driver/ACPI 8135d978 00000063
813418e8 /Driver/atapi 813419a0 IdeDeviceP1T0L0-e
!DevNode 812fd700 :
DeviceInst is "IDE/CdRomMITSUMI_CD-ROM_SR243T___________________L02G____/5&18b54618&0&0.0.0"
ServiceName is "cdrom"
kd> !devstack cdrom1
!DevObj !DrvObj !DevExt ObjectName
ff8bfa60 /Driver/redbook ff8bfb18
> ff8bf030 /Driver/Cdrom ff8bf0e8 CdRom1
8133b030 /Driver/Stlth317 8133b0e8 Stlth3171Port0Path0Target0Lun0
!DevNode 8133b850 :
DeviceInst is "SCSI/CdRom&Ven_V386&Prod_STEALTH_DVD&Rev_1.0h/1&2afd7d61&0&000"
ServiceName is "cdrom"
kd> !devstack cdrom6
!DevObj !DrvObj !DevExt ObjectName
> ff5fbac8 /Driver/Cdrom ff5fbb80 CdRom6
ff4c9a38 /Driver/FakeCD ff4c9af0 FakeCD1Port3Path0Target0Lun0
!DevNode ff9c68a0 :
DeviceInst is "SCSI/CdRom&Ven_WebCrazy&Prod_WebCrazy_FakeCD&Rev_1.00/1&1843ccbc&0&000"
ServiceName is "cdrom"
這裡windbg列出Cdrom0、Cdrom1與Cdrom6分別用於服務物理光碟機與Daemon Tools與FakeCD的虛擬光碟機。他們都是由/Driver/Cdrom(即class driver cdrom.sys)驅動建立的對象,且位於驅動棧的頂層(redbook只是一個Filter driver用於實現有關Audio的功能,這裡不加以考慮),這樣層現給IO管理器的只是一個“CDROM”,而不管底層具體是物理光碟機(位於IDE匯流排上),還是虛擬光碟機(位於虛擬SCSI匯流排上)。這即是class driver的作用,實際上這兒與檔案系統的互動也由class driver實現,所以FakeCD也不用考慮File System,也就是說對於ISO檔案等的格式我在FakeCD中沒有一行代碼。這點很多朋友來信詢問,在此一併予以回答。
共同實現port/miniport功能的atapi.sys的Cdrom0用於服務物理光碟機(ATAPI IDE),這裡也就比較容易理解了。對於虛擬SCSI匯流排的光碟機如Cdrom1,Cdrom6,裝置棧底層的miniport driver,由port driver scsiport.sys的提供服務,換句話說這兒port driver只是提供一個環境(正像ole32.dll的作用對於COM一樣)。cdrom.sys轉換標準IRP為SCSI的SRB(CDB)然後使用IOCTL調用下層的Miniport完成IO請求。實際上miniport一般調用port driver提供的常式來產生Device對象,如scsiport.sys提供ScsiPortInitialize、所以port driver總知道自己管理的裝置資訊。實際上port driver在2000/XP下一般都會向miniport driver開發人員隱藏使用Device Object,IRP等。而由miniport driver提供callback函數來完成操作,簡化開發人員的工作。
讓我們繼續看看Fakecd.sys驅動對象的內容:
kd> !drvobj fakecd 2
Driver object (ff3d5208) is for:
/Driver/FakeCD
DriverEntry: f9a50466
DriverStartIo: f94930c4 SCSIPORT!ScsiPortStartIo
DriverUnload: f949d3e0 SCSIPORT!ScsiPortUnload
Dispatch routines:
[00] IRP_MJ_CREATE f9490436 SCSIPORT!ScsiPortGlobalDispatch
[01] IRP_MJ_CREATE_NAMED_PIPE 804f986f nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE f9490436 SCSIPORT!ScsiPortGlobalDispatch
[03] IRP_MJ_READ 804f986f nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE 804f986f nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA 804f986f nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA 804f986f nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS 804f986f nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL 804f986f nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL 804f986f nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL f9490436 SCSIPORT!ScsiPortGlobalDispatch
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL ffa408e8 +0xffa408e8
[10] IRP_MJ_SHUTDOWN 804f986f nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL 804f986f nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP 804f986f nt!IopInvalidDeviceRequest
[13] IRP_MJ_CREATE_MAILSLOT 804f986f nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY 804f986f nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY 804f986f nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER f9490436 SCSIPORT!ScsiPortGlobalDispatch
[17] IRP_MJ_SYSTEM_CONTROL f9490436 SCSIPORT!ScsiPortGlobalDispatch
[18] IRP_MJ_DEVICE_CHANGE 804f986f nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA 804f986f nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA 804f986f nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP f9a503ba fakecd!FakeCDPnp
非常明顯對於SCSI miniport提供的driver入口,不管是StartIo,Unload還是IRP Dispatch函數都是由scsiport.sys提供的(為了提供充分的靈活,IRP_MJ_PNP入口被fakecd.sys miniport driver替換了用於實現動態實現CDROM個數的增減)。
本文只是從應用的角度討論了class/port/miniport的模型,對於這個模型的理解以及如何?類似的機制實際上只是一個最最為基礎的軟體複用的概念(當然加入了編寫裝置驅動的一些特色,如與硬體裝置的耦合考慮等)。這樣一個模型相對於使用者態的COM實現二進位層級的複用技術相對簡單的多。只是因為他們位於核心態,編寫容易Blue Screen,才被大家認識的不夠,本文只是希望為你做一個引導而已,在我看來,實在是沒有什麼特別的內容。