同僚在看裝置驅動同步時,問了我一個事:如果驅動程式建立了一個裝置,在應用層是否允許多個程序同時打開這個裝置;如果允許,這種方式應用層和驅動的通信方式是否會互相影響?我不是很确定,寫了個測試代碼并把結果記錄下來。
1.我們在DriverEntry/AddDevice中調用IoCreateDevice建立裝置對象。這是IoCreateDevice的接口,倒數第二個參數用于設定裝置是否支援獨占式通路。一般情況下我們都會傳入FALSE(MSDN也建議使用這種方式)。
NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,
_In_ ULONG DeviceExtensionSize,
_In_opt_ PUNICODE_STRING DeviceName,
_In_ DEVICE_TYPE DeviceType,
_In_ ULONG DeviceCharacteristics,
_In_ BOOLEAN Exclusive,
_Out_ PDEVICE_OBJECT *DeviceObject
);
對于FALSE的情況,裝置支援多個程序通路:
這部分是驅動建立裝置的片段,驅動在響應IRP_MJ_CREATE的派遣函數中設定了一個定時器。
NTSTATUS SampleCharAddDevice(PDRIVER_OBJECT drvObj, PDEVICE_OBJECT pdo)
{
NTSTATUS status = STATUS_SUCCESS;
RtlInitUnicodeString(&fdoName, L"\\Device\\SampleChar");
RtlInitUnicodeString(&fdoSymName, L"\\DosDevices\\SampleChar");
status = IoCreateDevice(drvObj, sizeof(SampleCharDevContext),
&fdoName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE, &fdo);
if (!NT_SUCCESS(status))
{
return status;
}
...
}
這部分是應用層打開裝置的片段:
int main()
{
GetDeviceInterface(interfaceBuff);
hDev = CreateFileA(interfaceBuff,
GENERIC_ALL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hDev == INVALID_HANDLE_VALUE)
{
lstErr = GetLastError();
return 0;
}
while (1)
{
Sleep(1000);
}
}
這是同時運作兩個測試程式打開裝置的截圖。從截圖中可以看出,兩個程序在打開裝置時都沒有從失敗的分支退出。
2.修改代碼,試圖以獨占方式通路裝置。如果單獨在調用IoCreateDevice時把Exclusive改為TRUE,并不能實作這個功能,還需要修改inf檔案裝置安裝節AddReg部分:
AddReg中"HKR,,Exclusive,0x10001,1"的行為将會為裝置的系統資料庫硬體鍵(HKLM\SYSTEM\CurrentControl\Set\Enum\裝置總線\裝置号)添加一個值為1的Exclusive鍵。
之後再用兩個程序去打開裝置,後調用CreateFile的程序會調用失敗,并且驅動程式的IRP_MJ_CREATE派遣函數也沒有得到執行(換句話說是檔案系統讓CreateFile調用失敗的,而不是驅動程式在派遣函數中做了特殊處理)。
3.程序間互相影響.個人認為,因為派遣函數運作線上程上下文中,必然用了不同線程的核心棧。是以,在不同線程的核心棧中定義的局部變量,如:buf/spinlock/Kevent等,不會互相影響;如果派遣函數中要用到全局變量,則這些全局變量的初始化應該盡早在DriverEntry/AddDevice中做初始化,避免在派遣函數中重複初始化。至于R0和R3之間通信的IRP及IRP的參數,這是IO管理器為每次IO請求新建立的,是以也不會有影響。
1.
kd> ?? devCtx->timer
struct _KTIMER
+0x000 Header : _DISPATCHER_HEADER
+0x010 DueTime : _ULARGE_INTEGER 0x0
+0x018 TimerListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x020 Dpc : (null)
+0x024 Period : 0
2.
kd> ?? devCtx->timer
struct _KTIMER
+0x000 Header : _DISPATCHER_HEADER
+0x010 DueTime : _ULARGE_INTEGER 0x00000001`5ecb0982
+0x018 TimerListEntry : _LIST_ENTRY [ 0x86e86f80 - 0x82b34f94 ]
+0x020 Dpc : 0x85381564 _KDPC
+0x024 Period : 0
3.
kd> ?? devCtx->timer
struct _KTIMER
+0x000 Header : _DISPATCHER_HEADER
+0x010 DueTime : _ULARGE_INTEGER 0x00000001`65ffd3f7
+0x018 TimerListEntry : _LIST_ENTRY [ 0x851544c0 - 0x82b3503c ]
+0x020 Dpc : 0x85381564 _KDPC
+0x024 Period
4.
kd> ?? devCtx->timer
struct _KTIMER
+0x000 Header : _DISPATCHER_HEADER
+0x010 DueTime : _ULARGE_INTEGER 0x00000001`760536ef
+0x018 TimerListEntry : _LIST_ENTRY [ 0x863e2e90 - 0x82b349c4 ]
+0x020 Dpc : 0x85381564 _KDPC
+0x024 Period : 0
5.
kd> ?? &timeEvt
struct _KEVENT * 0x8a8eba48
+0x000 Header : _DISPATCHER_HEADER
6.
kd> ?? &timeEvt
struct _KEVENT * 0x8cec6a48
+0x000 Header : _DISPATCHER_HEADER
解釋一下:1-4是進入SampleCharCreateClose時,調用KeSetTimer設定定時器的windbg的輸出,這是一個全局變量,存放在裝置擴充區;5-6是局部變量KEVENT timeEvt;的變量位址。
1處 進入SampleCharCreateClose時定時器對象時,KTIMER僅做過初始化,各個域值都是0;調用KeSetTimer後再次進入SampleCharCreateClose,可以看到每次定時器的定時間隔都非0,且每次改動都會被儲存到下一次進入SampleCharCreateClose前。這可以說明多線程環境下,裝置對象擴充區中的變量會互相影響;
5.6處 進入SampleCharCreateClose操作的KEVENT對象都是棧變量,且每次生成的棧變量的位址都是不同的。是以,在一個線程上下文中的局部變量不會影響其他線程上下文中的局部變量。換言之,如果要做線程同步,絕對不能在派遣函數中新配置設定一個spinlock/KEVENT,這根本起不到同步的作用
參考: