驅動程序簡單入門

我們學習程序設計,都是從“Hello World”開始的,驅動程序也不例外,今天我就寫一個驅動版的“Hello World”來熱熱身,目的希望大家能對驅動程序的基本框架有所瞭解。

驅動程序分爲2類,一個是Kernel模式驅動,另一個是Windows模式驅動,2種模式本質是相同,但細節不同,本文介紹的是內核模式驅動和驅動程序的安裝、使用。

驅動程序同普通的EXE,DLL一樣,都屬於PE文件,而且都有一個入口函數。但EXE中,入口函數是main()/WinMain()和Unicode的wmain()/wWinmain(),DLL的入口函數則可有可無,它是DllMain()。驅動程序也有入口函數,而且是必須的,它是DriverEntry(),再次提示,它是必須的,因爲I/O管理器會首先調用驅動程序的DriverEntry(),它的作用就像DllMain()--完成一些初始化工作。DriverEntry()一共有2個參數:1)PDRIVER_OBJECT DriverObject,指向驅動程序對象的指針,我們操作驅動程序,全靠它,它是由I/O管理器傳遞進來的;2)PUNICODE_STRING RegistryPath,驅動程序的服務主鍵,這個參數的使用並不多,但要注意,在DriverEntry()返回後,它可能會消失,所以如果需要使用,記住先要保存下來。DriverEntry()的返回一個NTSTATUS值,它是一個ULONG值,具體的定義,請參見DDK中的NTSTATUS.H頭文件,裏邊有詳細的定義。
既然要寫驅動版的“Hello World”,就需要確定如何來與驅動程序通信,常用的共享內存,共享事件,IOCTL宏,或者直接用ReadFile()或WriteFile()進行讀寫,在本文裏我就採用一種簡單的、但又很常用的IOCTL宏,它依賴的IRP派遣例程是IRP_MJ_DEVICE_CONTROL,Win32程序使用DeviceIoControl()與驅動進行通信,根據不同的IOCTL宏,輸出不同的調試信息。爲了簡便,我並沒有使用ReadFile()將信息讀出來,而是直接用DbgPrint()輸出,所以需要使用DbgView查看,其他調試工具也可以。PS:偷懶!

驅動程序與I/O管理器通信,使用的是IRP,即I/O請求包。IRP分爲2部分:1)IRP首部;2)IRP堆棧。IRP首部信息如下:
IRP首部:
IO_STATUS_BLOCK IoStatus???????????????????????????????? 包含I/O請求的狀態
PVOID AssociatedIrp.SystemBuffer???????????????? 如果執行緩衝區I/O,這個指針指向系統緩衝區
PMDL MdlAddress?????????????????????????????????????????????????? 如果直接I/O,這個指針指向用戶緩衝區的存儲器描述符表
PVOID UserBuffer???????????????????????????????????????????????? I/O緩衝區的用戶空間地址IRP堆棧:
UCHAR MajorFunction???????????????????????????? 指示IRP_MJ_XXX派遣例程
UCHAR MinorFunction???????????????????????????? 同上,一般文件系統和SCSI驅動程序使用它
union Parameters?????????????????????????????????? MajorFunction的聯合類型
{
struct Read???????????????????????????????????????????? IRP_MJ_READ的參數
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write?????????????????????????????????????????? IRP_MJ_WRITE的參數
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceIoControl?????????????????????? IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL的參數
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject???????????? 請求的目標設備對象的指針
PFILE_OBJECT FileObject???????????????????? 請求的目標文件對象的指針,如果有的話
操作IRP。對於不同的IRP函數,操作也是不同的:有的只操作IRP首部;有的只操作IRP堆棧;還有操作IRP整體,
下面是一些常用的函數:
IRP整體:
?????? 名稱???????????????????????????????????????? 描述???????????????????????????????????????????????? 調用者
IoStartPacket???????????????????? 發送IRP到Start I/O例程???????????????????? Dispatch
IoCompleteRequest???????????? 表示所有的處理完成???????????????????????????? DpcForIsr
IoStartNextPacket???????????? 發送下一個IRP到Start I/O例程???????? DpcForIsr
IoCallDriver?????????????????????? 發送IRP請求?????????????????????????????????????????? Dispatch
IoAllocateIrp???????????????????? 請求另外的IRP?????????????????????????????????????? Dispatch
IoFreeIrp???????????????????????????? 釋放驅動程序分配的IRP?????????????????????? I/O Completion
IRP堆棧:
?????? 名稱?????????????????????????????????????????????????????? 描述???????????????????????????????????????????????? 調用者
IoGetCurrentIrpStackLocation???? 得到調用者堆棧的指針???????????????????????? Dispatch
????
IoMarkIrpPending???????????????????????????? 爲進一步的處理標記調用者I/O堆棧?? Dispatch
IoGetNextIrpStackLocation?????????? 得到下一個驅動程序的I/O堆棧的指針???? Dispatch
IoSetNextIrpStackLocation?????????? 將I/O堆棧指針壓入堆棧?????????????????????? Dispatc
在驅動程序,IRP派遣例程起着很重要的作用,每個IRP派遣例程,幾乎都有對應的Win32函數,下面是幾個常用的:
IRP派遣例程:
?????? 名稱?????????????????????????????????????????????????????? 描述???????????????????????????????????????????????? 調用者
IRP_MJ_CREATE???????????????????????????????????? 請求一個句柄???????????????????????????????????? CreateFile
IRP_MJ_CLEANUP?????????????????????????????????? 在關閉句柄時取消懸掛的IRP?????????? CloseHandle
IRP_MJ_CLOSE?????????????????????????????????????? 關閉句柄???????????????????????????????????????????? CloseHandle
IRP_MJ_READ???????????????????????????????????????? 從設備得到數據???????????????????????????????? ReadFile
IRP_MJ_WRITE?????????????????????????????????????? 傳送數據到設備???????????????????????????????? WriteFile
IRP_MJ_DEVICE_CONTROL???????????????????? 控制操作(利用IOCTL宏)?????????????? DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL?? 控制操作(只能被內核調用)???????????? N/A
IRP_MJ_QUERY_INFORMATION?????????????? 得到文件的長度???????????????????????????????? GetFileSize
IRP_MJ_SET_INFORMATION?????????????????? 設置文件的長度???????????????????????????????? SetFileSize
IRP_MJ_FLUSH_BUFFERS?????????????????????? 寫輸出緩衝區或者丟棄輸入緩衝區 FlushFileBuffers FlushConsoleInputBuffer PurgeComm
IRP_MJ_SHUTDOWN???????????????????????????????? 系統關閉???????????????????????????????????????????? InitiateSystemShutdown
=================================================================================================================================
下面開始寫我們的驅動版的“Hello World”,程序很簡單,先介紹一下流程:
1,調用IoCreateDevice()創建一個設備,並返回一個設備對象。
2,調用IoCreateSynbolicLink()創建一個符號連接,使Win32程序可以使用驅動程序
3,設置IRP_MJ_DEVICE_CONTROL派遣例程HelloWorldDispatch()和卸載例程HelloWorldUnLoad()。
如果Win32程序使用DeviceIoControl(),則執行HelloWorldDispatch()函數
4,調用IoGetCurrentIrpStackLocation()得到當前調用者的IRP指針
5,取得IO控制代碼,完成後使用IoCompleteRequest()完成IRP操作
如果使用ControlService()停止驅動程序,則執行HelloWorldUnLoad()函數
4,調用IoDeleteSymbolicLink()刪除符號連接
5,調用IoDeleteDevice()刪除已建立的設備
驅動入口DriverEntry()
//創建設備
IoCreateDevice(DriverObject,?????????????? //驅動程序對象
???????????????????????????? 0,???????????????????????????????????? //擴展設備的大小,由於不需要,所以置0
???????????????????????????? &DeviceNameString,???? //設備名稱
???????????????????????????? FILE_DEVICE_UNKNOWN, //設備類型
???????????????????????????? 0,???????????????????????????????????? //指示設備允許的操作
???????????????????????????? FALSE,???????????????????????????? //如果爲TRUE,表示只能有一個線程使用該設備,爲FALSE,則沒有限制
???????????????????????????? &lpDeviceObject);?????? //返回的設備對象
//創建符號連接
IoCreateSymbolicLink(&DeviceLinkString,???? //存放符號連接的UNICODE_STRING
???????????????????????????????????????? &DeviceNameString);?? //設備名稱
//派遣例程和卸載例程
DriverObject->MajorFunction[IRP_MJ_CREATE]=
?????? DriverObject->MajorFunction[IRP_MJ_CLOSE]=
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
DriverObject->DriverUnload=HelloWorldUnLoad;
IRP派遣例程HelloWorldDispatch()
IrpStack=IoGetCurrentIrpStackLocation(pIrp);???? //得到當前調用者的IRP堆棧
//獲取IO控制代碼,並執行指定操作,這裏只是DbgPrint()
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes)?? {
......
IoCompleteRequest(pIrp,IO_NO_INCREMENT);???? //完成IRP操作
卸載例程HelloWorldUnLoad()
//刪除符號連接和設備
IoDeleteSymbolicLink(&DeviceLinkString);
IoDeleteDevice(DriverObject->DeviceObject);
=================================================================================================================================
完整代碼:
=================================================================================================================================
驅動程序的編譯需要使用DDK中的build實用程序,它是一個命令行程序,使用不是很方便。VC知識庫有一篇在VC++ 6.0中編譯驅動的文章,有興趣可以去看看。
1,makefile
編譯驅動程序,首先應該準備一個makefile,這個文件很簡單,只有一句代碼:
#
# DO NOT EDIT THIS FILE!!!?? Edit ./sources. if you want to add a new source
# file to this component.?? This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)/makefile.def
正如描述的那樣,不要修改這個文件---它是通用的!
2,sources
準備的第二個文件就是sources,它描述了一些編譯的細節。針對本文的程序,sources文件的內容是這樣的:
TARGETNAME=HelloWorld???? //驅動名稱
TARGETPATH=.?????????????????????? //編譯後SYS的路徑
TARGETTYPE=DRIVER???????????? //類型爲驅動程序
SOURCES= HelloWorld.c???? //只有一個源文件
有了這2個文件後,就可以使用build進行編譯了。進入「開始」菜單/程序/Development Kits/Windows 2000 DDK,
分別有3個CMD程序:1)Checked 64 Bit Build Environment,“Debug”的64位版本;2)Checked Build Environment
“Debug”的32位版本;3)Free Build Environment,“Release”的32位版本。不用說,肯定是使用Free Build Environment。
New or updated MSVC detected.?? Updating DDK environment....
Setting environment for using Microsoft Visual C++ tools.
Starting dirs creation...Completed.
C:/NTDDK>cd/
C:/>cd HelloWorld
C:/HelloWorld>build
BUILD: Object root set to: ==> objfre
BUILD: /i switch ignored
BUILD: Compile and Link for i386
BUILD: Loading c:/NTDDK/build.dat...
BUILD: Computing Include file dependencies:
BUILD: Examining c:/helloworld directory for files to compile.
?????? c:/helloworld - 1 source files (127 lines)
BUILD: Saving c:/NTDDK/build.dat...
BUILD: Compiling c:/helloworld directory
Compiling - helloworld.c for i386
BUILD: Linking c:/helloworld directory
Linking Executable - i386/helloworld.sys for i386
BUILD: Done
?????? 1 file compiled
?????? 1 executable built
C:/HelloWorld>
現在C:/HelloWorld/i386目錄下,就有了HelloWorld.sys。
=================================================================================================================================
驅動程序的安裝如同安裝服務一樣,唯一不同的是,創建服務時,類型是內核驅動,其他跟操作服務沒什麼區別。
安裝驅動程序流程:
1,調用OpenSCManager()打開服務控制管理器
2,調用CreateService()創建一個服務,服務類型爲內核驅動
3,調用OpenService()取得服務句柄
啓動服務
4,調用StartService()啓動服務
停止服務
4,調用ControlService()停止服務
刪除服務
4,調用DeleteService()刪除服務
5,調用CloseServiceHandle()關閉服務句柄
操作驅動程序流程:
1,調用CreateFile()取得設備句柄
2,調用DeviceIoControl()傳遞I/O控制代碼
3,調用CloseHandle()關閉設備句柄
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章