核心執行緒
在驅動中生成的執行緒一般是系統執行緒。系統執行緒所在的程式名為“System”。
NTSTATUS
PsCreateSystemThread(
OUT PHANDLE ThreadHandle, //用來返回控制程式碼,放入一個控制程式碼指標即可
IN ULONG DesiredAccess, //一般總是填寫0
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, //NULL
IN HANDLE ProcessHandle OPTIONAL, //NULL
OUT PCLIENT_ID ClientId OPTIONAL, //NULL
IN PKSTART_ROUTINE StartRoutine, //用於該執行緒啟動的時候執行的函式,傳入函式名即可
IN PVOID StartContext); //用於傳入該函式的引數
執行緒的結束應該線上程中自己呼叫PsTerminateSystemThread來完成。此外得到的控制程式碼也必須要用ZwClose來關閉。關閉控制程式碼並不結束執行緒。
例子:
VOID MyThreadProc(PVOID context) //我的執行緒函式,傳入一個引數,這個引數是一個字串
{
PUNICODE_STRING str = (PUNICODESTRING)context;
KdPrint(("PrintInMyThread:%wZ\r\n",str));
PsTerminateSystemThread(STATUS_SUCCESS); //終止一個執行緒
}
VOID MyFunction()
{
UNICODE_STRING str = RTL_CONSTANT_STRING(L"Hello!");
HANDLE thread = NULL;
status = PsCreateSystemThread(&thread,0L,NULL,NULL,NULL,MyThreadProc,(PVOID)&str); //開始一個執行緒
if(!NT_SUCCESS(status))
{
//錯如處理
}
//如果成功了,可以繼續做自己的事,之後得到的控制程式碼要關閉
ZwClose(thread);
}
注意 :MyThreadProc執行的時候,MyFunction可能已經執行完畢了,str就會無效,再執行K的Print去列印str一定會藍屏。解決方法是在隊中分配str的空間,或者str必須在全域性空間中。
睡眠
#define DELAY_ONE_MICROSECOND (-10) //10個100納秒 = 1 微秒, -10表示相對時間
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
VOID MySleep(LONG msec)
{
LARGE_INTEGER my_interval;
my_interval.QuadPart = DELAY_ONE_MILLISECOND;
my_interval.QuadPart *= msec;
KeDelayExecutionThread(KernelMode, //表示在核心程式設計中使用
0, //是否允許執行緒報警(用於重新喚醒)
&my_interval); //表示要睡眠多久
}
事件
核心中的事件是一個資料結構。這個結構的指標可以當作一個引數傳入一個等待函式中。如果這個事件不被“設定”,則這個等待函式不會返回,這個執行緒被阻塞。如果這個事件被“設定”,則等待結束,可以繼續下去。
事件不需要銷燬。
可以發現,關於事件的操作這一部分(如事件的重設,同步等),其原理和MFC中是很類似的,只是MFC中封封裝了更好處理的API函式而已。
實際上等待執行緒結束並不一定要用事件。執行緒本身也可以當作一個事件來等待。
例子:
KEVENT event; //定義一個事件
KeInitializeEvent(&event,SyschronizationEvent,TRUE); //事件初始化,SynchoronizationEvent為“自動重設”事件:只有一個執行緒的wait可以通過,通過之後被自動重設,其他的執行緒就只能繼續等待了;若為NotificationEvent,這個事件必須手動重設KeResetEvent(&event)之後才能使用,否則所有等待執行緒都通過了。
KeWaitForSingleObject(&event,Executive,KernelMode,0,0); //等待這個事件event,直到這個事件被人設定
KeSetEvent(&event); //在另一個地方,設定這個事件,前面等待的地方將繼續執行
驅動與裝置和請求處理
#include <ntddk.h>
NTSTATUS
DriverEntry (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
return status;
}
函式DriverEntry是每個驅動程式中必須的。如同Win32應用程式裡的WinMain。
DriverEntry的第一個引數就是一個 DRIVER_OBJECT的指標。這個DRIVER_OBJECT結構就對應當前編寫的驅動程式。其記憶體是Windows系統已經分配的。
第二個引數RegistryPath是一個字串。代表一個登錄檔子鍵。這個子鍵是專門分配給這個驅動程式使用的。用於儲存驅動配置資訊到登錄檔中。
DRIVER_OBJECT中含有分發函式指標,分發函式指標的個數為IRP_MJ_MAXIMUM_FUNCTION,儲存在一個陣列中。這些函式用來處理髮到這個驅動的各種請求。Windows總是自己呼叫DRIVER_OBJECT下的分發函式來處理這些請求。所以編寫一個驅動程式,本質就是自己編寫這些處理請求的分發函式。
NTSTATUS
DriverEntry (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
ULONG i;
for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;++i)
{
DriverObject->MajorFunctions[i] = MyDispatchFunction; //分發函式定義,所有的分發函式指標都指向一個分發函式MyDispatchFunction中
}
…
}
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT device,PIRP irp) //處理髮送給device的irp請求
{
……
}
VOID MyDriverUnload(PDRIVER_OBJECT driver)
{
……
}
這個函式的地址設定到DriverObject->DriverUnload即可。
裝置與符號連結
如果驅動程式要和應用程式之間通訊,則應該生成裝置。此外還必須為裝置生成應用程式可以訪問的符號連結(就像檔案路徑一樣)。
“\\.\”意味後面是一個符號連結名。 目前生成裝置,請總是生成在\Device\目錄下。
例子:
#include <ntifs.h>
NTSTATUS DriverEntry(
PDRIVER_OBJECT driver,
PUNICODE_STRING reg_path)
{
NTSTATUS status;
PDEVICE_OBJECT device;
UNICODE_STRING device_name = RTL_CONSTANT_STRING("\\Device\\MyCDO"); //裝置名
UNICODE_STRING symb_link = RTL_CONSTANT_STRING("\\DosDevices\\MyCDOSL"); //符號連結名
status = IoCreateDevice( //生成裝置,這個裝置必須用系統許可權才能訪問,IoCreateDeviceSecure可以產生使用者許可權可以訪問的裝置
driver, //生成裝置的驅動物件
0, //當使用者需要在裝置上記錄一些額外的資訊,就要指定裝置擴充套件區記憶體的大小,以後就可以從DeviceObject->DeviceExtension中來獲取這些資訊了。
device_name, //裝置名字
FILE_DEVICE_UNKNOWN, //裝置型別
0,
FALSE,
&device);
if(!NT_SUCCESS(status))
return status;
status = IoCreateSymbolicLink(
&symb_link,
&device_name);
if(!NT_SUCCESS(status))
{
IoDeleteDevice(device);
return status;
}
device->Flags &= ~DO_DEVICE_INITIALIZENG; //裝置生成之後,開啟初始化完成標記
return status;
}
符號連結與使用者相關性
某個使用者穿件的符號連結只能被該使用者訪問,系統建立的符號連結可以被所有使用者訪問。下面的例子生成的符號連結總是隨時可以使用:
UNICODE_STRING device_name;
UNICODE_STRING symbl_name;
if(IoIsWdmVersionAvailable(1,0x10))
{
RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\Global\\SymbolicLinkName");
//如果是支援符號連結使用者相關性的版本的系統,一個使用者產生的符號連結只能被這一個使用者訪問,其它使用者則不能訪問,所以必須產生全域性符號
}
else
{
RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\SymbolicLinkName"); //如果系統不支援符號連結的使用者相關性,則直接建立符號連結即可,所有使用者都可以訪問
}
IoCreateSymbolicLink(&symbl_name,&device_name);
請求處理
應用程式為了和驅動通訊,首先必須開啟裝置。然後傳送或者接收資訊。最後關閉它。這至少需要三個IRP:第一個是開啟請求。第二個傳送或者接收資訊。第三個是關閉請求。
IRP的種類取決於主功能號。
應用層呼叫的API 驅動層收到的IRP主功能號-----即DRIVER_OBJECT中分發函式指標陣列中的索引。
IRP的主功能號在IRP的當前棧空間中;IRP總是傳送給一個裝置棧,到每個裝置上的時候擁有一個“當前棧空間”來儲存在這個裝置上的請求資訊。
這些功能都有應用層API引發,對應關係如下:
CreateFile IRP_MJ_CREATE
CloseHandle IRP_MJ_CLOSE
DeviceIoControl IRP_MJ_DEVICE_CONTROL
ReadFile IRP_MJ_READ
WriteFile IRP_MJ_WRITE
返回時一個IRP成功是一個三部曲:
(1)設定irp->IoStatus.Information為0;
(2)設定irp->IoStatus.Status的狀態;成功或者失敗
(3)呼叫IoCompleteRequest(irp,IO_NO_INCREMENT),這個函式完成IRP;
最後返回irp->IoStatus.Status即可。
開啟和關閉請求
下面的函式能夠實現開啟和關閉請求:
NTSTATUS
MyCreateClose(
IN PDEVICE_OBJECT device,
IN PIRP irp)
{
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(irp,IO_NO_INCREMENT);
return irp->IoStatus.Status;
}
驅動的分發函式設定如下:
DriverObject->MajorFunctions[IRP_MJ_CREATE] = MyCreateClose;
DriverObject->MajorFunctions[IRP_MJ_CLOSE] = MyCreateClose;
應用層資訊的傳入
可以使用WriteFile或者DeviceIoControl(雙向的);
DeviceIoControl:裝置控制介面,可以傳送一個帶有特定控制碼的IRP,同時提供輸入和輸出緩衝區;應用程式可以定義一個控制碼,然後把相應的引數填寫在輸入緩衝區中,同時可以從輸出緩衝區得到返回的更多資訊。
驅動獲得一個DeviceIoControl產生的IRP的時候,需要獲得當前的控制碼、輸入輸出緩衝區的位置和長度;控制碼必須預先用一個巨集定義:
#define MY_DVC_IN_CODE \
(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, \
0xa01, \ //自定義的值,其它照抄即可
METHOD_BUFFERED, \
FILE_READ_DATA|FILE_WRITE_DATA)
可以通過裝置的棧空間獲得三個要素。
驅動層資訊的傳出
應用程式開啟一個執行緒,通過呼叫DeviceIoControl(或者ReadFile)實現該功能,驅動沒有訊息的時候,則阻塞這個IRP的處理,等待有訊息的時候返回。
具體實現見《天書夜讀:從組合語言到windows核心開發》第92-93頁。
關於上述幾項內容的專題論述,請參見相關文件。[5,6]
參考
[1] http://www.cnblogs.com/phinecos/archive/2009/02/19/1393803.html
[2] http://www.cnblogs.com/qsilence/archive/2009/06/11/1501511.html
[3 http://msdn.microsoft.com/en-us/library/ff557565%28VS.85%29.aspx
[4] http://www.cnblogs.com/wanghao111/archive/2009/05/25/1489041.html
[5] Windows驅動程式設計基礎教程.doc
[6] 天書夜讀-從組合語言到windows核心程式設計(改)
[7] Windows DDK
[8] 天書夜讀——從組合語言到Windows核心程式設計
http://download.csdn.net/source/2754275
http://msdn.microsoft.com/en-us/library/ff557573%28VS.85%29.aspx