若干關於 file system driver stack
寫這個文章的初衷是想知道究竟一個讀寫檔案的irp都是怎樣被處理的.....
大家都知道這樣的一個讀寫檔案irp是發送給file system的driver的file system把這個irp交給了下層的device
這個device叫logical volume device,它由device的vbp裡面的realdevice指標指出(不一定就會是這個device,而應該是這個device所在的stack的最上層的device).那這個device是個什麼東西呢?它是怎麼來的呢?按照道理,這個irp應該被發送到disk的驅動去才對?那個這個device是disk的驅動建立的麼?如果不是,那中間都有些什麼步驟呢?
本文就是回答上面這些問題的.
用softice看看就知道file system轉寄的irp並不是vpb指向的那個device,而是另外一個叫volsnap的driver建立的device. 再跟蹤看看,volsnap建立的device是跟vpb的device在同一個stack裡面,而且volsnap建立的device的下一個device就是vpb的device.
好了.至少知道了確實file system driver要把irp轉寄到vpb指向的device去.那接下來呢?irp又會到哪裡?
再跟蹤下去.irp到了一個由partmgr.sys建立的device裡,看看這個partmgr...它是一個disk driver 的filter(多用註冊表,當初我在這裡花了好長時間才發現這個東西是一個filter),所以partmgr把這個irp轉寄到了disk driver.disk driver接著發給了acpi.sys,然後acpi.sys轉寄給了atapi.sys建立的一個device去,看看這個device的stack,已經是1了,irp就到盡頭了,接下來的就不是irp的流程了,而是所謂的port/miniport流程了.
大致的把整個的結構過了一次,裡面有很多的疑問,下面要一一解釋,最好是能用device tree看看這個過程,看看哪些是pdo哪些是fdo,看看每個device的stack值都是多少,自己模範下看看能不能瞭解irp的流程.
上面的整個irp的流程是很大的.頂層device的stack達到了8,首先你就會奇怪這麼龐大一個device stack是怎麼建立起來的?
先說最上面的部分
file system在受到一個mount的fs control的時候,建立了一個無名的device,並且儲存了vpb 的 realdevice所在的stack的最上層的device,以後所有的irp都轉寄到這個儲存的指標上面.注意這裡並沒有attach到那個device的stack上面,而是直接使用指標call driver的.這裡會有一個問題,在file system mount以後,再attach到volume device的stack上的device 收不到 file system傳遞過來的irp,至於這個問題怎麼解決,msdn裡面有說,把這個filter driver設定成啟動時載入,至於為什麼這樣作了就能行,這個在後面會有解釋.
在我的機器上,file system儲存的device是由volsnap建立的一個filter device,在這個filter device下面是vbp指向的device,它有名字叫HarddiskVolume1(名字不一定就是這個),這個device是一個pdo,注意它是一個pdo,按照常規 pdo的建立不是由系統主導的,而是由driver主導了,它一般不在adddevice裡面建立,而是由driver在需要的時候手動建立的.為什麼說到這個細節呢?因為我們沒有這個driver(名字叫ftdisk.sys)的原始碼,在反組譯碼的時候,明白了這個以後比較有目標,這個ftdisk.sys的代碼是非常簡單的,ida反組譯碼一下就發現harddiskv
olumeX這個device並沒有attach到什麼stack上面(也算是pdo的一般做法),但是這個pdo卻儲存了兩個指標,一個指向另外一個pdo(70h),一個(74h)指向這個pdo的stack的最頂的device,而且以後的irp它都發給了這個device,這個device也就是由partmgr建立的device了.
partmgr其實只是一個filter,掛接到disk driver上面的,那麼接下來的stack建立就比較簡單了.用device tree向前看,partmgr建立的是一個filter,它attach的device叫dr0,是一個fdo,它對應的pdo叫idedevicepotolo-3,這個部分的過程就根據的容易了,disk driver是有原始碼的,仔細看看就能明白其中的建立關係.看清楚了,這個pdo的stack值是1,所以irp到這裡就算是盡頭了.
再向下又是一個fdo,ideport(atapi.sys),它建立了上面的那個pdo,ideport我沒有詳細的反組譯碼分析,不過可以推測得出來,它對應得pdo是ide0channal0,而這個pdo又是由pciide建立的,你可能會注意到中間多了一個filter acpi.sys建立的一個device,也許你會注意到這個acpi.sys到處建立filter.
再往下就是pci,acpi_hal這些device了,他們的行為都很正常.不詳細說了(其實是我沒有詳細的研究,只有些想當然的想法,不敢拿出來說)
到此,回顧下整個device stack,可以看到這裡涉及到好多的device stack,irp並沒有按照一個stack 走到底.
最上面的是file system的device
|fs filter| <-比如是filemon
|fs volume| <-一般沒有名字,由filesystem在mount的時候建立
fs的volume儲存有新的stack(partmgr device的stack? 叫這個名字麼?)的最上層device的指標,irp轉移到這個stack
|volsnap 建立的無名device |
|ftcontrol建立的storage volume device|
這個device有儲存有另外一個stack頂層device的指標,irp再次轉移
|partmgr建立的無名filter device|
|disk建立的DR0 fdo |
|acpi建立的filter |
|atapi建立的pdo |
這個就是irp的全流程,它經曆了3個device stack.
呼呼
吐口氣吧.......
本來這個文章寫到這裡就算完成了....
但是我還有些心得要寫出來.這些屬於是無心插柳的結果.只是在我跟蹤上面這些結果的時候額外的收穫.
先看device tree裡面,pnp方式查看,你會注意到有很多的pdo屬於pnpmanager,但是他們卻沒有對應的fdo.很奇怪吧,再看這些pdo的名字,你會發現partmgr赫然在目.奇怪了,它怎麼有個fdo呢?
首先要明確的就是凡是有填充adddevice域的driver都會對應一個pdo,這個是必然的,因為adddevice傳遞進來的參數就有一個是pdo,還有一個事情就是在某種情況下,即使不填充adddevice域,pnpmanager還是會建立一個pdo.你所看到的大多少沒有fdo的pdo都是這樣來的.那什麼時候pnpmanager建立這些pdo呢?答案就在系統啟動的時候.
你也應該要知道,有些驅動並不是由ntoskrnl載入的,有很多的driver是由loader載入的,win2000 把這些叫做 boot driver.
ntldr載入必要的driver以後,轉移控制權給ntoskrnl的入口函數(kiinitsystem是這樣麼?),經過一定的步驟以後來到了ioinitsystem函數,這個函數作一些初始化以後開始了pnp系統,這裡ntoskrnl建立一個pnpmanager的root device,並且start了這個device,然後發送query bus relation,這個時候pnp manager讀取註冊表一一建立每個需要建立的service的pdo,構造device tree,然後調用每個boot driver的driver entry函數,然後是他的add device函數(實際情況比我上面描述的要複雜得多).
然後pnp就開始了眾所周知的enum 工作,這個是委託給worker線程完成的,大部分情況是使用workitem進行的,這個部分顯得非常的複雜,跟蹤調試非常的不方便(恕我也沒有完全的弄明白,先跳過去,以後有機會再詳細的討論這個部分).
跳過了xxx的步驟以後,pnp enum到了主板的南橋晶片(我的是ich4)裝置,建立對應的fdo(沒有名字,屬於pci這個driver),繼續enum,得到ich4裡面的mass storage controller,建立pdo也是屬於pci的,建立對應的fdo,pciide(中間有個acpi的filter加進來,主要進行電源管理),這個fdo再enum,建立兩個ide channal的pdo,再建立對應的fdo,ideport(由atapi提供,還是有acpi的filter加進來),ideport再enum機器上的硬碟pdo,繼續載入acpi的filter,載入硬碟的fdo,也就是disk.sys了,再載入upperfilter,partmgr.sys.
接下來的事情關鍵了,硬碟的fdo告訴pnp它的bus relations要更新(可以查看disk.sys的原始碼觀察這個部分進行的事情),然後pnp發送一個 query bus relations的irp給硬碟pdo所在的stack,這個stack的最上部分並不是硬碟的pdo,也不是acpi的filter,也不是硬碟的fdo,而是partmgr.sys這個driver,它接受到這個irp的時候,安裝一個complete routing,然後把irp向下傳遞,等到控制權再回到手上的時候,partmgr.sys作了一系列的事情,最最重要的就是它發送了一個IO Control irp到另外的一個driver.
接手這個irp的是ftdisk.sys這個driver(指basic volume的情況,如果是dynamic volume的話呢,接受的是dmio.sys這個driver),這個driver然後建立自己的pdo,並且把這個pdo關聯(不是attach)到了partmgr.sys所在的stack,實現了上面兩個stack之間的關聯.這個pdo也載入了自己的fdo,一個叫volsnap.sys提供.
這個irp完成以後,partmgr完成原來的query bus relations irp,可以看到disk.sys建立了若干個pdo,attach到了自己的fdo上面,再這以後,disk所在的stack的top device就不是partmgr.sys了,而是最後一個由disk建立的pdo.而partmgr在系統裡面註冊了通知訊息,它能在以後的volume change的時候得到通知,然後會發送相應的io control irp到ftdisk,然後ftdisk完成更新.
接下來事情又變得簡單了,file system driver載入,在遇到第一次訪問磁碟的時候,mount到由ftdisk建立的pdo上面去,再次實現兩個stack的關聯.
上面就是整個過程的大概流程.
明白了這個過程,要實現一個虛擬xx的就容易了.
說說daemon-tools一類的實現方式吧.
他們基本都提供一個bus driver(d343bus.sys),作為一個 boot driver 由ntldr載入到記憶體,ntoskrnl在適當的時候會調用它的driverentry以及adddevice函數,這兩個函數裡面分別載入了fdo和建立了自己的pdo,這個pdo的inf檔案裡面表示它要載入的driver是一個叫d343port.sys的driver,這個driver再在scsiport的協助下create了一個pdo.這個pdo返回的id很有趣,SCSI//CDRom,SCSI//RAW,這樣的話,windows就把它當作了一個cdrom來處理.再向上的fdo,filter都交給windows來操作了.
如果是一個虛擬硬碟呢?你馬上就反映過來了,報告pdo的id為Gendisk一類的就ok.
當然實際的實現遠不是這麼簡單的,bus driver,mini port driver都有很多要注意的地方.
(以上大部分屬於想象推測,沒有經過嚴格的認證,有錯的地方請包涵).
到這裡基本上這個文章又結束了.
也許有人要問這些東西是怎麼來的?當然是用softice + 2000代碼 + ida來的了,唯一要修改的就是要讓softice在所有的外部driver的driverentry調用前正常工作.這個方法在驅網的論壇上能找到http://www.driverdevelop.com/forum/viewthread.php?tid=66643