日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

简单说说驱动程序设计的入门

發(fā)布時(shí)間:2025/3/15 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 简单说说驱动程序设计的入门 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

簡單說說驅(qū)動(dòng)程序設(shè)計(jì)的入門,其實(shí)初級(jí)驅(qū)動(dòng)設(shè)計(jì)中也能使用C++,也能使用類,但和用戶程序中的用法有一些區(qū)別,一些特殊的地方需要特別注意。從筆者的經(jīng)驗(yàn)來看,WDK給出的AVStream小端口驅(qū)動(dòng)示例工程,就都是C++代碼,這是由于AVStream的模塊性非常強(qiáng),在實(shí)現(xiàn)較大功能模塊時(shí),非得用類封裝,否則難以表述清楚。

很少有專題講內(nèi)核中的C++編程,中文資料恐怕更是罕見。由于C++的普及性、與C的親密關(guān)系,以及大部分情況下程序員都使用C++編譯器編譯C程序的事實(shí),當(dāng)初學(xué)者聽說內(nèi)核中“不容易”(筆者也聽說過“無法”二字)用C++進(jìn)行編程時(shí),會(huì)大吃一驚。不管是說者無意,還是聽者有心,Windows內(nèi)核的現(xiàn)狀,決定了C語言是內(nèi)核編程的首選。

本章專門講述如何在內(nèi)核中編寫C++驅(qū)動(dòng)程序。筆者先寫一個(gè)簡單的例子,顯示類的一些基本特性,并由此交代出幾項(xiàng)關(guān)鍵點(diǎn);然后改造《WDF USB設(shè)備驅(qū)動(dòng)開發(fā)》一章中的WDFCY001驅(qū)動(dòng)的例子,將它全部改造成一個(gè)驅(qū)動(dòng)類,并最終實(shí)現(xiàn)C++的最大優(yōu)點(diǎn):多態(tài)。

一個(gè)簡單的例子
首先我們嘗試把用戶程序中最簡單的類拷貝到內(nèi)核中,編譯鏈接,看看行不行。下面就是筆者定義的整數(shù)類,它封裝一個(gè)整數(shù),對(duì)象能夠被當(dāng)成整數(shù)使用。

以下是代碼片段: class clsInt{ Public: clsInt(){m_nValue = 0;} clsInt(int nValue){m_nValue = nValue;} void print(){KdPrint((“m_nValue:%d/n”, m_nValue));} operator int(){return m_nValue;} private: int m_nValue; };
上例是一個(gè)非常簡單的類定義,我們將在DriverEntry函數(shù)中使用它,分別定義一個(gè)局部變量和動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象。我們通過Debug信息來觀察對(duì)象行蹤,希望能夠得到正確的輸出。入口函數(shù)中的定義如下:

以下是代碼片段: extern "C" NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { // 創(chuàng)建兩個(gè)對(duì)象,一個(gè)是局部變量,一個(gè)是動(dòng)態(tài)創(chuàng)建的 clsInt obj1(1); clsInt* obj2 = new(NonPagedPool, "abcd") clsInt(2); // 打印Log信息 obj1.print(); obj2->print(); delete obj2; // 讓模塊加載失敗 return STATUS_UNSUCCESSFUL; }
上面代碼中先后創(chuàng)建了兩個(gè)clsInt對(duì)象,一個(gè)是在棧中創(chuàng)建的,初始變量為1;一個(gè)是動(dòng)態(tài)創(chuàng)建的,初始變量為2。后者由于是動(dòng)態(tài)創(chuàng)建的,必須手動(dòng)調(diào)用delete函數(shù)釋放內(nèi)存,所以其析構(gòu)函數(shù)比前者先調(diào)用。我們必須從Log信息中得到類似的脈絡(luò),以證明其正確性。代碼請(qǐng)參看simClass工程。圖6-1是Log信息的截圖,我們?nèi)缭敢詢數(shù)氐玫搅讼胍慕Y(jié)果。

?

?

圖6-1 對(duì)象Log信息

new/delete
查看上面的代碼,會(huì)發(fā)現(xiàn)一個(gè)不同于以往的new操作符。這是怎么回事呢?我們這一節(jié)就講講它。在用戶程序中,創(chuàng)建和釋放一個(gè)對(duì)象使用 new/delete方法,其底層乃是調(diào)用HeapAllocate/HeapFree 堆API從線程堆棧中申請(qǐng)空間。但問題是,內(nèi)核CRT沒有提供new/delete操作符,所以需要自己定義。自定義的new/delete操作符,自然也是能夠從堆棧中分配內(nèi)存的,內(nèi)核中有RtlAllocateHeap/RtlFreeHeap堆棧服務(wù)函數(shù)。但在內(nèi)核中,我們一般使用內(nèi)存池來獲取內(nèi)存,實(shí)際上內(nèi)存池和堆棧使用了同一套實(shí)現(xiàn)機(jī)制。使用ExAllocatePool/ExFreePool函數(shù)對(duì)從內(nèi)存池申請(qǐng)/釋放內(nèi)存,下面是一個(gè)例子。

下面是使用new進(jìn)行內(nèi)存申請(qǐng)的一個(gè)例子。

以下是代碼片段: // 定義一個(gè)32位的TAG值 #define TAG "abcd" // 外部已經(jīng)定義了一個(gè)clsName類 extern class clsName; // 為clsName申請(qǐng)對(duì)象空間 clsName* objName = NULL; objName = new(NonPagedPool, TAG)clsName();
上面的new操作和用戶程序中的new操作具有同樣的功效,但需要注意第一個(gè)參數(shù)size_t是必須外置的,編譯器會(huì)自動(dòng)用sizeof(clsName)求取長度并作為第一個(gè)參數(shù)。一般地說,對(duì)于類似下面的語句:
className objName = new(…) className(…)

其執(zhí)行過程是,首先由new操作符為新對(duì)象動(dòng)態(tài)分配內(nèi)存,并返回指針;然后再對(duì)此新創(chuàng)建的對(duì)象,選擇與className(…) 相符的構(gòu)造函數(shù)進(jìn)行初始化。

再來看看delete操作符的重載。

以下是代碼片段: __forceinline void __cdecl operator delete(void* pointer) { ASSERT(NULL != pointer); if (NULL != pointer) ExFreePool(pointer); }
刪除對(duì)象數(shù)組,即delete[]操作符重載。

以下是代碼片段: __forceinline void __cdecl operator delete[](void* pointer) { ASSERT(NULL != pointer); if (NULL != pointer) ExFreePool(pointer); }
上面兩個(gè)函數(shù)最終都會(huì)將指定地址的內(nèi)存釋放,但在釋放之前,前者會(huì)調(diào)用指定對(duì)象的析構(gòu)函數(shù),后者會(huì)對(duì)數(shù)組中每個(gè)成員調(diào)用析構(gòu)函數(shù)。示例如下:

以下是代碼片段: extern clsName *objName; extern clsName *objArray[]; delete objName; delete[] objArray;
extern "C"
對(duì)extern "C"編譯指令,大家不會(huì)感到陌生。它一般這樣用:

以下是代碼片段: extern "C"{ //…內(nèi)容 }
既然是編譯指令,就一定是作用于編譯時(shí)刻的。它告訴編譯器,對(duì)于作用范圍內(nèi)的代碼,以C編譯器方式編譯。一般是針對(duì)C++/Java等程序而用的。如果括號(hào)內(nèi)僅有一項(xiàng),那么括號(hào)可以省略。
最早讓我們見識(shí)到它的作用的是在入口函數(shù)DriverEntry中。現(xiàn)在必須這樣聲明它:

以下是代碼片段: extern "C" NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath );
初學(xué)者未必知道這一點(diǎn),如果“忘記”做上述改動(dòng),將得到如下錯(cuò)誤:

