[内核编程] 内核环境及其特殊性,驱动编程基础篇
[內(nèi)核編程] 內(nèi)核環(huán)境及其特殊性,驅(qū)動編程基礎(chǔ)篇
?在學(xué)習(xí)漢江獨釣一書后,打算總結(jié)一下內(nèi)核編程應(yīng)該注意的事項,以及有關(guān)的一些基礎(chǔ)知識。第一次接觸內(nèi)核編程,還真是很生疏,很多東西不能一下馬上消化。這里做個回顧總結(jié),好加深自己的印象。
## 1、內(nèi)核編程環(huán)境
?這里涉及到兩個模式:內(nèi)核模式和用戶模式。這個可以和CPU的等級聯(lián)系到一塊:ring0,ring1,ring2,ring3,特權(quán)等級依次降低,最底層ring0層擁有最高的特權(quán)等級。而windows簡化了這樣的特權(quán)等級層次次關(guān)系,分成了內(nèi)核層和用戶層,對應(yīng)的就是內(nèi)核模式和用戶模式。平時我們所編寫的應(yīng)用程序,運行后,在windows下就會生成一個對應(yīng)的進程,針對的是單個進程進行程序的編寫,受到了隔離保護的,運行環(huán)境受到操作系統(tǒng)的保護,很多底層的問題無需考慮,這使得編寫應(yīng)用程序變得相當容易。
【圖】CPU特權(quán)等級和windows層次對應(yīng)關(guān)系
【圖】windows結(jié)構(gòu)
所謂的內(nèi)核模式,實際上就是不受操作系統(tǒng)管制的最底層結(jié)構(gòu),為操作系統(tǒng)提供各項服務(wù)的核心部件。在這里,理論上可以實現(xiàn)任何可以想到的功能,而不受到操作系統(tǒng)的制約,也正因為如此,問題也就產(chǎn)生了,面對共享的內(nèi)存空間、共享資源,如何進行同步操作就成了一個關(guān)鍵的問題。再者,內(nèi)核結(jié)構(gòu)很多都是不公開的,并且又沒有操作系統(tǒng)的保護,所以一點系統(tǒng)出錯,就是直接藍屏或死機,這給內(nèi)核調(diào)試帶來了很大的麻煩。
windows一般都是用系統(tǒng)進程來加載內(nèi)核模塊的,但這并不是說內(nèi)核代碼始終運行在System進程里,也就是說當DriverEntry被調(diào)用時,一般是位于System進程中的,其他時候則不一定。內(nèi)核模塊位于內(nèi)核空間,而內(nèi)核空間又被所有的進程共享。因此,內(nèi)核模塊實際上位于任何一個進程空間中。但是任意一段代碼的任意一次執(zhí)行,一定是位于某個進程空間中的。而至于這是哪一個進程,取決于請求的來源、處理的過程等。
## 2、常用數(shù)據(jù)類型
一般來說,在進行內(nèi)核編程時,應(yīng)當遵守WDK的編碼習(xí)慣,雖然這并不是必須的,但是如果不這么做,有可能還是會導(dǎo)致一些不穩(wěn)定性的問題。比如unsigned long 在64bit 環(huán)境下為8字節(jié)、而在32bit 環(huán)境下是4字節(jié),這個時候要把數(shù)據(jù)寫到磁盤上時,到底寫4個字節(jié)還是8個字節(jié)呢?問題也就出現(xiàn)了,所以WDK重新定義了這個類型,為ULONG。
以下是一些常用的數(shù)據(jù)類型的轉(zhuǎn)換關(guān)系:
- unsigned long ?--- ULONG
- unsigned char --- UCHAR
- unsigned int ?--- UINT
- void --- VOID
- unsigned long * ---- PULONG
- unsigned char * --- PUCHAR
- unsigned int * ?--- PUCHAR
- void* ---PVOID
- char* --- PCHAR
- ........
當然也不僅僅是這些,一般來說,指針前面加上P,unsigned 對應(yīng) U,基本類型名不便只是換成了大寫(例如char --- CHAR),當然這只是我個人見到的,也不排除有特例,我這么對應(yīng)一般都沒有什么問題。
函數(shù)一般都會返回操作的狀態(tài),以說明操作的情況,內(nèi)核中這個類型是NTSTATUS。調(diào)用函數(shù)時一般這樣做:
NTSTATUS status; ?status = function(..); if(NT_SUCCESS(status)){... 操作成功后要做的事情 ?...}
NT_SUCCESS() 可以判斷操作時候成功,當談status還有其他的狀態(tài),可以查看WDK。
驅(qū)動力字符串一般用一個結(jié)構(gòu)來保存,是一個寬字符串:
| 1 2 3 4 5 | typedef?struct?_UNICODE_STRING{ ?????USHORT? Length; ?// Buffer的字節(jié)長度,實際存儲的長度 ?????USHORT? MaximumLength;?//Buffer的最大長度,即開辟的空間大小 ?????PWSTR??? Buffer;//存儲字符的緩沖區(qū) }UNICODE_STRING *PUNICODE_STRING; |
## 3、一些重要的數(shù)據(jù)結(jié)構(gòu)
驅(qū)動編程中,比較重要的幾個數(shù)據(jù)結(jié)構(gòu)是:驅(qū)動對象(DRIVER_OBJECT)、設(shè)備對象(DEVICE_OBJECT)和請求(IRP)。
a. 一個驅(qū)動對象代表一個驅(qū)動程序,或者說一個內(nèi)核模塊。
b. 設(shè)備對象是由驅(qū)動創(chuàng)建的,一個驅(qū)動可以對應(yīng)多個設(shè)備對象,設(shè)備對象是唯一能接收請求的實體。
c.windows內(nèi)核中各部件的通信,是通過請求來完成的,來自用戶層的相關(guān)操作,會被IO管理器翻譯成請求(IRP或者與其等效的其它形式),處理這一個個的請求也就完成相應(yīng)的操作。
?關(guān)于這三個對象的結(jié)構(gòu)可以在wdm.h中找到。這里給出重要數(shù)據(jù)結(jié)構(gòu)的關(guān)系圖:
【圖】設(shè)備棧結(jié)構(gòu),驅(qū)動垂直結(jié)構(gòu)
【圖】驅(qū)動的水平結(jié)構(gòu),一個驅(qū)動可以闖將對個設(shè)備對象,這些設(shè)備構(gòu)成一個鏈表結(jié)構(gòu)
## 4、常用函數(shù),以及內(nèi)核API的學(xué)習(xí)
編寫內(nèi)核程序的時候,盡量使用內(nèi)核API,雖然類似于wcscpy、memcpy等函數(shù)也可以用,但這些函數(shù)沒有長度的檢查,很容易發(fā)生溢出錯誤,應(yīng)該避免使用。
主要的函數(shù)前綴有:Rtl-, Io-, Ke-, Zw-, Nt-, Ps-等。
有以下幾類:
Ex系列--分配內(nèi)存,獲取互斥體等:
??ExAllocatePool,分配內(nèi)存
??ExFreePool,釋放內(nèi)存
??ExAcquireFastMutex獲取一個快速互斥體
??ExReleaseFastMutex釋放一個快速互斥體
??ExRaiseStatus拋出異常
????Zw--Nt系列文件操作函數(shù):
??ZwCreateFile--創(chuàng)建文件
??ZwWriteFile--寫文件
??ZwReadFile--讀取文件
??ZwQueryDirectoryFile--查詢目錄
??ZwDeviceIoControlFile--發(fā)出設(shè)備控制請求
??ZwCreateKey--打開一個注冊表鍵
??ZwQueryValueKey--讀取一個注冊表鍵
????Rtl系列字符串操作函數(shù):
??RtlInitUnicodeString--初始化一個Unicode字符串
??RtlCopyUnicode--拷貝字符串
??RtlAppendUnicodeToString--追加字符串到另一個字符串
??RtlStringCbPrintf--將字符打印到字符串中,相當于格式化字符串
??RtlCopyMemory--拷貝內(nèi)存
??RtlMoveMemory--移動內(nèi)存數(shù)據(jù)塊
??RtlZeroMemory--內(nèi)存數(shù)據(jù)塊清零
??RtlCompareMemory--比較內(nèi)存
??RtlGetVersion--得到當前windows版本
????Io開頭的IO管理函數(shù):
??IoCreateFile--打開文件,比ZwCreateFile函數(shù)更加底層
??IoCreateDevice--生成一個設(shè)備對象
??IoCallDriver發(fā)送請求
??IoCompleteRequest--完成IRP請求
??IoCopyCurrentIrpStackLocationToNext--講當前IRP棧空間拷貝到下一個棧空間
??IoSkipCurrentIrpStackLocationToNext--跳過當前IRP棧空間
??IoGetCurrentIrpStackLocation--得到當前IRP棧空間。
對于詳細的說明可以查看WDK的幫助文檔,API很多,我們不肯能全都記住,常使用,常動手,常查幫助,是很好的學(xué)習(xí)方法。見一個學(xué)一個,不記得就立即查幫助。相關(guān)的結(jié)構(gòu)也可以直接查看WDK的頭文件,這里可以查到幫助文檔中沒有的一些信息哦!!
## 5、Womdows的驅(qū)動開發(fā)模型
?NT(KDM)、WDM、WDF(WDM的升級版)
## 6、WDK編程中的特殊性
調(diào)用源:沿著該函數(shù)往上走,再也不能找到其調(diào)用者,則這個函數(shù)就是調(diào)用源,一般的個單線程函數(shù)的調(diào)用源只有一個,也就是主函數(shù),比如我們常見的main函數(shù)。而在內(nèi)核編程中,一個函數(shù)往往有多個調(diào)用源,主要可以追溯到的調(diào)用源有以下幾個:入口函數(shù)DriverEntry、卸載函數(shù)DriverUnload;各種分發(fā)函數(shù);處理請求時的完成函數(shù);其它回調(diào)函數(shù)。
在內(nèi)核中,一個函數(shù)有可能同時被多個線程調(diào)用,這個是有就好保證多線程的安全性,也就是在輸入相同的情況下要保證輸出是相同的。多線程安全性可以依據(jù)以下幾個規(guī)則進行判斷:
- 可能運行于多線程環(huán)境下的函數(shù),必須是多線程安全的。
- 如果函數(shù)A的所有調(diào)用源都是運行于單線程的,那么函數(shù)A也是運行于單線程的。
- 如果函數(shù)A的調(diào)用源中,其中有一個可能運行于多線程環(huán)境下,并且在調(diào)用路徑上沒有將多線程序列化成單線程,那么函數(shù)A也是可能運行于多線程環(huán)境下的。
- 如果函數(shù)A的所有可能出現(xiàn)多線程的調(diào)用路徑上都被單線程化了,那么函數(shù)A就是單線程環(huán)境下的。
- 只是用函數(shù)內(nèi)部資源,則函數(shù)是多線程安全的
- 如果函數(shù)在訪問全局變量或者靜態(tài)變量的操作采用強制的同步手段進行限制,可以等同于使用內(nèi)部變量(上一條規(guī)則成立)。
內(nèi)核代碼主要調(diào)用源運行環(huán)境:
DriverEntry、DriverUnload 單線程 —— 這兩個函數(shù)由系統(tǒng)進程的單一線程調(diào)用,不會出現(xiàn)多線程同步調(diào)用的情況
各種分發(fā)函數(shù) 多線程 ? —— 沒有文檔能保證分發(fā)函數(shù)不會被多線程同步調(diào)用,分發(fā)函數(shù)不會和DriverEntry同步,但是可能和DriverUnload同步
完成函數(shù) 多線程 —— 完成函數(shù)隨時可能被未知的線程調(diào)用
各種NDIS回調(diào)函數(shù) 多線程 —— 隨時可能被位置的線程調(diào)用
代碼的中斷級:
Windows為CPU的運行狀態(tài)定義了許多的級別,即IRQL,任一時間中,CPU總是運行在其中的某一級別,各個級別規(guī)定了CPU能做哪些事情,哪些不可以做。高中斷級可以搶占低中斷級。
級別定義如下:
#define?PASSIVE_LEVEL??0??;級別最低,CPU在用戶層,或剛進內(nèi)核運行于管理層時就運行在此級別上
#define?LOW_LEVEL??0??;
#define?APC_LEVEL??1??;比PASSIVE_LEVEL略高,運行APC函數(shù)(進程與線程)時需要的級別
#define?DISPATCH_LEVEL??2??;相當于cpu運行在內(nèi)核層,線程切換時級別從此下降
#define?PROFILE_LEVEL??27??;級別3以上用于硬件中斷。
#define?CLOCK1_LEVEL??28
#define?CLOCK2_LEVEL??28
#define?IPI_LEVEL??29
#define?POWER_LEVEL??30
#define?HIGH_LEVEL??31
兩個規(guī)則:
- 如果在調(diào)用路徑上沒有特殊情況(導(dǎo)致中斷級的提高或者降低),則一個函數(shù)執(zhí)行時的中斷級和它的調(diào)用源的中斷級相同。
- 如果在調(diào)用路徑上又獲取自旋鎖,則中斷級隨之升高,釋放自旋鎖,中斷級隨之下降。
處于高中斷級中的函數(shù)不能調(diào)用處于低中斷級中的API,若dispach級想調(diào)用passive級上的AIP,可以另創(chuàng)建一個線程專門執(zhí)行。
其他:
函數(shù)定義中,變量的類型前常常會有:IN、OUT這樣的字符,這些字符被定義成空,起到說明性的作用,IN表示輸入?yún)?shù),OUT表示輸出參數(shù)。
PAGED_CODE(),進行分頁測試,只要級別不高于APC_LEVEL,其代碼都允許換出,若發(fā)現(xiàn)更高級的中斷發(fā)出缺頁中斷,則發(fā)出異常,讓編程者知道。
指定函數(shù)位置的預(yù)編譯指令:
#pragma alloc_text(INIT, DriverEntry) #pragma alloc_text(INIT, DriverUnload) #pragma alloc_text(PAGE,NdisProtUnload) .... ....這個宏僅僅用來指定某個函數(shù)的可執(zhí)行代碼在編譯出來后在sys文件中的位置。內(nèi)核模塊編譯出來后是一個PE文件的sys文件,這個文件的代碼段(text段)中有不同的節(jié)(Section),不同的節(jié)被加載到內(nèi)存中之后處理情況不同。主要有3個節(jié):
- INIT節(jié):在初始化完畢之后就被釋放,不再占用內(nèi)存空間。
- PAGE節(jié):位于可分頁交換的內(nèi)存空間,這些空間在內(nèi)存緊張的時候可以被交換到磁盤上以節(jié)省空間。
- PAGELK節(jié):不適用預(yù)編譯指定的時候,默然的狀態(tài)。加載后位于不可分頁交換的內(nèi)存空間中。
VOID ASSERT( Expression ); —— 這個宏用于測試一個表達式,如果這個表達式的值是false,它就終止,并跳出到內(nèi)核調(diào)試器。
## 7、課后習(xí)題
(1)內(nèi)核編程環(huán)境和用戶應(yīng)用程序編程環(huán)境有哪些不同?
編程模式可分為兩種:用戶模式和內(nèi)核模式。
其中用戶應(yīng)用程序的編程采用的是用戶模式,這里都是在操作系統(tǒng)的隔離環(huán)境中完成的,也就是說對于這個模式來說不用考慮通用寄存器,內(nèi)存是共享的,可通過操作系統(tǒng)實現(xiàn)進程間的資源共享,這屬于單進程編程,利用的都是進程內(nèi)的資源,不用擔(dān)心會產(chǎn)生什么沖突。
內(nèi)核編程使用的是內(nèi)核模式編程,其內(nèi)核屬于操作系統(tǒng)的一個模塊供各個進程調(diào)用,在內(nèi)核空間中資源都是共享的并且不受操作系統(tǒng)的限制,很容易發(fā)生沖突。
(2)Windows有哪幾種驅(qū)動開發(fā)模型?它們的發(fā)展現(xiàn)狀如何?
model是根據(jù)操作系統(tǒng)的類型不同而取名的,有KDM(windows NT),WDM(windows 98 —— windows 2000),WDF(WDM的升級版),一脈相承的,不用擔(dān)心過時。
(3)什么是用戶空間?什么事內(nèi)核空間?
進程的空間實際上被分成兩個部分,一部分是進程獨立使用的用戶空間,一部分是容納操作系統(tǒng)內(nèi)核的內(nèi)核空間。具體到4G內(nèi)存控件的32位windows系統(tǒng)上,低2G是用戶空間,高2G是內(nèi)核空間。
(4)內(nèi)核模塊運行在什么進程環(huán)境下??
內(nèi)核模塊無處不在,內(nèi)核模塊屬于操作系統(tǒng)的一個部分,為各進程提供服務(wù),也就是說每個進程中都有可能運行有內(nèi)核模塊,但是內(nèi)核模塊一般是通過系統(tǒng)system進程進行加載的。
(5)請簡述驅(qū)動對象、設(shè)備對象、請求之間的關(guān)系。
驅(qū)動對象、設(shè)備對象、請求
內(nèi)核編程采用的是面向?qū)ο蟮木幊趟枷雭磉M行編程的,把每個事物都看成一個對象。而驅(qū)動對象可以說是一個驅(qū)動程序也可以說是一個內(nèi)核模塊。設(shè)備對象是唯一能接受請求的對象,而設(shè)備對象是在內(nèi)核模塊中建立的,也就是說設(shè)備對象屬于驅(qū)動對象,設(shè)備對象在接收到請求以后交由驅(qū)動對象的分發(fā)函數(shù)進行處理。請求,內(nèi)核模塊之間的交互都是通過一個個的請求實現(xiàn)的,比如說申請資源、讀取信息等,一般使用IRP請求,一個請求有可能要歷經(jīng)多個設(shè)備,所以需要暫時存儲中間變量的棧空間。
(6)請簡述如何判斷對一個全局變量的訪問是否要加自旋鎖或者互斥體使之序列化。
當一個程序有可能會運行在多線程環(huán)境下的時候就需要保證其是多線程安全的,也就是說不能出現(xiàn)線程沖突。對一個全局變量的訪問時候要加自旋鎖或互斥體使之序列化,取決于被使用的變量是否影響到保證多線程的安全。
(7)請簡述如何估計當前代碼的中斷級。
1、如果調(diào)用途徑?jīng)]有特殊情況,中斷級和調(diào)用源一樣
2、獲自旋鎖中斷級升高,失自旋鎖中斷級降低
總結(jié)
以上是生活随笔為你收集整理的[内核编程] 内核环境及其特殊性,驱动编程基础篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 雉离爱吃的东西是什么(鸡形目雉科动物)
- 下一篇: 微信小程序业务-字符串生成二维码(wea