概述
很久沒有網了,出了一段時間的差,近來,莫名的就有點郁悶!前不久在大富翁上發了一份帖子是關于delphi程序員的發展,大家的反應并不都是很好。于是開始覺得可以考慮換個方向。以前我是做MIS開發的。換哪個方向呢?人越多的方向,好像越是沒有前途。想想當初上大學,那可是越多人考的學校,學費越貴啊!可現在的職業呢?越多人干的事,越是沒有前途了。考慮來考慮去,決定學習一下驅動程序的開發吧!于是從網上查找了一些資料,看的懂的覺得蠻不錯適合我這種小學生的就貼了出來,算是學習筆記吧!
用戶模式與內核模式
從Intel80386開始,出于安全性和穩定性的考慮,該系列的CPU可以運行于ring0~ring3從高到低四個不同的權限級,對數據也提供相應的四個保護級別。運行于較低級別的代碼不能隨意調用高級別的代碼和訪問較高級別的數據,而且也只有運行在ring0層的代碼可以直接對物理硬件進行訪問。由于WindowsNT是一個支持多平臺的操作系統,為了與其他平臺兼容,它只利用了CPU的兩個運行級別。一個被稱為內核模式,對應80x86的ring0層,是操作系統的核心部分,設備驅動程序就是運行在該模式下;另一個被稱為用戶模式,對應80x86的ring3層,操作系統的用戶接口部分(就是我們通常所說的win32 API)以及所有的用戶應用程序都運行在該級別。操作系統對運行在內核模式下的代碼是不設防的,所以不管是建設還是破壞內核模式下的編程都是值得去研究的。

圖1-WIN2000系統的分層結構
在物理硬件與系統核心之間有一個硬件抽象層(HardwareAbstractionLayer),它屏蔽了不同平臺硬件的差異,向操作系統的上層提供了一套統一的接口。從圖中我們還可以看到,設備驅動程序(DeviceDriver)是被I/O管理器(I/OManager)包圍起來的,即驅動程序與操作系統上層的通信全部都要通過I/O管理器。這給驅動程序的編寫帶來了很大的便利,因為很多諸如接收用戶的請求、與用戶程序交換數據、內存映射、掛接中斷、同步等等麻煩的工作都由I/O管理器代勞了。
驅動程序的分類
驅動程序并不像所有人想的那樣一定要和硬件打交道,我粗略的把他分為兩類:硬驅動和軟驅動。硬驅動就是對硬件直接編程進行控制,這類驅動通常必須遵守硬件的通信協議,直接對硬件進行端口訪問、中斷響應、DMA傳輸。它包括:串、并行口,鍵盤,文件系統,SCSI,網絡等驅動程序;另外一種軟驅動呢?不需要直接對硬件就行操作。我認為他可以理解為它是在硬驅動之上的一層更為高級的驅動。我想學習的主要是軟驅動。
一般來說,設備驅動程序的任務主要有兩個:第一,接受來自用戶程序的讀寫請求,把用戶的數據傳送給設備,或把從設備接收到的數據傳送給用戶;第二,輪詢設備或處理來自設備的中斷請求,完成數據傳輸。
驅動程序的結構
在這里,我主要介紹WDM的結構。WDM(Windows driver module)是什么東西呢?在Windows98\95下面,也許你聽得最多的是VXD,我只知道VXD是一種驅動程序,和WDM差不多的東西。只是因為Windows2000是WindowsNT那條線過來的東西,要加上兩個主要的新功能:即插即用(Plug and Play)和電源管理(Power Menage),又不能用Windows98\95那一套,所以就搞出一個叫WDM這么個東西,來支持PNP和PM.。其實想想,現在的技術名詞還不是一般的多啊!總之wdm大家都叫它windows驅動程序模型。
Windows2000里有叫即插即用管理器和I\O(此I\O非彼I\O端口)管理器的兩個東西。比如說我在機器上插了一張符合PCI規范的PCI卡。即插即用管理器會發現這張卡插在第N個插槽上,然后即插即用管理器會說它找到了這樣一張卡,它就去找有沒有現成的驅動程序,如果沒有找到,它會告訴我們,我找到了這樣一張卡,請你插入這張卡的驅動程序盤。好,我們就把驅動程序盤給它,即插即用管理器會去找驅動程序盤上的INF文件,找到后它會比較PCI卡上的標志和INF文件里的標志是否相同,如果相同,它就會依照INF文件里提供的路徑去找驅動程序,找到之后就可以交給I\O管理器,I\O管理器會裝載這個驅動程序。I\O管理器在做了一些接口的工作后,即插即用管理器會先分配好相關的資源給PCI卡,比如說I\O端口空間、內存空間和中斷向量,然后告訴這張卡的驅動程序,我給你分配了這些資源,你看怎么的。如果你沒有怎么的或不敢怎么的,那就趕快記下這些資源,以備后用。
下面說I\O管理器這個東西。上面我們講到I\O管理器裝載這個驅動程序,驅動程序有一個大門,還有N多的小門。I\O管理器先從大門進去(因為I\O管理器只找得到大門,I\O管理器是不是很傻,NO,當然有它的道理,你別問我:I\O管理器怎么找到大門的?驅動程序無非就是一些文件,I\O管理器把這么些文件加載到系統中去),去找一樣東西:進小門的地圖。我們要在大門進去的房間里放這張地圖(驅動程序都是我們造的,我們當然有驅動程序的地圖啦)。I\O管理器找到了地圖,就可以自由進出大小門了。———這些大小門說白了就是函數(不要問我函數是什么東東),小門的地圖就是函數的地址。I\O管理器知道了這些函數的地址,當然就可以調用這些函數啦。還有一個叫IRP的東西,中文名叫I\O請求包。我們這樣來理解它:在用戶的應用程序這一端,要和驅動程序對話,它們之間不是簡單的調用函數(至于為什么,我現在也不知道),應用程序和驅動程序之間有I\O管理器隔著,應用程序對驅動程序的操作,首先由I\O管理器處理成一個包,這個包里面有應用程序請求的操作內容、傳送的數據等等一些東西,然后I\O管理器把這個包扔給驅動程序,驅動程序依照包里的請求,完成操作,把該回傳的數據放進包里,再把包扔還給I\O管理器,I\O管理器再把數據返回給應用程序。——這里所說的包,就是IRP。
這里說的只是WDM結構的一部分,但是有了這一部分知識,其它部分就不難懂了。通過上面的介紹你看見了什么。你可以想象得出驅動程序是什么樣子的嗎?我是這樣想的。驅動程序好像就是一個函數庫,只不過在大門的地方放了一張地圖。而這個大門與地圖我們見到過嗎?好像有點像dll文件呢。在早些時候我學dll的時候,我就只會用dll來存放函數。
工具篇
因為我學習的時候是在win2000下進行的,所以一切以我學習時的配置為準。
第一:安裝win2000操作系統,我安裝是win2000高級服務器版本。
第二:安裝Vc++6.0,我裝的是英文版。
第三:安裝win2000DDK;
通常驅動程序的調試都是用ddk在cmd中完成的。這部分我暫時略過。下面先介紹如何設置vc++6.0在Visual Studio 6.0集成環境中開發設備驅動程序的方法。
在Windows上,Windows DDK提供的開發環境是基于命令行的,操作起來極為不便,而Visual Studio 6.0給我們提供了非常友好易用的集成環境,讓我們有如虎添翼之感。
那么,能否利用Visual Studio的集成環境來開發驅動程序呢?答案是可以的。通過對Visual Studio集成環境的簡單設置,創建好自己的驅動開發集成環境就可以了。
首先要求系統已安裝DDK和Visual C++6.0(安裝時選上所有工具),
1、接下來需要改造ddk\bin\setenv.bat 把要求mstools的有關語句注釋掉(若想在命令行環境開發驅動則還需加入call VC_DIR\VC98\Bin\Vcvars32. bat),以便能在命令行使用vc的相關工具;若只想在IDE環境開發就不必調用Vcvars32.bat,因為相關工具的路徑信息可以在vc環境中設置.)
2、創建一個目錄DriverEnv(目錄名隨意),作為你開發驅動的大本營
3、在該目錄下創建一個批處理文件MakeDrvr.bat,內容如下:
@echo off
if "%1"=="" goto usage
if "%3"=="" goto usage
if not exist %1\bin\setenv.bat goto usage
call %1\bin\setenv %1 %4
%2
cd %3
build -b -w %5 %6 %7 %8 %9
goto exit
:usage
echo usage MakeDrvr DDK_dir Driver_Drive Driver_Dir free/checked [build_options]echo eg MakeDrvr %%DDKROOT%% C: %%WDMBOOK%% free -cef
:exit
該批處理首先對傳遞的參數作一些檢查,然后調用ddk的setenv命令設置環境變量,然后改變目錄為源程序所在驅動器和目錄,并最后調用build,-b保證顯示完全的錯誤信息,-w保證在屏幕上輸出警告,在vc ide里的output窗口中可以看到這些錯誤和警告。
4.建立一個空白工程
選File的new菜單項,然后選project欄的makefile,然后輸入路徑,一路next下去即可,visual studio提供兩種配置win32 debug和win32 release.
5.修改這兩種配置
選project的settings菜單項win32 debug:
在Build Command Line一欄填入MakeDrvr DDK_DIR SOURCE_DRIVE SOURCE_DIR checked [build options]
在Rebuild all options一欄填入 -nmake /a
在output file一欄填入與sources文件中的TARGETNAME相同的文件名
在Browse info file name一欄填入obj\i386\checked\(與TARGETNAME相同的文件名,見下述).bsc
win32 release:
在Build Command Line一欄填入MakeDrvr DDK_DIR SOURCE_DRIVE SOURCE_DIR free [build options]
在Rebuild all options一欄填入 -nmake /a
在output file一欄填入與sources文件中的TARGETNAME相同的文件名
在Browse info file name一欄填入obj\i386\free\(與TARGETNAME相同的文件名).bsc
注:DDK_DIR一般可以寫成%BASEDIR%,build options一般為-cef即已足夠
6.添加源文件到工程
可以新建,也可以添加,這和普通的win32開發一樣。
7.添加資源文件
選INSERT的RESOURCE菜單項即可
8.把文件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
9.把文件Sources放入源程序目錄,內容為
TARGETNAME=RamDrive//這是要生成的驅動程序.sys文件的名字
TARGETPATH=obj //.sys文件所在目錄的上層目錄,(由于ddk的bug)應手工在obj目錄下創建checked和free目錄,以作為.sys的最終存放目錄
TARGETTYPE=DRIVER //驅動程序的類型,一般不變
INCLUDES=$(BASEDIR)\inc //ddk包含文件路徑,一般不變
SOURCES=RamDrive.cpp RamDrive.rc //源文件(不要頭文件),資源文件
BROWSER_INFO = 1 //若想要瀏覽信息,則要有本行;否則可無
10.因為MakeDrvr.bat在DriverEnv目錄,所以應把該目錄添加到vc的Executable files里面
選tools的options菜單項,然后選directories頁,在show directories for一欄選擇Executable files,然后添加即可.
至此,環境設置完畢,你可以按F7, build你的驅動程序了。
看了好多天的書!特別到書店買了《Windows 200/xp wdm 設備驅動開發》這本書,在這里我不想怎么評論它!對于高手來說,我覺得她一定不能滿足,但是對于像我這樣想入門的人來說,仿佛看了半天,還是不知道從何下手。什么原理、模型、分層等等講不講,講!絕對應該講!但是你得快點告訴我怎么先弄一個像“Hello Word!”的什么簡單來不能再簡單的完整的例子給我呀!到網上找阿找啊!那些高手啊!也不為我們新手寫點圖文并茂的上手資料。沒辦法!結合自己的需要再參考一些別人的東東,算是自己的一點不成熟的想法吧!
實例篇
我覺得下面這個介紹非常不錯!我能看懂,所以貼了出來。
我道為什么找不到“Hello Word!”呢?原來在驅動開發的例子里是沒有所謂的“Hello World”程序的。這主要還是因為網絡上的WDM資料太少造成的。但是程序的入口點呢?c語言有Main(),用Vc的常看見的是WinMain(),Delphi開發的是Program里的Begin,但是驅動開發呢?那也是應該有程序的入口點啊。后來我才明白了,那就是DriverEntry()函數。還有一個問題讓我懷疑了老半天,那就是驅動開發的源程序中需不需要include頭文件呀?為什么會懷疑呢?那是因為我看了半天的書都沒有看到一個完整的驅動程序結構。真的是郁悶。下面是我看到的一個完整的結構,我先放上來,讓大家看看驅動開發的結構吧。
/***************************************************************
程序名稱:Hello World for WDM
文件名稱:HelloWDM.cpp
日期:2002-8-16
***************************************************************/
//一定要的頭文件,聲明了函數模塊和變量:
#include "HelloWDM.h"
/***************************************************************
函數名稱:DriverEntry()
功能描述:WDM程序入口(原來的WinMain被換成了DriverEntry,也是驅動程序的大門)
***************************************************************/
//extern "C"是必須的,表示“用C鏈接”。如果你的文件名是HelloWDM.c的話,這句可以省略。
extern "C"
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, //IN 是一個關鍵字表示這是一個輸 入參數,PDRIVER_OBJECT是一個數據結構的指針,就像PCHAR一樣,這個數據結構是什么樣子的,后面我會列出來。她描述了一個驅動設備對象。
IN PUNICODE_STRING RegistryPath)//參數RegistryPath指定了驅動程序注冊表健的路徑,因為驅動程序安裝后總會在系統注冊表里留下一點東西的。
{
//指定“添加設備”消息由函數“HelloWDMAddDevice()”來處理:
DriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
//指定“即插即用”消息由函數“HelloWDMPnp()”來處理:
DriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
//返回一個NTSTATUS值STATUS_SUCCESS。幾乎所有的驅動程序例程都必須返回一個NTSTATUS值,這些值在NTSTATUS.H DDK頭文件中有詳細的定義。
return STATUS_SUCCESS;
}
//NTSTATUS也是一個數據類型,上面我所說的消息有點不準確的,準確地說是“I/O請求包”,不過如果像我們以前理解消息那樣來理解也無不可,我覺得兩者太想了。無非就是上層的應用程序通過它來告訴驅動程序,你要給我什么服務吧!IRP_MJ_PNP就是即插即用處理的請求。你發沒發覺上面其實是在制造進入各個房間的“小門”
/***************************************************************
函數名稱:HelloWDMAddDevice()
功能描述:處理“添加設備”消息
***************************************************************/
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject)
{
//定義一個NTSTATUS類型的返回值:
NTSTATUS status;
//定義一個功能設備對象(Functional Device Object):
PDEVICE_OBJECT fdo;
//創建我們的功能設備對象,并儲存到fdo中:
status = IoCreateDevice(
DriverObject, //驅動程序對象
sizeof(DEVICE_EXTENSION), //要求的設備擴展的大小
NULL, //設備名稱,這里為NULL
FILE_DEVICE_UNKNOWN, //設備的類型,在標準頭文件WDM.H或NTDDK.H中列出的FILE_DEVICE_xxx值之一
0, //各種常量用OR組合在一起,指示可刪除介質、只讀等。
FALSE, //如果一次只有一個線程可以訪問該設備,為TRUE,否則為FALSE
&fdo); //返回的設備對象
//NT_SUCCESS宏用于測試IoCreateDevice內核是否成功完成。不要忘記檢查對內核的所有調用是否成功。NT_ERROR宏不等同于!NT_SUCCESS,最好使用!NT_SUCCESS,因為除了錯誤外,它還截獲警告信息。
if( !NT_SUCCESS(status))
return status;
//創建一個設備擴展對象dx,用于存儲指向fdo的指針:
PDEVICE_EXTENSION dx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
dx->fdo = fdo;
//用IoAttachDeviceToDeviceStack函數把HelloWDM設備掛接到設備棧:
dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
//設置fdo的flags。有兩個“位”是必須改變的,一個是必須清除DO_DEVICE_INITIALIZING標志,如果在DriverEntry例程中調用IoCreateDevice(),就不需要清除這個標志位。還有一個是必須設置DO_BUFFER_IO標志位:
fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
fdo->Flags &= ~DO_DEVICE_INITIALIZING;
//返回值:
return STATUS_SUCCESS;
}
/***************************************************************
函數名稱:HelloWDMPnp()
功能描述:處理“即插即用”消息
***************************************************************/
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
IN PIRP Irp)
{
//創建一個設備擴展對象dx,用于存儲指向fdo的指針:
PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
//首先要通過函數IoGetCurrentIrpStackLocation()得到當前的IRP,并由此得到Minor Function:
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG MinorFunction = IrpStack->MinorFunction;
//然后把這個Minor Function傳遞給下一個設備棧:
IoSkipCurrentIrpStackLocation(Irp);
NTSTATUS status = IoCallDriver( dx->NextStackDevice, Irp);
//處理“即插即用”次功能代碼:
//當Minor Function等于IRP_MN_REMOVE_DEVICE時,說明有設備被拔出或卸下,這時要取消資源分配并刪除設備:
if( MinorFunction==IRP_MN_REMOVE_DEVICE)
{
//取消設備接口:
IoSetDeviceInterfaceState(&dx->ifSymLinkName, FALSE);
RtlFreeUnicodeString(&dx->ifSymLinkName);
//調用IoDetachDevice()把fdo從設備棧中脫開:
if (dx->NextStackDevice)
IoDetachDevice(dx->NextStackDevice);
//刪除fdo:
IoDeleteDevice(fdo);
}
//返回值:
return status;
}
/***************************************************************
程序名稱:Hello World for WDM
文件名稱:HelloWDM.h
作者:羅聰
日期:2002-8-16
***************************************************************/
//頭文件,只是聲明一些函數和變量,比較簡單就不多說了,請讀者自行研究:
#ifdef __cplusplus
extern "C"
{
#endif
#include "ntddk.h"
#ifdef __cplusplus
}
#endif
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo;
PDEVICE_OBJECT NextStackDevice;
UNICODE_STRING ifSymLinkName;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject);
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
IN PIRP Irp);
好了,第一個WDM版的“Hello World”就介紹到這里,雖然實際上它什么都沒有做,但是由于它包含了完整的框架,所以對于向我這樣的新手來說還是很有參考價值的。至于怎么編譯及安裝只有下次再說了
好啦,辛辛苦苦終于寫完了程序,讓我們編譯運行吧!按下Ctrl+F5(嘿嘿,讓我們先假設你習慣用VC來寫程序),我等啊等……疑?怎么毫無動靜的?再看看Output窗口,哇!有幾百個錯誤啊!!不禁頭大——這是怎么回事呢?
原來,WDM程序編譯出來的并不是我們常見的.exe,而是.sys文件,在未經設置編譯環境之前,是不能直接用VC來編譯的(這就是為什么會有幾百個錯誤了)。這種類型的文件你可以在WINNT\System32\Drivers里面找到很多。其實驅動程序也是一種PE文件,它同樣由DOS MZ header開頭,也有完整的DOS stub和PE header,同樣擁有Import table和Export table——……那跟普通的PE文件有什么不一樣呢?那么就讓我們先來做個小剖析,加深對.sys文件的認識吧
首先祭出Delphi里附帶的tdump.exe程序。讓我們鍵入:
C:\WINNT\System32\Drivers>tdump ccport.sys -em -ee
參數-em是列出Import table,-ee是列出Export table。回車之后,屏幕列出一大堆東西:
C:\WINNT\SYSTEM32\DRIVERS>tdump ccport.sys -em -ee Turbo Dump Version 5.0.16.12 Copyright ? 1988, 2000 Inprise Corporation Display of File CCPORT.SYS
IMPORT: NTOSKRNL.EXE={hint:011Fh}.’memcpy’ IMPORT: NTOSKRNL.EXE={hint:003Dh}.’IoDeleteDevice’ IMPORT: NTOSKRNL.EXE={hint:0030h}.’IoAttachDeviceToDeviceStack’ IMPORT: NTOSKRNL.EXE={hint:008Eh}.’KeSetEvent’ IMPORT: NTOSKRNL.EXE={hint:0068h}.’IofCallDriver’ IMPORT: NTOSKRNL.EXE={hint:0095h}.’KeWaitForSingleObject’ IMPORT: NTOSKRNL.EXE={hint:0074h}.’KeInitializeEvent’ IMPORT: NTOSKRNL.EXE={hint:003Fh}.’IoDetachDevice’ IMPORT: NTOSKRNL.EXE={hint:00D3h}.’RtlFreeUnicodeString’ IMPORT: NTOSKRNL.EXE={hint:0077h}.’KeInitializeSpinLock’ IMPORT: NTOSKRNL.EXE={hint:0129h}.’strcpy’ IMPORT: NTOSKRNL.EXE={hint:0121h}.’memset’ IMPORT: NTOSKRNL.EXE={hint:003Ch}.’IoCreateUnprotectedSymbolicLink’ IMPORT: NTOSKRNL.EXE={hint:0038h}.’IoCreateDevice’ IMPORT: NTOSKRNL.EXE={hint:00C2h}.’RtlAnsiStringToUnicodeString’ IMPORT: NTOSKRNL.EXE={hint:0069h}.’IofCompleteRequest’ IMPORT: NTOSKRNL.EXE={hint:0124h}.’sprintf’ IMPORT: NTOSKRNL.EXE={hint:003Eh}.’IoDeleteSymbolicLink’ IMPORT: NTOSKRNL.EXE={hint:0042h}.’IoFreeIrp’ IMPORT: NTOSKRNL.EXE={hint:004Dh}.’IoInitializeIrp’ IMPORT: NTOSKRNL.EXE={hint:002Dh}.’IoAllocateIrp’ IMPORT: NTOSKRNL.EXE={hint:0027h}.’InterlockedExchange’ IMPORT: NTOSKRNL.EXE={hint:0025h}.’InterlockedCompareExchange’ IMPORT: NTOSKRNL.EXE={hint:0035h}.’IoCancelIrp’ IMPORT: NTOSKRNL.EXE={hint:012Ah}.’strlen’ IMPORT: NTOSKRNL.EXE={hint:0126h}.’strcat’ IMPORT: NTOSKRNL.EXE={hint:0114h}.’atoi’ IMPORT: NTOSKRNL.EXE={hint:0128h}.’strcmp’ IMPORT: NTOSKRNL.EXE={hint:0034h}.’IoBuildSynchronousFsdRequest’ IMPORT: NTOSKRNL.EXE={hint:00D5h}.’RtlInitAnsiString’ IMPORT: HAL.DLL={hint:0006h}.’KfAcquireSpinLock’ IMPORT: HAL.DLL={hint:0009h}.’KfReleaseSpinLock’
EXPORT ord:0001=’Vcomm_DriverControl’
|
看到了嗎?它主要調用了NTOSKRNL.EXE和HAL.DLL文件(實際上你會發現,幾乎所有的WDM驅動程序都會調用NTOSKRNL.EXE文件,從它的名字你可以看出為什么了吧?),并且輸出了一個函數“Vcomm_DriverControl”。這表明,其實.sys跟.exe文件一樣,都是一種PE文件來的。不同的是,.sys文件Import的通常是NTOSKRNL.EXE,而.exe文件Import的通常是KERNEL32.DLL和USER32.DLL。
知道了這些有什么用呢?實際上,由于.sys通常不調用KERNEL32.DLL和USER32.DLL,所以你是不能在設備驅動程序里面調用任何C、C++和Win32函數的,而且也不能用C++關鍵字new和delete等(可以用malloc和free來代替),而必須使用大量的內核函數。另外,你應該也能看到她調用了像IoDeleteDevice、IoAttachDeviceToDeviceStack等等函數,這些你以前可能沒有見過的函數都是些內核函數。為了讀者的方便,下面我列出一些常見的驅動程序可用的內核函數:
Ex… 執行支持 Hal… 硬件抽象層(僅NT/Windows 2000) Io… I/O管理器(包括即插即用函數) Ke… 內核 Ks… 內核流IRP管理函數 Mm… 內存管理器 Ob… 對象管理器 Po… 電源管理 Ps… 進程結構 Rtl… 運行時庫 Se… 安全引用監視 Zw… 其他函數
|
最后讓我們再來看看,寫設備驅動程序時必須注意的一些問題:
1、內核宏
如果查看DDK頭文件,會發現有幾個內核函數是以宏的方式實現的。這種宏中有幾個宏的定義是相當糟糕的。例如,我們看到RemoveHeadList的定義如下:
#define RemoveHeadList(ListHead) (ListHead)->Flink; {RemoveEntryList((ListHead)->Flink)}
|
如果以以下方式調用RemoveHeadList,則將編譯錯誤的代碼:
if(SomethingInList) Entry = RemoveHeadList(list);
|
使這個調用安全的唯一方法是使用花括號:
if(SomethingInList) { Entry = RemoveHeadList(list); }
|
所以我們切勿為了貪圖一時的方便,而使用不太規范的寫法,最好是在所有的if、for和while等語句中使用花括號。
2、驅動程序函數名稱
跟C/C++的main()函數一樣,設備驅動程序也有一個必須存在,而且只能以DriverEntry()為名稱的入口函數。然而,除此之外,我們可以使用任何名字來給其他函數命名——只要你自己記得就行了,當然,最好符合某些特定的規范啦,例如匈牙利命名法……
3、安裝時的問題
·在Windows98中驅動程序可執行文件必須是8.3文件名。(別問我為什么,我也不知道,我只能建議你去問比爾該死)
·如果INF文件中含有非法節的詳細資料,Windows將不使用這個INF文件。
本節羅羅嗦嗦講了一大堆,跟實際的編程卻并沒有太大的關系,前傳嘛!就是這樣的啦!
我在前面也講過了一些關于編譯環境及工具的。在這里結合本例子我再說一下:
DDK分為98 DDK和2000 DDK兩種,它們工作起來是大同小異的,不過有些驅動程序只能在2000 DDK中使用。由于Win98注定是一種即將被淘汰的操作系統了,所以我學習的時候也沒有過多的關注,我用的是2000的DDK,所以以下的所有內容都是針對2000 DDK的。
·準備工作
1、確定你已經安裝了Visual C++
2、安裝2000 DDK
3、安裝2000 DDK成功后,在“開始”->“程序”里應該有“Development Kits”->“Windows 2000 DDK”的項目。
(注意一定要先安裝好VC,然后才安裝DDK,這個順序決不能顛倒!!)
4、保證DDKROOT環境變量設置為Windows 2000 DDK的基目錄,如果不是的話,請在控制面板“系統”屬性的“高級”標簽環境變量編輯器中設置好這個環境變量。
·編寫必需的文件
編譯WDM程序的時候,有兩個文件是必須要有的,它們是:
1、makefile
(這個是什么啊?你可能會問。)對于比較年輕的程序員來說,有可能沒有見過這個文件吧。其實在VC這些IDE出現之前,我們都必須使用makefile來確定項目中哪些文件需要重新編譯,現在的IDE都把這個工作自動做好了
我們要做的工作很簡單,就是提供這樣一個文件,它的內容是:
# # 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
|
正如它所述,不要編輯這個文件。事實上每個WDM程序所需要的makefile的內容都是一樣的,也就是說,我們只需要簡單地copy一個makefile到新的項目中就可以了
2、Sources
TARGETNAME=HelloWDM //編譯出來的驅動程序的名稱 TARGETTYPE=DRIVER //編譯的類型是驅動程序編譯 DRIVERTYPE=WDM //驅動程序的類型是WDM驅動程序 TARGETPATH=OBJ //生成的文件存放在OBJ目錄中
INCLUDES=$(BASEDIR)\inc;\ //這是需要引入的頭文件 $(BASEDIR)\inc\ddk;\
TARGETLIBS=$(BASEDIR)\lib\*\free\usbd.lib\ //這是需要引入的庫文件
SOURCES=HelloWDM.cpp\ //這是源碼文件
|
這個文件指定了驅動程序目標名是HelloWDM.sys,是一個WDM驅動程序,生成的文件存放在OBJ目錄中。值得注意的是,“=”前后不能有空格,否則編譯的時候會出錯。
·開始編譯
娃哈哈,前面羅羅嗦嗦講了一大堆,現在終于到重點了。WDM程序的編譯過程比較特殊,它不是在VC里面按F7來編譯的(盡管你可以通過設置來達到這一目的),而是通過一個DDK實用工具build來完成。下面我們來講講具體步驟:
1、“Debug”版的生成
首先,我們假設你的源代碼放在D:\HelloWDM里面。請跟著以下步驟:
“開始”->“程序”->“Development Kits”->“Windows 2000 DDK”->“Checked Build Environment”
屏幕將顯示:(有“回車”的那行是需要讀者你親自打進去的)
New or updated MSVC detected. Updating DDK environment….
Setting environment for using Microsoft Visual C++ tools. Starting dirs creation…Completed.
D:\NTDDK>cd\HelloWDM (回車)
D:\HelloWDM>build (回車)
|
如果源代碼沒有錯誤的話,生成的HelloWDM.sys將存放在objchk\i386目錄中。
2、“Release”版的生成
請跟著以下步驟:
“開始”->“程序”->“Development Kits”->“Windows 2000 DDK”->“Free Build Environment”
隨后的步驟跟“Debug”版相同,不同的是生成的HelloWDM.sys將存放在objfre\i386目錄中。
為一個完整的例子,你開發出來驅動還必須要能安裝。所以下面我講一下安裝。
如果前面的編譯過程沒有錯誤的話,現在我們應該已經得到了一個HelloWDM.sys文件,假設它是放在D:\HelloWDM\objfre\i386中。
安裝WDM驅動程序可以用兩種方法,一種是利用注冊表,還有一種是利用INF文件。我們一般是采用INF文件(這是微軟推薦的)。INF文件可以在 WINNT\INF 目錄中找到很多。為了順利安裝,我在這里先給出 HelloWDM 所需要的 HelloWDM.INF 文件:
;; The Win2K DDK documentation contains an excellent INF reference.
;--------- Version Section ---------------------------------------------------
[Version] Signature="$CHICAGO$" Provider=LC_Device DriverVer=8/21/2002,3.0.0.3
; If device fits one of the standard classes, use the name and GUID here, ; otherwise create your own device class and GUID as this example shows.
Class=Unknown ClassGUID={ff646f80-8def-11d2-9449-00105a075f6b}
;--------- SourceDiskNames and SourceDiskFiles Section -----------------------
; These sections identify source disks and files for installation. They are ; shown here as an example, but commented out.
[SourceDisksNames] 1 = "HelloWDM",Disk1,,
[SourceDisksFiles] HelloWDM.sys = 1,objfre\i386,
;--------- ClassInstall/ClassInstall32 Section -------------------------------
; Not necessary if using a standard class
; 9X Style [ClassInstall] Addreg=Class_AddReg
; NT Style [ClassInstall32] Addreg=Class_AddReg
[Class_AddReg] HKR,,,,%DeviceClassName% HKR,,Icon,,"-5"
;--------- DestinationDirs Section -------------------------------------------
[DestinationDirs] YouMark_Files_Driver = 10,System32\Drivers
;--------- Manufacturer and Models Sections ----------------------------------
[Manufacturer] %MfgName%=Mfg0
[Mfg0]
; PCI hardware Ids use the form ; PCI\VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd ;改成你自己的ID %DeviceDesc%=YouMark_DDI, PCI\VEN_9999&DEV_9999
;---------- DDInstall Sections ----------------------------------------------- ; --------- Windows 9X -----------------
; Experimentation has shown that DDInstall root names greater than 19 characters ; cause problems in Windows 98
[YouMark_DDI] CopyFiles=YouMark_Files_Driver AddReg=YouMark_9X_AddReg
[YouMark_9X_AddReg] HKR,,DevLoader,,*ntkern HKR,,NTMPDriver,,HelloWDM.sys HKR, "Parameters", "BreakOnEntry", 0x00010001, 0
; --------- Windows NT -----------------
[YouMark_DDI.NT] CopyFiles=YouMark_Files_Driver AddReg=YouMark_NT_AddReg
[YouMark_DDI.NT.Services] Addservice = HelloWDM, 0x00000002, YouMark_AddService
[YouMark_AddService] DisplayName = %SvcDesc% ServiceType = 1 ; SERVICE_KERNEL_DRIVER StartType = 3 ; SERVICE_DEMAND_START ErrorControl = 1 ; SERVICE_ERROR_NORMAL ServiceBinary = %10%\System32\Drivers\HelloWDM.sys
[YouMark_NT_AddReg] HKLM, "System\CurrentControlSet\Services\HelloWDM\Parameters",\ "BreakOnEntry", 0x00010001, 0
; --------- Files (common) -------------
[YouMark_Files_Driver] HelloWDM.sys
;--------- Strings Section ---------------------------------------------------
[Strings] ProviderName="Flying L Co.,Ltd." MfgName="LC Soft" DeviceDesc="Hello World WDM!" DeviceClassName="LC_Device" SvcDesc="???"
|
注意它可以同時在Win98或者Win2000中使用(系統會通過這個INF文件里面的字段名稱,自動選擇適合當前系統的安裝方法的)。關于INF文件的各個字段含義現在我也不知道,所以也沒有辦法說清楚,如果誰看到這篇文章,而又知道的話,不妨為我一份。
準備好這個 HelloWDM.INF 文件后,讓我們打開控制面板,雙擊“添加/刪除硬件”,選擇“添加/排除設備故障”->“添加新設備”->“否,我想從列表選擇硬件”->“其它設備”->“從磁盤安裝”,選擇 HelloWDM.INF 所在的路徑,然后安裝。
當安裝完成后,系統就會添加上你寫好的驅動程序了。(可以在“設備管理器”中查看到)。然后重啟電腦,這個驅動程序就投入使用啦。
關于安裝,我也只知道這么多,到底安裝驅動程序時,操作系統都作了些什么,我也不是很清楚,等我弄明白了我再貼上。