摘 要: 介紹了Windows NT4.0內核模式設備驅動程序開發(fā)中的一般性過程。通過提供一個最小化驅動程序的核心代碼,解釋各組成部分的結構功能和使用方法。在實踐中,結合自身的開發(fā)需要,可編寫出具有實用價值的驅動程序。
關鍵詞: Win32子系統(tǒng) 設備驅動 系統(tǒng)注冊表 I/O請求包
?
Windows NT 以其安全、穩(wěn)定及界面友好等特性逐漸成為工業(yè)控制領域的前臺操作系統(tǒng)。面對工業(yè)控制中大量采用的串/并行通信及總線控制等技術,要求用戶不斷開發(fā)出滿足自身需要的硬件設備,同時又要求用戶應用程序與這些硬件設備進行通信,發(fā)送控制命令,讀取狀態(tài)信息等等。Windows NT出于安全性、穩(wěn)定性等考慮,不允許用戶應用程序對物理硬件進行直接訪問,這就需要使用設備驅動程序跨越操作系統(tǒng)邊界對物理硬件進行操作,并向上提供客戶應用程序控制接口以供調用。
1 分層結構與設備驅動程序
Windows NT分層結構(如圖1所示)包括運行于用戶模式及內核模式的各種部件,設備驅動程序在圖1的左下角,處于內核模式下I/O管理器之中。
2 驅動程序工作方式
內核模式驅動程序與應用程序之間的最大差別之一是驅動程序的控制結構。內核模式驅動程序沒有main或WinMain,而是由I/O管理器根據需要調用一個驅動程序例程:
· 驅動程序被裝入時;
· 驅動程序被卸出或系統(tǒng)關閉時;
· 用戶程序發(fā)出I/O系統(tǒng)服務調用時;
· 共享硬件資源對驅動程序可用時;
· 設備操作過程中的任何時候。
3 初始化過程
3.1 系統(tǒng)注冊表中有關設備驅動程序的項目是系統(tǒng)
加載設備驅動程序的入口點
系統(tǒng)注冊表中用于系統(tǒng)加載設備驅動程序的項目如下:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DriverName]
″Type″ = dword:00000001
″Start″ = dword:00000002
″Group″ = ″Extended Base″
″ErrorControl″ = dword:00000001
其中Start含義如下:
SERVICE_BOOT_START (0×0) 操作系統(tǒng)裝入時
SERVICE_SYSTEM_START (0×01) 操作系統(tǒng)初始化時
SERVICE_AUTO_START (0×02) 服務控制管理器啟動時
SERVICE_DEMAND_START (0×03) 服務控制管理器手工啟動
SERVICE_DISABLED (0×04) 不啟動
Type含義如下:
SERVICE_KERNEL_DRIVER (0×1)
SERVICE_FILE_SYSTEM_DRIVER (0×2)
SERVICE_ADAPTER (0×4)
系統(tǒng)注冊表中用于設備驅動程序加載后讀取的項目如下:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DriverName\Parameters]
″Parameter1″ = dword:00000001
″Parameter2″ = dword:00000004
3.2 加載驅動程序的裝入例程
I/O管理器調用驅動程序的DriverEntry例程,執(zhí)行初始化。該例程完成:
· 初始化其它例程的入口;
· 創(chuàng)建命名設備對象;
· 讀取系統(tǒng)注冊表中相關項目并聲明必要的資源;
· 設置內核驅動程序名與Win32子系統(tǒng)名的聯接;
· 創(chuàng)建或初始化任意驅動程序使用的對象、類型和資源;
· 返回狀態(tài)值。
I/O管理器建立與設備關聯的Driver對象,并將其傳遞給DriverEntry例程。實際上Driver對象基本上是一個目錄,含有指向各個驅動程序服務例程函數的指針,其結構如表1所示。
I/O管理器能夠找到DriverEntry例程,是因為它有一個公認的名字,而其他的例程則通過下列兩種方法查找:
·在Driver對象中有明確槽的函數,如DirverObject->DriverUnload;
·在Driver對象的MajorFunction數組中——Driver對象的MajorFunction支持兩種類型的功能代碼。一種為標準的功能代碼,如IRP_MJ_CREATE。另一種是用戶自定義的功能代碼,如IRP_MJ_DEVICE_CONTROL。
所有驅動程序必須支持IRP_MJ_CREATE功能代碼,這是因為Win32子系統(tǒng)下的用戶程序調用CreateFile函數創(chuàng)建設備時,產生該功能代碼。如果不處理這個功能代碼,Win32程序就不能得到設備句柄。
用戶自定義的功能代碼IRP_MJ_DEVICE_CONTROL只有在用戶模式下的客戶程序執(zhí)行自定義的功能時可用。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
//聲明設備對象
PDEVICE_OBJECT DeviceObject;
//生成函數接口指針
DriverObject->MajorFunction[IRP_MJ_CREATE]=XxSelfDispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE]=XxSelfDispatch;
DriverObject->MajorFunction[IRP_MJ_READ]=XxReadDispatch;
DriverObject->MajorFunction[IRP_MJ_WRITE]=XxWriteDispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=XxSelfDispatch;
DriverObject->DriverUnload=XxUnload;
//生成Windows NT Executive知道的設備名
RtlInitUnicodeString(&NtDeviceName, SelfDeviceName);
//生成自己的設備
Status=IoCreateDevice(
DriverObject, // Driver對象
sizeof(SELF_DEVICE_INFO), // Device對象
Extension結構大小
&NtDeviceName,
DeviceType,
0,
FALSE, // 不執(zhí)行
&DeviceObject //Device對象指針
);
//生成Win32子系統(tǒng)下的用戶程序可識別的設備名
RtlInitUnicodeString(&Win32DeviceName, SelfWin32Name);
//聯接內部設備名與Win32子系統(tǒng)下的設備名
Status = IoCreateSymbolicLink( &Win32DeviceName, &NtDeviceName );
//利用RtlQueryRegistryValues函數讀出注冊表中Parameters下的參數值,初始化自己的硬件
...
}
4 驅動程序服務例程
驅動程序初始化之后,始終等待發(fā)自用戶的命令或由其它事件源引起的事件。一旦命令或事件發(fā)生,I/O管理器就調用相應的服務例程提供服務。而幾乎所有的I/O都是通過I/O請求包(IRP)驅動的。所謂IRP驅動,就是I/O管理器負責在非分頁的系統(tǒng)內存中分配一定空間,當接受用戶發(fā)出的命令或由事件引發(fā)后,將工作指令按一定的數據結構置于其中,傳遞到驅動程序服務例程。換言之,IRP包含了驅動程序服務例程所需要的信息指令。表2、表3為IRP的一些數據結構。
同時,I/O管理器和驅動程序都需要在所有時候知道一個I/O設備所進行的情況。系統(tǒng)提供Device對象以滿足此要求。該對象在DriverEntry例程中生成設備時由系統(tǒng)創(chuàng)建后,分配給驅動程序,并在整個驅動程序生存期內有效。當I/O管理器調用驅動程序服務例程時,傳遞該對象。表4為Device對象的外部可見域。
其中,DeviceExtension域是一個重要的數據結構。它是由I/O管理器創(chuàng)建并自動掛接到Device對象的非分頁池,是保存驅動程序任意全局變量的最好辦法。因為DeviceExtension是驅動程序特定的,要自定義它的數據結構。
下面是一個驅動程序服務例程利用Device對象和IRP的片段:
NTSTATUS XxSelfDispatch(IN PDEVICE_OBJECT pDO, IN PIRP pIrp);
{
PLOCAL_DEVICE_INFO pLDI;
PIO_STACK_LOCATION pIrpStack;
PULONG pIOBuffer;//得到全局信息
pLDI = (PSELF_DEVICE_INFO)pDO->DeviceExtension;
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
//得到由用戶應用程序發(fā)來的用戶數據,并在需要時,將結果通過此變量返回給用戶
pIOBuffer=(PULONG)pIrp->AssociatedIrp.System Buffer;
// 由IRP攜帶的信息決定驅動程序的執(zhí)行
switch (pIrpStack->MajorFunction)
{
case IRP_MJ_CREATE:
case IRP_MJ_CLOSE:
Status = STATUS_SUCCESS;
break;
case IRP_MJ_DEVICE_CONTROL:
//由Parameters進一步解釋控制代碼含義
switch (pIrpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_Function1: //執(zhí)行功能代碼
Field1 = pLDI->SelfField1;
...
break;
case IOCTL_Function2: //執(zhí)行功能代碼
...
break;
}
break;
}
// 返回I/O操作的狀態(tài)
pIrp->IoStatus.Status = Status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT );
return Status;
}
5 驅動程序終止例程
Unload例程負責取消由DriverEntry例程所做的任何事情,包括解除屬于該驅動程序的任何硬件資源的分配,以及刪除屬于驅動程序的任何內核對象。通常這僅在系統(tǒng)關閉時需要。
VOID XxUnload(PDRIVER_OBJECT DriverObject)
{
PLOCAL_DEVICE_INFO pLDI;
UNICODE_STRING Win32DeviceName;
// 得到全局數據,根據全局數據進行清理工作
pLDI=(PLOCAL_DEVICE_INFO)DriverObject->Device
Object->DeviceExtension;
if (pLDI->Field2 == TRUE)
{
...
}
// 刪除分配的設備名及設備
RtlInitUnicodeString(&Win32DeviceName, SelfWin32Name);
IoDeleteSymbolicLink(&Win32DeviceName);
IoDeleteDevice(pLDI->DeviceObject);
}
6 用戶層應用程序與驅動程序間的接口
驅動程序完成后,將在系統(tǒng)重新引導時裝入并初始化(由DriverEntry例程完成)。此時,驅動程序處于可用狀態(tài),等待用戶層應用程序使用。用戶層應用程序可以:
·打開該設備文件(由IRP_MJ_CREATE功能代碼完成)
·讀出數據(由IRP_MJ_READ功能代碼完成)
·寫入數據(由IRP_MJ_WRITE功能代碼完成)
·執(zhí)行用戶自定義的功能代碼(由IRP_MJ_DEVICE_CONTROL功能代碼完成)
·關閉該設備文件(由IRP_MJ_CLOSE功能代碼完成)
以下是部分實現代碼:
void main()
{
HANDLE hndFile; // 由CreateFile得到
union {
ULONG LongData;
USHORT ShortData;
UCHAR CharData;
} DataBuffer; //從設備驅動程序中得到的數據
LONG IoctlCode; //功能代碼
ULONG DataLength;
LONG Parameter1;
//調用IRP中的IRP _MJ_CREATE功能
hndFile = CreateFile(
″\\\\.\\SelfWin32Name″, // 打開設備文件″SelfWin32Name″
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hndFile == INVALID_HANDLE_VALUE)
{
printf(″Unable to open the device.\n″);
exit(1);
}
IoctlCode = IOCTL_Function1; //自定義功能代碼
Parameter1 = 1;
DataLength = sizeof(DataBuffer.CharData);
IoctlResult = DeviceIoControl(
hndFile, //設備文件句柄
IoctlCode,//功能代碼,對應IRP中的Parameter.
//DeviceIoControl.IoControlCode域&Parameter1,//傳遞到驅動程序的參數緩沖區(qū),對應
//IRP中的AssociatedIrp.SystemBuffer
sizeof(Parameter1), //參數緩沖區(qū)長度
&DataBuffer, //從驅動程序傳出的數據緩沖區(qū)
DataLength, //緩沖區(qū)長度
&ReturnedLength, //返回的實際緩沖區(qū)長度
NULL //等待,直到操作完成
);
if (!CloseHandle(hndFile)) //關閉設備
{
printf(″Failed to close device.\n″);
}
}
以上介紹了Windows NT4.0設備驅動程序開發(fā)中的一般性過程。用戶可利用NT SDK 及DDK開發(fā)工具包,并根據自身需要,對以上核心代碼進行擴充完成所需任務。
參考文獻
1 Art Baker. Windows NT設備驅動程序設計指南.
2 Microsoft. Windows NT DDK聯機手冊.