以下是代碼片段: error LNK2019: unresolved external symbol _DriverEntry@8 referenced in function _GsDriverEntry@8 error LNK1120: 1 unresolved externals
很奇怪,這是一個(gè)鏈接錯(cuò)誤,說明編譯過程是通過的。怎么回事呢?認(rèn)真看一下錯(cuò)誤內(nèi)容,原來是系統(tǒng)在鏈接時(shí)找不到入口函數(shù)_DriverEntry@8。這個(gè)奇怪的函數(shù)名,很顯然是C編譯器對(duì)DriverEntry進(jìn)行編譯后的結(jié)果,前綴“_”是C編譯器特有的,后綴“@8”是所有參數(shù)的長度。原來我們現(xiàn)在使用的是C++編譯器,一定是它把DriverEntry編譯成了系統(tǒng)無法認(rèn)識(shí)的另一副模樣了(實(shí)際上,C++編譯器會(huì)把它編譯成以“?DriverEntry@@”開頭的一串很長的符號(hào))。
一旦加上extern "C"修飾符,上述問題即立刻消失了。extern "C"提醒編譯器要使用C編譯格式編譯DriverEntry函數(shù),這樣編譯生成的函數(shù)名稱為“_DriverEntry@8”,鏈接器即可正確地識(shí)別出符號(hào)了。

全局/靜態(tài)變量
首先列出規(guī)則如下:

不能定義類的全局或者靜態(tài)對(duì)象,除非這個(gè)類沒有構(gòu)造函數(shù);否則全局對(duì)象將因初始化過程中含有無法解決的符號(hào),而導(dǎo)致鏈接失敗。

讀者可能難以理解這個(gè)規(guī)定,所以要用實(shí)例進(jìn)行更深的挖掘才行。以simClass的clsInt類為例,如果定義如下全局變量:

clsInt gA;

對(duì)項(xiàng)目進(jìn)行編譯,會(huì)毫不留情地得到如下錯(cuò)誤(也是鏈接錯(cuò)誤):

errors in directory c:/trunk/simclass

c:/trunk/simclass/main.obj : error LNK2019: unresolved external symbol _atexit referenced in function "void __cdecl "dynamic initializer for "gA""(void)" (__EgA@@YAXXZ)

上面的鏈接錯(cuò)誤,是由于函數(shù)__EgA@@YAXXZ中找不到符號(hào)_atexit。這兩個(gè)名字都怪得不得了!理解它們要從C++標(biāo)準(zhǔn)說起,C++標(biāo)準(zhǔn)規(guī)定對(duì)于全局對(duì)象的處理,編譯器要保證全局對(duì)象在main()函數(shù)運(yùn)行之前已經(jīng)被初始化,并且保證main()函數(shù)在退出前被刪除(析構(gòu))。變量的初始化與刪除,需要編譯器專門為它們各自創(chuàng)建一個(gè)函數(shù),并在合適的時(shí)機(jī)進(jìn)行調(diào)用。函數(shù)名稱根據(jù)不同的編譯器會(huì)有所不同,在這里看到,用于對(duì)gA進(jìn)行初始化的是函數(shù)__EgA@@YAXXZ,筆者通過IAD反匯編后看到,用于刪除(析構(gòu))的是函數(shù)__FgA@@YAXXZ。后者一點(diǎn)問題都沒有,但前者遇到了問題,無法解析_atexit符號(hào)。筆者將其匯編代碼拷貝如下:

