26、Windows驱动程序的同步处理(2)
4、内核模式下的同步对象
用户模式下用句柄来操作同步对象,而内核模式下可以获得同步对象的指针。每种同步对象在内核中均对应一种数据结构。
1)等待
KeWaitForMultipleObjects
KeWaitForSingleObject
如果超时则返回STATUS_TIMEOUT。如果是因为数组中其一个同步对象变为激发态,则函数的返回值减去STATUS_WAIT_0,就是激发的同步对象在数组中的索引号。
2)多线程
PsCreateSystemThread 包括用户线程与系统线程。用户线程属于当前进程(当前IO操作的发起者;如在IRP_MJ_READ的派遣函数中调用 PsCreateSystemThread创建用户线程,新线程属于调用ReadFile的进程)中的线程。系统线程一般是System线程。
创建的线程必须手动用PsTerminateSystemThread结束。PEPROCESS记录进程信息。
1 #include "ntddk.h"
2 VOID SystemThread(IN PVOID pContext)
3 {
4 KdPrint(("Enter SystemThread\n"));
5 PEPROCESS pEProcess = IoGetCurrentProcess();
6 PTSTR ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);
7 KdPrint(("this thread is run in %s process!\n", ProcessName));
8 KdPrint(("Leave SystemThread\n"));
9 PsTerminateSystemThread (NT_STATUS);
10 }
11
12 VOID MyProcessThread(IN PVOID pContext)
13 {
14 KdPrint(("Enter MyProcessThread\n"));
15 PEPROCESS pEProcess = IoGetCurrentProcess();
16 PTSTR ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);
17 KdPrint(("this thread is run in %s process!\n", ProcessName));
18 KdPrint(("Leave MyProcessThread\n"));
19 PsTerminateSystemThread (NT_STATUS);
20 }
21
22 void CreateThread_Test()
23 {
24 HANDLE hSystemThread, hMyThread;
25 NTSTATUS status = PsCreateSystemThread(&hSystemThread, 0, NULL, NULL, NULL, SystemThread, NULL);
26 NTSTATUS status = PsCreateSystemThread(&hMyThread, 0, NULL, NULL, NULL, MyProcessThread, NULL);
27 }
示例代码 P236
3)内核模式下的事件对象
KEVENT
KeInitializeEvent
1 void MyProcessThread(IN VOID pContext)
2 {
3 PKEVENT pEvent = (PKEVENT)pContext;
4 kdPrint(("Enter MyProcessThread\n"));
5 KeSetEvent(pEvent, IO_NO_INCREMENT, FALSE);
6 kdPrint(("Leave MyProcessThread\n"));
7 PsTerminateSystemThread (NT_STATUS);
8 }
9 void Test()
10 {
11 HANDLE hMyThread;
12 KEVENT kEvent;
13 KeInitializeEvent(&kEvent, NotificationEvent, FALSE);
14 NTSTATUS status = PsCreateSystemThread(&hMyThread, 0, NULL, NtCurrentProcess(), NULL, MyProcessThread, &kEvent);
15 keWaitforSingleobject(&kEvent, Executive, KernalMode, FALSE, NULL);
16 }
示例代码 P237
4)驱动程序与应用程序交互事件对象
在用户模式下创建一个同步事件,然后用DeviceIoControl把事件句柄传递给驱动程序。句柄是与进程相关的,一个进程中的句柄只能在这个进程中有效,句柄相当于事件对象在进程中的索引。DDK中提供了如下函数:
ObReferenceObjectByHandle
ObDereferenceObject
ObReferenceObjectByHandle 在得到指针的同时,会为对象的指针维护一个计数,每次调用ObReferenceObjectByHandle会使计数加1;为使计数平衡,应当在后面适当时候调用ObDereferenceObject 来使计数减一。
1 int main()
2 {
3 HANDLE hDevice = CreateFile("\\\\.\\HelloDDK",
4 GENERIC_READ |GENERIC_WRITE,
5 0,
6 NULL,
7 OPEN_EXISTING,
8 FILE_ATTRIBUTE_NORMAL,
9 NULL);
10 if (hDevice == INVALID_HANDLE_VALUE)
11 {
12 printf("Failed to obtain file handle to device:%s with win32 error code:%d\n","MYWDMDevice", GetLastError());
13 return 1;
14 }
15 bool bRet;
16 DWORD dwOutput;
17 HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
18 //建立辅助线程
19 HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, Thread1, &hEvent, 0, NULL);
20 //将用户模式下的事件句柄传递给驱动
21 //IOCTL_TRANSMIT_EVENT是自己定义的CODE
22 bRet = DeviceIoControl(hDevice,
23 IOCTL_TRANSMIT_EVENT,
24 &hEvent,
25 sizeof(hEvent),
26 NULL,
27 0,
28 &dwOutput,
29 NULL);
30 WaitForSingleObject(hThread1, INFINITE);
31 CloseHandle(hDevice);
32 CloseHandle(hThread1);
33 CloseHandle(hEvent);
34 return 0;
35 }
36
37 NTSTATUS HelloDDKDeviceIoControl(IN PDEVICE_OBJECT pDevObj,
38 IN PIRP pIrp)
39 {
40 NTSTATUS status = STATUS_SUCESS;
41 KdPrint(("Enter HelloDDKDeviceIoControl\n"));
42 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
43 //获得输入参数大小
44 ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;
45 //获得输出参数大小
46 ULONG cbout = stack->Parameters.DeviceIoControl.OutPutBufferLength;
47 //获得IOCTL码
48 ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
49 ULONG info = 0;
50 switch (code)
51 {
52 case IOCTL_TRANSMIT_EVENT:
53 {
54 //得到应用程序传进来的事件
55 HANDLE hUserEvent = *(HANDLE*)pIrp->AssociatedIrp.SystemBuffer;
56 PKEVENT pEvent;
57 status = ObReferenceObjectByHandle(hUserEvent, EVENT_MODIFY_STATE, *ExEventObjecttype,
58 kernalmode,
59 (PVOID*)&pEvent, NULL);
60 keSetEvent(pEvent, IO_NO_INCREMENT, FALSE);
61 ObDereferenceObject(pEvent);
62 break;
63 }
64 default:
65 status = STATUS_INVALID_PARAMETER;
66 }
67 pIrp->IoStatus.Status = status;
68 pIrp->IoStatus.Information = info;
69 IoCompleteRequest(pIrp, IO_NO_INCREMENT);
70 return status;
71 }
示例代码 P238
5)驱动程序与驱动程序间交互事件对象
最简单的方法是创建一个有名字的事件对象,这样在另一个驱动程序中就可以根据名字来寻找到事件对象的指针。
6)内核模式下的信号灯
用户模式下,信号灯对象用句柄表示,在内核模式下,用ksemaphore结构表示。
KeInitializeSemaphore
KeReadStateSemaphore
KeReleaseSemaphore 释放信号灯会增加计数
KeWaitXXX可获取信号灯,如果成功获得,计数减一,否则等待。
示例代码 P240,基本原理同上,略
7)内核模式下的互斥体
KeInitializeMutex
KeReleaseMutex
1 VOID MyProcessThread1(IN PVOID pContext)
2 {
3 PKMUTEX pkMutex = (PKMUTEX)pContext;
4 keWaitforSingleobject(pkMutex, Executive, KernalMode, FALSE, NULL);
5 kdPrint(("Enter MyProcessThread1\n"));
6 //停止50ms
7 KeStallExecutionProcessor(50);
8 kdPrint(("Leave MyProcessThread1\n"));
9 KeReleaseMutex(pkMutex, FALSE);
10 PsTerminateSystemThread(STATUS_SUCCESS);
11 }
12
13 VOID MyProcessThread2(IN PVOID pContext)
14 {
15 PKMUTEX pkMutex = (PKMUTEX)pContext;
16 keWaitforSingleobject(pkMutex, Executive, KernalMode, FALSE, NULL);
17 kdPrint(("Enter MyProcessThread2\n"));
18 //停止50ms
19 KeStallExecutionProcessor(50);
20 kdPrint(("Leave MyProcessThread2\n"));
21 KeReleaseMutex(pkMutex, FALSE);
22 PsTerminateSystemThread(STATUS_SUCCESS);
23 }
24 #pragma PAGEDCODE
25 VOID Test()
26 {
27 HANDLE hMyThread1, hMyThread2;
28 KMUTEX hMutex;
29 KeInitializeMutex(&hMutex, 0);
30 NTSTATUS status = PsCreateSystemThread(&hMyThread1, 0, NULL, NtCurrentProcess(), NULL, MyProcessThread1, &hMutex);
31 NTSTATUS status2 = PsCreateSystemThread(&hMyThread2, 0, NULL, NtCurrentProcess(), NULL, MyProcessThread2, &hMutex);
32 PVOID Pointer_Array[2];
33 ObReferenceObjectByHandle(hMyThread1, 0, NULL,
34 kernalmode,
35 Pointer_Array[0],
36 NULL);
37 ObReferenceObjectByHandle(hMyThread2, 0, NULL,
38 kernalmode,
39 Pointer_Array[1],
40 NULL);
41 KeWaitForMultipleObjects(2, Pointer_Array, WaitAll, Executive, KernelMode,
42 FALSE, NULL, NULL);
43 ObDereferenceObject(Pointer_Array[0]);
44 ObDereferenceObject(Pointer_Array[1]);
45 kdPrint(("After KeWaitForMultipleObjects\n"));
46 }
示例代码 P242
8)快速互斥体
比普通互斥体快。不过不能递归获取互斥体对象。普通互斥体数据结构是MUTEX,快带互斥体是FAST_MUTEX。
示例代码 P244 基本原理同上,只是数据结构变了,此略
4、其他同步方法
1)使用自旋锁
KeInitializeSpinLock
KeReleaseSpinLock
1 #include "windows.h"
2 #include "process.h"
3 #include "stdio.h"
4 #include "winioctl.h"
5 #define "..\NT_Driver\Ioctls.h"
6
7 UINT WINAPI Thread1(LPVOID pContext)
8 {
9 BOOL bRet;
10 DWORD dwOutput;
11 bRet = DeviceIoControl(*(PHANDLE)pContext,
12 IOCTL_MYDEFINITION,
13 NULL,
14 0,
15 NULL,
16 0,
17 &dwOutput,
18 NULL);
19 return 0;
20 }
21
22 int main()
23 {
24 HANDLE hDevice = CreateFile("\\\\.\\HelloDDK",
25 GENERIC_READ |GENERIC_WRITE,
26 0,
27 NULL,
28 OPEN_EXISTING,
29 FILE_ATTRIBUTE_NORMAL,
30 NULL);
31 if (hDevice == INVALID_HANDLE_VALUE)
32 {
33 printf("Failed to obtain file handle to device:%s with win32 error code:%d\n","MYWDMDevice", GetLastError());
34 return 1;
35 }
36 HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, Thread1, &hDevice, 0, NULL);
37 HANDLE hThread2 = (HANDLE)_beginthreadex(NULL, 0, Thread1, &hDevice, 0, NULL);
38 WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
39 CloseHandle(hThread1);
40 CloseHandle(hThread2);
41 CloseHandle(hDevice);
42 return 0;
43 }
44
45 NTSTATUS HelloDDKDeviceIoControl(IN PDEVICE_OBJECT pDevObj,
46 IN PIRP pIrp)
47 {
48 ASSERT(KeGetCurrentIrpl() == PASSIVE_LEVLE));
49 PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
50 KIRQL oldirql;
51 KeAcquireSpinLock(&pdx->My_SpinLock, &oldirql);
52 //...开始
53 ASSERT(KeGetCurrentIrpl() == DISPATCH_LEVEL));
54 pIrp->IoStatus.Status = STATUS_SUCCESS;
55 pIrp->IoStatus.information = 0;
56 IoCompleteRequest(pIrp, IO_NO_INCREMENT);
57 NTSTATUS status = STATUS_SUCESS;
58 //...结束
59 KeReleaseSpinLock(&pdx->My_SpinLock, oldirql);
60 return status;
61 }
示例代码 P245
2)互锁操作进行同步
在多线程操作中,对于全局变量的自增、自减等操作,可能不同时候操作,结果是不一样的。因为一个自增等操作翻译成汇编是多条语句,多个线程访问导致了不可重入性。解决方法一是通过自旋锁等同步操作,另外一个方法,便是下面介绍的互锁操作。
保证操作的原子性。DDK提供了如下函数:
InterlockedXX
ExInterlockedXX
1 int number = 0;
2 void Foo()
3 {
4 KeAcquireSpinLock(...)
5 number++;
6 KeReleaseSpinLock(...)
7 }
8
9 int number2 = 0;
10 void Foo2()
11 {
12 InterlockedIncrement(&number2);//原子方式的自增
13 }
前者不通过自旋锁实现,内部不会提升IRQL,可以操作非分页数据和分页数据;后者是通过自旋锁实现的,需要程序员提供一个自旋锁,不能操作分页内存的数据。
ExInterlockedXX
内核函数 |
功能 |
ExInterlockedAddLargeInteger |
64位整数加法互锁操作 |
ExInterlockedAddLargeStatistic |
64位整数加法互锁操作 |
ExInterlockedAddUlong |
32位整数加法互锁操作 |
ExInterlockedAllocateFromZone |
分配互锁操作 |
ExInterlockedCompareExchange64 |
两个32位整数互换互锁操作 |
ExInterlockedDecrementLong |
32位整数减法互锁操作 |
ExInterlockedExchangeAddLargeInteser |
64为整数加法互锁操作 |
ExInterlockedExchangeUlong |
两个整数互换互锁操作 |
ExInterlockedFlushSList |
删除链表全部元素的互锁操作 |
ExInterlockedIncrementLong |
32位整数自增互锁操作 |
ExInterlockedInsertHeadList |
插入双向链表互锁操作 |
ExInterlockedInsertTailList |
插入双向链表互锁操作 |
ExInterlockedPopEntryList |
删除单向链表互锁操作 |
ExInterlockedPopEntrySList |
删除单向链表互锁操作 |
ExInterlockedPushEntryList |
插入单向链表互锁操作 |
ExInterlockedPushEntrySList |
插入单向链表互锁操作 |
ExInterlockedRemoveHeadList |
插入双向链表互锁操作 |
InterlockedXX
内核函数 |
功能 |
InterlockedCompareExchange |
比较互锁操作 |
InterlockedCompareExchangePointer |
比较互锁操作 |
InterlockedDecrement |
整型自减互锁操作 |
InterlockedExchange |
整型交换互锁操作 |
InterlockedExchangeAdd |
两个整型相加互锁操作 |
InterlockedExchangePinter |
为指针赋值互锁操作 |
InterlockedIncrement |
整型自增互锁操作 |