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 |
整型自增互锁操作 |