以下是代碼片段: // 函數(shù)名,注釋很明白地告訴我們,此函數(shù)是gA的初始化函數(shù) __EgA@@YAXXZ: ; DATA XREF: .CRT$XCU:_gA$initializer$o 0000031E mov edi, edi 00000320 push ebp 00000321 mov ebp, esp // 下面首先會(huì)調(diào)用clsInt的默認(rèn)構(gòu)造函數(shù) // 第一句是將m_nValue賦值為0 00000323 mov ds:clsInt gA, 0 // 下面是DbgPrint調(diào)用 0000032D mov eax, ds:clsInt gA 00000332 push eax 00000333 push offset clsInt gA 00000338 push offset PrintString 0000033D call _DbgPrint 0000033D 00000342 add esp, 0Ch // 初始化已經(jīng)完畢了,問題出在這里 //初始化完畢后,把地址作為參數(shù),調(diào)用_atexit以注冊(cè)終止函數(shù) 00000345 push offset 0000034A call _atexit 0000034A // 恢復(fù)堆棧 0000034F add esp, 4 00000352 pop ebp 00000353 retn 00000353 00000353 _text$yc ends
上面的匯編代碼,大部分都是正確的,只是到了最后調(diào)用_atexit函數(shù)時(shí)才出了錯(cuò)(_atexit是導(dǎo)入符號(hào),實(shí)際函數(shù)名應(yīng)去掉前面的“_”,即atexit)。atexit是一個(gè)C標(biāo)準(zhǔn)函數(shù),其作用是向系統(tǒng)注冊(cè)終止函數(shù),即主程序在終止之前需調(diào)用的處理函數(shù)。上面我們看到,atexit將作為參數(shù)進(jìn)行了調(diào)用以析構(gòu)gA。在邏輯上是沒有問題的,但atexit函數(shù)在內(nèi)核中未實(shí)現(xiàn)。實(shí)際上,它有下面的一行調(diào)用:
atexit();

現(xiàn)在的問題就歸結(jié)為:內(nèi)核中沒有C運(yùn)行時(shí)函數(shù)atexit。請(qǐng)問:它可以有嗎?它難道不可以有嗎?

上面筆者也說過,內(nèi)核代碼和用戶程序是非常不一樣的。用戶程序的生命周期由main()調(diào)用開始,main()調(diào)用結(jié)束,整個(gè)程序也即完結(jié)。而驅(qū)動(dòng)程序卻不一樣,雖然我們有時(shí)候把DriverEntry比作main(),但二者在本質(zhì)上不同,DriverEntry的生命周期非常短,其作用僅是將內(nèi)核文件鏡像加載到系統(tǒng)中時(shí)進(jìn)行驅(qū)動(dòng)初始化,調(diào)用結(jié)束后驅(qū)動(dòng)程序的其他部分依舊存在,并不隨它而終止。所以我們一般可把DriverEntry稱為“入口函數(shù)”,而不可稱為“主函數(shù)”。因此作為初級(jí)驅(qū)動(dòng)設(shè)計(jì)來說,它沒有一個(gè)明確的退出點(diǎn),這應(yīng)該是atexit無法在內(nèi)核中實(shí)現(xiàn)的原因吧。

從圖6-2我們看到,用戶程序是一個(gè)獨(dú)立運(yùn)行單位,main()函數(shù)是主線程,它的生命周期也就是程序的生命周期。而初級(jí)驅(qū)動(dòng)設(shè)計(jì)呢?它的生命周期其實(shí)只是鏡像文件的生命周期,即加載與卸載,并沒有固定的主線程與之匹配甚至支配其生命周期;相反,驅(qū)動(dòng)代碼可以出現(xiàn)在任何線程環(huán)境中,被任何線程調(diào)用。

話說回來,其實(shí)驅(qū)動(dòng)程序也是有明顯的生命周期的,即從DriverEntry開始到DriverUnload結(jié)束的鏡像文件的生命周期,如圖6-3所示。這也并非不可利用,筆者覺得,如果在DriverEntry調(diào)用前執(zhí)行全局對(duì)象的初始化函數(shù),而同時(shí)把終止函數(shù)注冊(cè)到DriverUnload中,或許能夠解決問題,但前提是要求系統(tǒng)要做相應(yīng)的改動(dòng)了。因?yàn)镈riverUnload是可選的,所以若采用這種方法,應(yīng)采取措施為未提供DriverUnload函數(shù)的驅(qū)動(dòng)設(shè)置默認(rèn)的卸載函數(shù)。但隨著微軟對(duì)這方面研究的深入,筆者相信,這個(gè)問題一定是他們的問題列表中必須解決的一項(xiàng)。

內(nèi)核中使用C++還有一點(diǎn)需要注意,就是C++編譯器會(huì)在不提醒的情況下,使用堆棧生成臨時(shí)變量若干,而內(nèi)核堆棧是非常有限的,所以常常需要對(duì)此保持一份警惕。

總結(jié)

以上是生活随笔為你收集整理的简单说说驱动程序设计的入门的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。