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