最近在學習windows驅動設計,認真看了些教材後總結了我認爲驅動中都會涉及到,也最重要的概念,和大家分享。如果有說的不對的請大家留言指出。謝謝!
這裏主要是寫概念,代碼涉及的不多也不詳細,但是我會說出涉及到的API,詳細的使用細節大家可以自己動手搜搜。掌握下面的概念之後,看驅動開發的教材裏的代碼,或者理解教材裏說的內容應該就順利很多!
過濾驅動程序概括:
對於windows驅動程序設計來說,理論上,我們要做的就是創建設備對象(包括完成這個設備對象內部的功能、參數等),然後將這個設備對象綁定到我們要過濾的設備上。一個設備對應一個驅動對象,而一個驅動對象可以生成許多設備對象,這些設備對象是實現功能、完成任務的東西(我們在程序中反覆玩弄和折磨的就是他們)。
驅動的層次結構架構:
------------------------------------------------------------------------------------------------------------------------------------------------
上層應用(調用系統API,希望完成一些工作)
------------------------------------------------------------------------------------------------------------------------------------------------
IO控制器(解析上層請求,向下層發IRP包)
------------------------------------------------------------------------------------------------------------------------------------------------
N層過濾驅動(攔截IRP包,做預處理)
------------------------------------------------------------------------------------------------------------------------------------------------
。。。
------------------------------------------------------------------------------------------------------------------------------------------------
目標設備(被過濾設備或者原始設備或者真實設備或者物理設備。。。)(接收IRP包,並按包的指示工作)
------------------------------------------------------------------------------------------------------------------------------------------------
重要概念:
1.設備棧:DeviceStack
設備棧是什麼?裏面裝的是什麼東西?怎麼工作的?
在編寫過濾驅動的過程中,我們每生成一個設備對象(代表一個過濾驅動),就一定會把它綁定到我們要過濾的目標設備上。那麼如果把多個過濾驅動的對象綁定到同一個設備上是怎麼綁呢?系統怎麼管理他們呢?答案就是設備棧。原始的被綁定設備和往他上面綁的過濾設備們組合在一起就成了一個設備組。這個設備組會被系統管理成一個棧的結構,形成的就是設備棧。後綁定的設備原本希望綁最下面的目標設備,而實際上綁在了最外面,就像禮品包裝紙一樣,一層層往外裹。所以當有請求發給目標設備的時候,請求會按棧中的次序從棧頂(最外層)開始依次被棧中的過濾對象依次處理。最後發給目標設備。
相關的函數:
當新的設備想要加入設備棧(綁定目標設備)的時候,就會調用綁定函數:IoAttachDeviceToDeviceStackSafe,它第三個參數返回被綁定後的對象指針(其實根據上面的說明能看出來,這個這真就是棧頂)。在這個API的使用當中,大家會發現,一般把第三個參數保存到這個過濾驅動的設備擴展的AttachedToDeviceObject字段,那麼通過這個字段是不是可以把IRP依次傳遞下去?當然,因爲它代表當前過濾驅動下一層的設備。
綁定設備的三部曲:
第一步:創建設備(準備往其他設備上綁的設備),用IoCreateDevice,第一個參數是設備對象,過濾驅動中填的就是過濾設備。
第二步:爲創建的設備設置標誌位:把A綁定到B上,就要讓AB的標識位一致(這樣系統看起來,就以爲自己發送IRP到目標設備了),所以創建了A後,要設置一些重要的標識位。
第三步:綁定:調用綁定函數進行綁定,綁上目標設備棧的棧頂設備(這一步結束後,可以通過我們創建的設備對象flag設置設備已經啓動)
2.服務請求包:IRP和IRP棧
IRP
包裏面是什麼?怎麼產生?怎麼工作的?
包裏面存放了接收設備要完成請求的工作所需要的一切信息~~~很大很複雜的一個結構體。在WDK的wdm.h中可以找到他。大家在使用的時候會發現,一般都只涉及到一些常用字段。
當用戶程序在用戶層(Ring3)調用一些系統API,比如(ReadFile)的時候,這些函數的請求最終會被IO控制器接收,然後發出IRP,這個IRP會被向下發送,然後一次又一次的被各種設備處理然後發送給下一個設備。在這個過程中,IRP包又可以被中間的處理設備修改。
IRP棧:
那麼IRP棧中的內容是什麼?系統怎麼使用的這個棧進行工作的?
大家看一些教材的時候一定會發現在自己創建的設備過濾到IRP包,然後想對IRP包進行操作的時候,總是會訪問IRP棧。上一段中說了,一個IRP的生命中,有可能會敬禮許多段和接收到他的設備的愛情,在這個過程中,IRP中的一些字段難免會不斷被改變。而一個IRP棧就像是日記本,記錄了IRP的改變過程,所以棧頂的肯定是現在最新的IRP。這就是爲什麼。。。看下面的函數:
IoGetCurrentIrpStackLocation(irp),每當我們想獲得IRP的時候,就這樣調用。仔細看這個api中的單詞Current,就能猜到這個函數的意思就是訪問IRP棧頂、最新狀態的IRP(這個最新狀態的IRP是經過上層設備各種修改的IRP)。
3.下面說下IRP中存的數據
IRP攜帶數據是通過攜帶數據所在內存區域的指針來實現的。這樣的指針有三個:
UserBuffer,MDLAddress,SystemBuffer。不同的設備會用不同的指針去存要攜帶的數據。下面是三個指針的原理:
a.SystemBuffer是把用戶層空間中的緩衝中的數據複製到內核層空間中使用,這種方式效率很低。
b.UserBuffer是把應用層的緩衝地址直接傳遞進來,供內核層使用。優點是系統實現的很快,傳個地址就行,缺點:內核進程切換,當前的進程被切出去了,訪問會結束。爲什麼會這樣?內核空間是共享的,所以內核進程都用公共的空間,內核進程一旦切換,前一個進程遺留的數據肯定就被幹掉了~
c.MDL方式是彌補了上面這個缺點,也就是說這個內存描述符表通過添加一個表項,永久的把一段應用層空間和內核空間一段虛擬空間綁定在了一起。這樣相當於內核使用訪問自己空間的地址,其實是在不知不腳的訪問用戶空間中的內容。因爲這個訪問方式是用MDL實現和管理的,所以就算內核當前進程發生了切換啥的,只要MDL表在(這不廢話嗎?),就永遠可以訪問到用戶空間的那一段內容。而不是簡單的把用戶空間的一個指針作爲值暫時的存到內核空間的一個變量中。
4.IRP的傳遞
IRP在驅動設備的工作中起着全程指導的作用,他會在一系列的設備中被轉發,自己也會不斷改變,那麼大家經常遇到這個情況:對於一個過濾驅動來說,他攔截了IRP後,仔細的看了看這個IRP後,發現不是自己感興趣的,就打算原封不懂得扔給下一層設備處理。在一些教材的示例代碼中完成這功能的就一對好基友:
如下:
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(DeviceObject,Irp);
上面這對基友的工作機制可有講究了:IoCallDriver單獨工作時,效果是自動把irp棧中當前IRP包的下一個包發送到某個真實設備上。。。(因爲這個函數認爲之前的操作修改了IRP,並且把修改內容壓入到IRP棧中新的棧頂地址上,所以讓他來調用下一個設備工作,下一個設備收到的IRP也是目前設備處理後的新的IRP,從地址角度來說,就是當前IRP在IRP棧中的地址的下一個地址(新的棧頂)),而IoSkipCurrentIrpStackLocation寫在IoCallDriver前面的效果是讓IoCallDriver調用的下一層設備收到的IRP包就是本層設備受到的那個。這就完成了所謂的“跳過當前設備”的操作。【抱歉,雜家表達能力有限,是不是說的很暈?】
5.IRP請求的完成:
比較簡單,調用一個函數:IoCompleteRequest 例程表示調用者的已經完成了對指定I/O請求的所有處理操作,並且向I/O管理器返回指定的IRP報文。
6.當然,過濾設備可以綁定也可以解綁Unload:
Unload設備沒什麼複雜代碼,就是調用IoDeleteDeviceIRP。
技術相關更多文章猛擊:哇啦天堂論壇技術區