RtlInitUnicodeString函數的作用是計算Unicode字串的大小並且填充UNICODE_STRING結構,一般來說, Unicode字串都是在代碼中靜態定義的,並且在運行中保持不變,所以在連結的時候就把UNICODE_STRING結構給填好是完全可能的並且是很 容易的,這樣更容易理解、 更節省空間的(省去8位元組的UNICODE_STRING結構、最多3位元組的對齊空間以及至少14位元組調用RtlInitUnicodeString的代 碼)。這就是我為什麼不喜歡以上代碼的原因,我經常使用CCOUNTED_UNICODE_STRING宏來完成它,這樣上面的代碼就可以用2行來完成:
CCOUNTED_UNICODE_STRING "//Device//DevName", usDeviceName, 4
CCOUNTED_UNICODE_STRING "//??//DevName", usSymbolicLinkName, 4
如果你認同我的做法的話,也可以在自己的驅動程式中這樣定義驅動名稱和符號串連名稱:
.const
CCOUNTED_UNICODE_STRING "//Device//devVirtToPhys", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "//??//slVirtToPhys", g_usSymbolicLinkName, 4
(註:原作者的宏在處理英文的Unicode字串的時候是不錯的,但是中文字串就不行了,所以如果用到中文串,還是乖乖地動態轉換最方便,常用的方法 是先用RtlInitAnsiString函數產生一個ANSI_STRING結構,再用RtlAnsiStringToUnicodeString函數 將ANSI_STRING轉換到UNICODE_STRING即可,把這兩句寫成一個子程式或者宏的話,使用起來也是很方便的)。
在早些的Windows NT版本中,對象管理器中的"/??"目錄是沒有的,所以在那種情況下使用要將"/??"改為"/DosDevices",這種用法在後續的 Windows版本中也可以使用。為了向前相容,系統在根目錄下建立了一個"/DosDevices"串連,直接指向"/??"目錄。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
KeInitializeSpinLock
i)自旋鎖(SpinLock)
驅動程式可以在初始化時調用KeInitializeSpinLock建立該對象。在任何程式碼片段訪問被保護的資料之前,先調用 KeAcquireSpinLock試圖獲得該對象的所有權,如果成功,該段代碼被系統提升至DISPATCH_LEVEL,進行資料訪問。訪問完畢後須 調用KeRelease SpinLock釋放所有權,運行層級也被恢複。此方法只適用於同步運行層級小於等於DISP ATCH_LEVEL的代碼,主要用於多CPU的情形。此外,還有一種中斷自旋鎖用於與中斷處理過程同步,可以將較低層級的代碼提升到需要與之同步的中斷 DIRQL。
ii)控制器(Controller)
該對象主要用於同步一個驅動程式中的多個裝置,保證它們能順序地訪問特定的代碼或資料。該對象在驅動程式初始化調用 IoCreateController被建立。裝置在StartIo過程中調用IoAllocateController請求獲得Controller對 象的獨佔權。使用完後調用IoFreeController釋放。驅動程式停止時調用IoDeleteController從記憶體刪除該對象。該對象有一 個指標ControllerExtension指向一塊由驅動程式定義的結構,其中儲存有此驅程式的公用資料。
iii)適配器(Adapter)
該對象用於同步多個裝置(不一定在一個驅動程式中)對DMA通道的使用。該對象在系統啟動偵測硬體時自動被建立。驅動程式在初始化時調用 HalGetAdapter獲得該對象的指標。裝置在StartIo過程中調用IoAllocateAdapterChannel請求獲得DMA通道的獨 占權,然後開始傳輸資料。使用完後調用IoFreeControllerChannel釋放DMA通道。
iv)DPC
由於DPC隊列中的對象總是被系統順序地處理,所以也可以將需要同步的代碼做成Dpc過程,需要調用時將相應的DPC對象放到隊列的末尾即可。
v)其他
同使用者模式的應用程式類似,驅動程式也可以使用多線程,也提供了一套用來同步的對象,如Event,Mutex,Semaphore,Timer,Thread。其中Event對象可以被命名,不同的驅動程式可以利用同名的Event對象同步對公用資料的訪問。
////////////////////////////////////////////////////////////////////////////////////////
windows nt下核心模式裝置驅動程式的結構和運行
一般來說,裝置驅動程式的任務主要有二:第一,接受來自使用者程式的讀寫請求,把
使用者的資料傳送給裝置,或把從裝置接收到的資料傳送給使用者;第二,輪詢裝置或處理
來自裝置的插斷要求,完成資料轉送。
1.2.1 驅動程式與使用者程式的通訊
i/o管理器把每一個裝置對上層都抽象成了檔案,所以在win32使用者程式中只要通過以
下幾條簡單的檔案操作api函數就可以實現與驅動程式中的某個裝置通訊(請注意,一個
驅動程式可以驅動多個裝置):
函數名 功能
createfile 開啟一個裝置,準備進行資料轉送。返回一個與裝置相關的控制代碼。
closehandle 關閉一個由createfile開啟的裝置。
readfile 從裝置讀取資料。
writefile 向裝置寫資料。
deviceiocontrol 對裝置進行一些自訂的操作,比如更改設定等。
表一
1.2.2 driverentry過程
這是每一個裝置驅動程式的入口,每次該程式啟動時被系統自動調用。大部分的裝置
初始化的工作都在這個過程中完成。包括設定響應各種使用者請求的過程的入口,使i/o管
理器能知道當使用者的開啟、關閉、讀寫等請求到來時各應調用那些過程來處理。驅動程
序中只有本過程的名字"driverentry"是固定的,以下列出的所有過程都要由本過程向系
統註冊。
如果該驅動程式不響應任何請求的話,只要一個driverentry過程就可以構成一個能運
行的驅動程式。
1.2.3 unload和shutdown過程
unload過程負責在驅動程式被停止前做一些必要的處理。比如釋放資源,記錄最終狀
態等。shutdown過程在系統即將關閉時被調用,與前者的區別在於不用釋放任何資源。
1.2.4 dispatchopen和dispatchclose過程
這兩個過程在使用者調用createfile和closehandle時被調用,為即將到來的讀寫操作做
備,或做一些讀寫完成後的必要處理。
1.2.5 dispatchread, dispatchwrite與startio過程
這前兩個過程在使用者調用readfile和writefile時被調用。它們先做一些檢驗使用者請求
合法性的工作,然後啟動一個被稱為startio的過程開始實際的與硬體間的資料轉送。i
/o管理器還通過irp為它們提供了一個指向使用者緩衝區的指標,用於與使用者程式交換資料
。詳情請見1.3.2
1.2.6 接受自訂的其他請求
這兩個過程在使用者調用deviceiocontrol時被調用。它通過irp獲得使用者的請求號,以
及一個指向使用者緩衝區的指標,可以與使用者程式進行通訊。
1.2.7 中斷處理過程(isr)
這些過程在中斷髮生時被系統調用。
1.2.8 延遲過程(deferred procedure)
這些過程用來在較低的運行層級完成較高運行層級過程(如中斷處理過程)的一
些任務。詳情請見1.3.3
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1.3.2 幾個對象
i) I/O請求包(IRP)
I/O管理器每收到一個來自使用者的請求就建立一個該結構,並將其作為參數傳給驅
動程式的DispatchXxx、StartIo過程。該結構中存放有請求的類型,使用者緩衝區的首地
址,使用者請求資料的長度等資訊。驅動程式處理完這個請求後,也在該結構中添入處理
結果的有關資訊,調用IoCompleteRequest將其返回給I/O管理器,使用者程式的請求隨即
返回。
ii) DPC
當驅動程式中要用到Dpc過程時,需要建立該對象。具體作用請見1.3.3。
iii) 驅動程式對象(DriverObject)
該對象在驅動程式被啟動時由I/O管理器建立,puNV絡F_U理_網!Q絡(12$*儲存有該程式處理各種請求的過程
入口、該程式所驅動的全部裝置對象的鏈表等。
iv) 裝置對象(DeviceObject)
每發現一個可以驅動的裝置,
x]uI6k教絡網育wklCc
驅動程式調用IoCreateDevice建立一個該對象。該
對象有一個指標DeviceExtension指向一塊由驅動程式定義的結構,其中儲存有關此裝置
的如連接埠號碼,中斷向量等全部資訊。
v) 中斷對象(Interrupt)
該對象在驅動程式調用IoConnectInterrupt時建立,存有中斷及處理的過程的資訊。
當一個中斷髮生時,I/O管理器用它尋找對應的處理過程。
1.3.3 延遲程序呼叫(Deferred Procedure Call)
由於中斷處理過程運行於較高的DIRQL級,{育的9TCwl的&m!C它們能屏蔽許多層級小於或等於它們的過程
的執行,如果它們佔用CPU時間過長,很容易使系統效能下降。因此中斷處理過程應將一
些不是很緊急的任務放在被稱為Dpc的過程中,在完成資料轉送等緊急任務後將一個DPC
對象放在系統DPC隊列的末尾,然後退出,盡量早地讓出CPU。系統將在完成所有DIRQL級
的任務後處理DPC隊列,在DISPATCH_LEVEL執行每一個DPC 對象指定的Dpc過程,完成中
處理斷過程未盡的任務。
1.3.6緩衝的i/o與直接i/o
在驅動程式建立了一個裝置後,可以通過設定deviceobject的flags域的值來將裝置設定成緩衝的i/o或直接的i/o。
如果該值被設為do_buffered_io,每當i/o管理器收到一個讀寫請求,就在記憶體的非分 頁區分配一塊與使用者區大小相同的地區,並將首指標存放於irp對象的associatedirp.s ystembuffer中,驅動程式就通過這個緩衝區與使用者交換資料。每當一個讀請求被完成時 i/o管理器自動將該緩衝區中的內容複寫到使用者區,並釋放該地區。
如果使用者區大於一頁(在80x86上為4096位元組),一般將該值設為do_direct_io。這時每當i/o管理器收到一個讀寫請求,先鎖定使用者 區的實體記憶體,然後為其建立一個內 存描述表(mdl),並將該表的首指標存放於irp對象的mdladdress中,驅動程式可以通過調用 mmgetsystemaddressformdl獲得使用者區在系統空間中的地址。每當一個讀請求被完 成時i/o管理器自動將該地區解鎖。
1.3.7定時
為了防止當裝置出現某種故障時導致讀寫請求逾時,或需要定時輪詢某些裝置的狀態 ,驅動程式需要設定一些定時器。驅動程式中有兩種方法可以設定定時器。一種是調用ioinitializetimer將一個定時器過程iotimer與一 個裝置對象聯絡起來。在調用iostar ttimer後,系統將每一秒鐘調用一次iotimer,直至驅動程式調用iostoptimer。如果需要設定更小間隔的定時器,需要用到被稱為 customtimerdpc的一種延遲程序呼叫機制。 它可以設定系統每隔一定時間將一個設定好的dpc對象放到dpc隊列的末尾,執行一個指定的定時器dpc過程。這個時間間隔可以精確到100ns。