Windows 多线程创建&同步 创建线程 PsCreateSystemThread
是一个用于创建内核模式线程的函数。
1 2 3 4 5 6 7 8 9 NTSTATUS PsCreateSystemThread ( OUT PHANDLE ThreadHandle, IN ULONG DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN HANDLE ProcessHandle OPTIONAL, OUT PCLIENT_ID ClientId OPTIONAL, IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext ) ;
**ThreadHandle (OUT PHANDLE)**:
类型:输出参数,指向一个句柄变量。
描述:当线程创建成功时,此参数将返回线程的句柄。你可以使用此句柄来操作线程,例如关闭线程句柄或获取线程状态。
**DesiredAccess (IN ULONG)**:
类型:输入参数。
描述:指定所需的访问权限。这通常是一个访问掩码,例如 THREAD_ALL_ACCESS
。这个参数决定了调用者对线程的访问权限。
**ObjectAttributes (IN POBJECT_ATTRIBUTES OPTIONAL)**:
类型:可选输入参数。
描述:指向一个 OBJECT_ATTRIBUTES
结构,它指定线程对象的属性。通常为 NULL
,除非你需要设置特定的对象属性。
**ProcessHandle (IN HANDLE OPTIONAL)**:
类型:可选输入参数。
描述:指定创建线程的进程句柄。通常为 NULL
,表示线程将被创建在系统进程上下文中。如果指定了某个进程句柄,则线程将被创建在该进程中。
**ClientId (OUT PCLIENT_ID OPTIONAL)**:
类型:可选输出参数。
描述:如果不为 NULL
,则此参数将返回线程的客户端 ID,包括进程 ID 和线程 ID。
**StartRoutine (IN PKSTART_ROUTINE)**:
类型:输入参数。
描述:指向线程入口函数的指针。当线程开始执行时,将调用此函数。入口函数的原型应为 VOID (*PKSTART_ROUTINE)(PVOID StartContext)
。
**StartContext (IN PVOID)**:
类型:输入参数。
描述:传递给 StartRoutine
的上下文参数。可以为任意类型的指针,在线程入口函数中使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <header.h> VOID ThreadEntryRoutine (PVOID Context) { UNREFERENCED_PARAMETER (Context); DbgPrint ("System thread is running\n" ); LARGE_INTEGER interval; interval.QuadPart = -10 * 1000 * 1000 ; KeDelayExecutionThread (KernelMode, FALSE, &interval); DbgPrint ("System thread is exiting\n" ); PsTerminateSystemThread (STATUS_SUCCESS); } NTSTATUS CreateSystemThreadExample (PHANDLE pThreadHandle) { NTSTATUS status; status = PsCreateSystemThread ( pThreadHandle, THREAD_ALL_ACCESS, NULL , NULL , NULL , ThreadEntryRoutine, NULL ); if (NT_SUCCESS (status)) { DbgPrint ("System thread created successfully\n" ); } else { DbgPrint ("Failed to create system thread\n" ); } return status; } HANDLE g_ThreadHandle = NULL ; NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER (RegistryPath); NTSTATUS status; DriverObject->DriverUnload = DriverUnloadRoutine; status = CreateSystemThreadExample (&g_ThreadHandle); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to create thread in DriverEntry\n" ); return status; } return STATUS_SUCCESS; } VOID DriverUnloadRoutine (IN PDRIVER_OBJECT DriverObject) { UNREFERENCED_PARAMETER (DriverObject); if (g_ThreadHandle) { NTSTATUS status = ZwWaitForSingleObject (g_ThreadHandle, FALSE, NULL ); if (status == STATUS_SUCCESS) { DbgPrint ("System thread has exited\n" ); } else { DbgPrint ("Failed to wait for system thread to exit\n" ); } ZwClose (g_ThreadHandle); g_ThreadHandle = NULL ; } DbgPrint ("Driver unloaded\n" ); }
KeDelayExecutionThread
是 Windows 内核模式中的一个函数,用于使当前线程进入等待状态一段指定的时间。这个函数在内核模式下运行,主要用于实现线程的延迟或睡眠操作。
将 LARGE_INTEGER
的 QuadPart
成员设置为负数是为了指定相对时间,使线程从当前时间起等待指定的时间长度。如果你希望线程等待绝对时间点到达,则应将 QuadPart
设置为正值。相对时间通常更直观和常用,因为它表示从当前时间起的等待时长。
内核模式下创建的线程必须使用PsTerminateSystemThread
强制结束线程,否则该线程无法自动退出
对应三环的内核同步 同步对象的等待 应用层:WaitForSingleObject和WaitForMultipleObjects 内核层:KeWaitForSingleObject和KeWaitForMultipleObjects
同步对象的状态通常有两种:激发态(Signaled)和非激发态(Non-signaled)。
激发态 :同步对象在激发态时,等待这个对象的线程会被唤醒。比如,一个事件对象在被设置为激发态时,所有等待这个事件的线程都会继续执行。
非激发态 :同步对象在非激发态时,等待这个对象的线程会进入等待状态,直到对象变为激发态或超时。
KeWaitForSingleObject
如果等待的同步对象变为激发态,这个函数会退出睡眠状态,并返回STATUS_SUCCESS 如果这个函数因为超时而退出,则返回STATUS_TIMEOUT
KeWaitForMultipleObjects
:如果这个函数因为超时而退出,返回STATUS_TIMEOUT 如果数组中其中一个同步对象变为激发态,这个函数返回的状态码减去STATUS_WAIT_0就是激发的同步对象在数组中的索引号
事件对象Event 应用层:CreateEvent 内核层:KeInitializeEvent
注意: 如果创建的事件对象是通知事件,当事件对象变为激发态时,程序员需手动将其改为未激发态。 如果创建的事件对象是同步事件,当事件对象变为激发态时(遇到KeWaitForXXX等内核函数),事件对象自动变回未激发态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER (RegistryPath); UNREFERENCED_PARAMETER (DriverObject); KeInitializeEvent (&Event, NotificationEvent, FALSE); NTSTATUS status = PsCreateSystemThread (&ThreadHandleA, THREAD_ALL_ACCESS, NULL , NULL , NULL , ThreadA, NULL ); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to create Thread A\n" ); return status; } status = PsCreateSystemThread (&ThreadHandleB, THREAD_ALL_ACCESS, NULL , NULL , NULL , ThreadB, NULL ); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to create Thread B\n" ); ZwClose (ThreadHandleA); return status; } ZwClose (ThreadHandleA); ZwClose (ThreadHandleB); return STATUS_SUCCESS; } VOID ThreadA (PVOID Context) { UNREFERENCED_PARAMETER (Context); DbgPrint ("Thread A: Initializing resource...\n" ); LARGE_INTEGER interval; interval.QuadPart = -10 * 1000 * 1000 ; KeDelayExecutionThread (KernelMode, FALSE, &interval); KeSetEvent (&Event, IO_NO_INCREMENT, FALSE); DbgPrint ("Thread A: Resource initialized, event set.\n" ); PsTerminateSystemThread (STATUS_SUCCESS); } VOID ThreadB (PVOID Context) { UNREFERENCED_PARAMETER (Context); DbgPrint ("Thread B: Waiting for resource initialization...\n" ); NTSTATUS status = KeWaitForSingleObject (&Event, Executive, KernelMode, FALSE, NULL ); if (status == STATUS_SUCCESS) { DbgPrint ("Thread B: Resource initialized, proceeding with execution.\n" ); } PsTerminateSystemThread (STATUS_SUCCESS); }
应用程序和驱动如何使用同一个事件对象
事件对象在用户模式使用句柄代表,在驱动下使用KEVENT数据结构代表
在用户模式创建一个同步事件 然后使用DeviceIoControl将同步事件的句柄传递给驱动 驱动程序在通过DeviceIoContol的IRP派遣函数获得同步事件的句柄 驱动程序调用ObReferenceObjectByHandle将句柄转化为实际的事件对象指针(此时该事件对象内部维护的一个引用计数将加1) 驱动程序使用此事件对象指针进行同步等自定义操作 使用完毕后,调用ObDereferenceObject函数(将这个事件对象内部维护的引用计数减1)
步骤如下:
在用户模式创建同步事件 :
应用程序在用户模式中使用 CreateEvent
函数创建一个同步事件,并获取到事件对象的句柄。
通过 DeviceIoControl 传递句柄给驱动 :
应用程序通过 DeviceIoControl
函数将事件对象的句柄传递给驱动程序。这可以通过向驱动程序发送 IOCTL(Input/Output Control)请求来实现。
驱动程序处理 IOCTL 请求 :
驱动程序在其 IRP (I/O Request Packet) 派遣函数中处理来自应用程序的 IOCTL 请求,从中获取到事件对象的句柄。
使用 ObReferenceObjectByHandle
获取事件对象指针 :
驱动程序使用 ObReferenceObjectByHandle
函数将从用户空间接收到的事件对象句柄转换为实际的 KEVENT
数据结构指针。这个函数调用会增加事件对象的引用计数。
驱动程序使用事件对象进行操作 :
驱动程序通过这个指针进行需要的同步操作,比如等待事件的触发 (KeWaitForSingleObject
) 或者设置事件为激发状态 (KeSetEvent
)。
**使用完毕后调用 ObDereferenceObject
**:
驱动程序在不再需要事件对象指针时,使用 ObDereferenceObject
函数将事件对象的引用计数减少,确保正确释放。
效果图:
三环代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 #include <windows.h> #include <iostream> using namespace std;void PrintErrorMessage (DWORD errorCode) { LPVOID lpMsgBuf; FormatMessageW ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL , errorCode, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&lpMsgBuf, 0 , NULL ); wprintf (L"Error code %d: %s\n" , errorCode, (LPWSTR)lpMsgBuf); LocalFree (lpMsgBuf); } int main () { HANDLE hDevice = CreateFile (L"\\\\.\\2024_07_18_link" , GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hDevice == INVALID_HANDLE_VALUE) { DWORD error = GetLastError (); std::cerr << "Failed to open device. Error: " << error << std::endl; if (error == ERROR_FILE_NOT_FOUND) { std::cerr << "The system cannot find the file specified." << std::endl; } else if (error == ERROR_ACCESS_DENIED) { std::cerr << "Access is denied." << std::endl; } else if (error == ERROR_INVALID_PARAMETER) { std::cerr << "The parameter is incorrect." << std::endl; } system ("pause" ); return 1 ; } std::cout << "Device opened successfully" << std::endl; HANDLE hEvent = CreateEvent (NULL , FALSE, TRUE, L"MyEvent" ); if (hEvent == NULL ) { std::cerr << "Failed to create event. Error: " << GetLastError () << std::endl; system ("pause" ); return 1 ; } #define IOCTL_CUSTOM_FUNC1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_CUSTOM_FUNC2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) DWORD bytesReturned; cout << "事件的句柄是0x" << hex << hEvent << endl; if (!DeviceIoControl (hDevice, IOCTL_CUSTOM_FUNC1, &hEvent, sizeof (HANDLE), NULL , 0 , &bytesReturned, NULL )) { PrintErrorMessage (GetLastError ()); CloseHandle (hDevice); CloseHandle (hEvent); return 1 ; } while (1 ) { WaitForSingleObject (hEvent, INFINITE); cout << "已释放Event" << endl; DWORD result=MessageBox (NULL , NULL , NULL , NULL ); if (result == IDCANCEL) { break ; } SetEvent (hEvent); } CloseHandle (hDevice); CloseHandle (hEvent); system ("pause" ); return 0 ; }
驱动代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 #include "header.h" PKEVENT g_Event = NULL ; HANDLE hUserEvent = NULL ; HANDLE g_ThreadHandle = NULL ; VOID ThreadEntryRoutine (PVOID Context) { UNREFERENCED_PARAMETER (Context); LARGE_INTEGER interval; interval.QuadPart = -10 * 1000 * 1000 ; KeDelayExecutionThread (KernelMode, FALSE, &interval); KdBreakPoint (); while (1 ) { KeWaitForSingleObject (g_Event, Executive, KernelMode, FALSE, NULL ); KdPrint (("拿到Event!\n" )); interval.QuadPart = -10 * 1000 * 1000 ; KeDelayExecutionThread (KernelMode, FALSE, &interval); KeSetEvent (g_Event, IO_NO_INCREMENT, FALSE); } PsTerminateSystemThread (STATUS_SUCCESS); } NTSTATUS CreateSystemThreadExample (PHANDLE pThreadHandle) { NTSTATUS status; status = PsCreateSystemThread ( pThreadHandle, THREAD_ALL_ACCESS, NULL , NULL , NULL , ThreadEntryRoutine, NULL ); if (NT_SUCCESS (status)) { DbgPrint ("System thread created successfully\n" ); } else { DbgPrint ("Failed to create system thread\n" ); } return status; } NTSTATUS MyDeviceControlRoutine (PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION irpSp; PVOID inputBuffer; ULONG inputBufferLength; ULONG ioControlCode; NTSTATUS ntStatus = STATUS_SUCCESS; LARGE_INTEGER interval; UNREFERENCED_PARAMETER (DeviceObject); interval.QuadPart = -10 * 1000 * 1000 ; KeDelayExecutionThread (KernelMode, FALSE, &interval); irpSp = IoGetCurrentIrpStackLocation (Irp); inputBuffer = Irp->AssociatedIrp.SystemBuffer; inputBufferLength = irpSp->Parameters.DeviceIoControl.InputBufferLength; ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode; __try { if (MmIsAddressValid (inputBuffer)) { KdPrint (("地址正确!" )); } else { KdPrint (("Invalid user buffer address\n" )); Irp->IoStatus.Status = STATUS_INVALID_USER_BUFFER; Irp->IoStatus.Information = 0 ; IoCompleteRequest (Irp, IO_NO_INCREMENT); return STATUS_INVALID_USER_BUFFER; } } __except (EXCEPTION_EXECUTE_HANDLER) { KdPrint (("Exception occurred: Access violation\n" )); Irp->IoStatus.Status = GetExceptionCode (); Irp->IoStatus.Information = 0 ; return Irp->IoStatus.Status; } switch (ioControlCode) { case IOCTL_CUSTOM_FUNC1: { hUserEvent = *(HANDLE*)inputBuffer; NTSTATUS status = ObReferenceObjectByHandle (hUserEvent, EVENT_MODIFY_STATE, *ExEventObjectType, KernelMode, (PVOID*)&g_Event, NULL ); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to reference event object from user mode handle. Status: %08x\n" , status); break ; } else { KdPrint (("拿到内核事件对象!" )); } status = CreateSystemThreadExample (&g_ThreadHandle); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to create thread in DriverEntry\n" ); return status; } else { KdPrint (("创建线程成功!" )); } return STATUS_SUCCESS; } default : { ntStatus = STATUS_INVALID_DEVICE_REQUEST; break ; } } Irp->IoStatus.Status = ntStatus; IoCompleteRequest (Irp, IO_NO_INCREMENT); return ntStatus; } NTSTATUS CreateDispatch ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { DeviceObject; Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0 ; IoCompleteRequest (Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER (RegistryPath); KdPrint (("Create!" )); DriverObject->DriverUnload = DriverUnloadRoutine; DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateDispatch; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDeviceControlRoutine; NTSTATUS status; PDEVICE_OBJECT DeviceObject = NULL ; UNICODE_STRING DeviceName; RtlInitUnicodeString (&DeviceName, L"\\Device\\MyDevice" ); status = IoCreateDevice ( DriverObject, 0 , &DeviceName, FILE_DEVICE_UNKNOWN, 0 , FALSE, &DeviceObject ); if (!NT_SUCCESS (status)) { KdPrint (("Failed to create device: %X\n" , status)); return status; } KdPrint (("Device created successfully\n" )); UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING (L"\\??\\2024_07_18_link" ); status = IoCreateSymbolicLink (&symbolicLink, &DeviceName); if (!NT_SUCCESS (status)) { KdPrint (("Failed to create device: %X\n" , status)); return status; } KdPrint (("Device created successfully\n" )); return STATUS_SUCCESS; } VOID DriverUnloadRoutine (IN PDRIVER_OBJECT DriverObject) { NTSTATUS status = ObDereferenceObject (&g_Event); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to Deference event object .Status: %08x\n" , status); } else { KdPrint (("对象的引用计数成功减少!" )); } if (DriverObject->DeviceObject != NULL ) { UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING (L"\\??\\2024_07_18_link" ); IoDeleteSymbolicLink (&symbolicLink); IoDeleteDevice (DriverObject->DeviceObject); } if (g_Event!=NULL ) { ObDereferenceObject (g_Event); g_Event = NULL ; } DbgPrint ("Driver unloaded\n" ); }
写这些代码确实遇到不少坑:
1.三环要用DeviceIoControl传递数据,驱动程序不仅要注册IRP_MJ_DEVICE_CONTROL还要注册IRP_MJ_CREATE,否则会报错,呜呜呜,搞好久
2.三环用DeviceIoControl传的句柄貌似只能在IRP_MJ_DEVICE_CONTROL事件处理函数有效??我创了一个子线程,调用ObReferenceObjectByName报错句柄无效,但是实际上句柄值是一样的….又搞了好久…
3.HANDLE hEvent = CreateEvent(NULL, FALSE, TRUE, L”MyEvent”);第三个参数是决定事件Event一开始的状态的,FALSE表示一开始无信号,TRUE表示一开始有信号
参考:驱动程序和驱动程序使用同一个事件对象 场景:驱动程序A的派遣函数需要和驱动程序B的派遣函数进行同步 问题:驱动A如何获取驱动B所创建的事件对象 解决:驱动B创建一个带有名字的事件对象,这样驱动A即可通过名字寻找到事件对象的指针
创建有名事件对象:IoCreateNotificatiionEvent(创建通知事件对象)和IoCreateSynchronizationEvent(创建同步事件对象)
这两个函数得到的是事件对象的句柄,要想进一步得到事件对象指针,还需调用ObReferenceObjectByHandle 内核中不存在指定名称的事件对象,这两个函数会创建;如果已经存在指定名称的事件对象,则会打开
信号灯Semaphore 应用层:CreateSemaphore 内核层:KeInitializeSemaphore对信号对象初始化
Semaphore:需要初始化的内核对象信号灯的指针 Count:初始化时信号灯的计数 Limit:指明信号灯计数的上限值
读取信号灯当前的计数值:KeReadStateSemaphore 释放信号灯,增加计数值:KeReleaseSemaphore(可指定增加计数值的数目) 获得信号灯,减少计数值:KeWaitXXX系列函数(如果能获得信号灯,则计数减1,否则陷入等待)
示例代码:
一共有四个线程,要求同一时间只允许2个线程同时进行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 VOID ThreadFunction (PVOID Context) { UNREFERENCED_PARAMETER (Context); NTSTATUS status = KeWaitForSingleObject (&Semaphore, Executive, KernelMode, FALSE, NULL ); if (status == STATUS_SUCCESS) { DbgPrint ("Thread %d: Acquired semaphore, performing task...\n" , PsGetCurrentThreadId ()); LARGE_INTEGER interval; interval.QuadPart = -10 * 1000 * 1000 ; KeDelayExecutionThread (KernelMode, FALSE, &interval); KeReleaseSemaphore (&Semaphore, IO_NO_INCREMENT, 1 , FALSE); DbgPrint ("Thread %d: Released semaphore.\n" , PsGetCurrentThreadId ()); } PsTerminateSystemThread (STATUS_SUCCESS); } VOID ThreadFunction (PVOID Context) { UNREFERENCED_PARAMETER (Context); NTSTATUS status = KeWaitForSingleObject (&Semaphore, Executive, KernelMode, FALSE, NULL ); if (status == STATUS_SUCCESS) { DbgPrint ("Thread %d: Acquired semaphore, performing task...\n" , PsGetCurrentThreadId ()); LARGE_INTEGER interval; interval.QuadPart = -10 * 1000 * 1000 ; KeDelayExecutionThread (KernelMode, FALSE, &interval); KeReleaseSemaphore (&Semaphore, IO_NO_INCREMENT, 1 , FALSE); DbgPrint ("Thread %d: Released semaphore.\n" , PsGetCurrentThreadId ()); } PsTerminateSystemThread (STATUS_SUCCESS); } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER (RegistryPath); UNREFERENCED_PARAMETER (DriverObject); KeInitializeSemaphore (&Semaphore, 2 , 2 ); for (int i = 0 ; i < 4 ; ++i) { NTSTATUS status = PsCreateSystemThread (&ThreadHandles[i], THREAD_ALL_ACCESS, NULL , NULL , NULL , ThreadFunction, NULL ); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to create Thread %d\n" , i); for (int j = 0 ; j < i; ++j) { ZwClose (ThreadHandles[j]); } return status; } } for (int i = 0 ; i < 4 ; ++i) { ZwClose (ThreadHandles[i]); } return STATUS_SUCCESS; }
互斥体Mutex 应用层:CreateMutex 内核层:KeInitializeMutex
Mutex
:需要初始化的互斥体Level
:保留值,一般为0
获得互斥体:KeWaitXXX
系列函数 释放互斥体:KeReleaseMutex
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 VOID ThreadFunction (PVOID Context) { UNREFERENCED_PARAMETER (Context); NTSTATUS status = KeWaitForMutexObject (&Mutex, Executive, KernelMode, FALSE, NULL ); if (status == STATUS_SUCCESS) { DbgPrint ("Thread %d: Acquired mutex, accessing shared resource...\n" , PsGetCurrentThreadId ()); SharedResource++; DbgPrint ("Thread %d: Shared resource updated to %lu.\n" , PsGetCurrentThreadId (), SharedResource); KeReleaseMutex (&Mutex, FALSE); DbgPrint ("Thread %d: Released mutex.\n" , PsGetCurrentThreadId ()); } PsTerminateSystemThread (STATUS_SUCCESS); } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER (RegistryPath); UNREFERENCED_PARAMETER (DriverObject); KeInitializeMutex (&Mutex, 0 ); HANDLE ThreadHandleA, ThreadHandleB; NTSTATUS status; status = PsCreateSystemThread (&ThreadHandleA, THREAD_ALL_ACCESS, NULL , NULL , NULL , ThreadFunction, NULL ); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to create Thread A\n" ); return status; } status = PsCreateSystemThread (&ThreadHandleB, THREAD_ALL_ACCESS, NULL , NULL , NULL , ThreadFunction, NULL ); if (!NT_SUCCESS (status)) { DbgPrint ("Failed to create Thread B\n" ); ZwClose (ThreadHandleA); return status; } ZwClose (ThreadHandleA); ZwClose (ThreadHandleB); return STATUS_SUCCESS; }
使用自旋锁进行同步
驱动程序常使用自旋锁作为一种有效的同步机制 举例:应用程序打开一个设备后,使用多线程对设备进行读取,此时IRP_MJ_READ的派遣函数会并发进行,然而绝大部分设备没有响应并发请求的能力,此时需要使用自旋锁进行同步
内核特有的同步对象 驱动如果很慢,会导致整个系统变卡变慢,因此驱动程序对比三环程序很讲究效率,所以微软给内核多加了几种同步对象
自旋锁(Spin Lock) 特点
忙等待 :自旋锁是一种忙等待的锁,线程在获取锁时会不断地检查锁的状态,直到成功获取锁。不会主动让出 CPU 时间片。
低开销 :适合用于保护短时间内的临界区,因为自旋锁避免了线程上下文切换的开销。
非阻塞 :因为自旋锁不会阻塞线程,所以不会发生线程切换。
高优先级 :通常用于高优先级的线程中,以避免线程上下文切换的开销。
适用场景
保护临界区时间非常短的代码段。
中断上下文中使用,防止在中断处理过程中被其他中断打断。
什么是自旋?
“自旋”可以理解为“自我旋转”,这里的“旋转”指“循环”,比如 while 循环或者 for 循环。“自旋”就是自己在这里不停地循环,直到目标达成。而不像普通的锁那样,如果获取不到锁就进入阻塞。
对比自旋和非自旋的获取锁的流程
下面我们用这样一张流程图来对比一下自旋锁和非自旋锁的获取锁的过程
自旋锁并不会放弃 CPU 时间片,而是通过自旋等待锁的释放,也就是说,它会不停地再次地尝试获取锁,如果失败就再次尝试,直到成功为止。
我们再来看下非自旋锁,非自旋锁和自旋锁是完全不一样的,如果它发现此时获取不到锁,它就把自己的线程切换状态,让线程休眠,然后 CPU 就可以在这段时间去做很多其他的事情,直到之前持有这把锁的线程释放了锁,于是 CPU 再把之前的线程恢复回来,让这个线程再去尝试获取这把锁。如果再次失败,就再次让线程休眠,如果成功,一样可以成功获取到同步资源的锁。
可以看出,非自旋锁和自旋锁最大的区别,就是如果它遇到拿不到锁的情况,它会把线程阻塞,直到被唤醒。而自旋锁会不停地尝试。
首先,阻塞和唤醒线程都是需要高昂的开销的,如果同步代码块中的内容不复杂,那么可能转换线程带来的开销比实际业务代码执行的开销还要大。
在很多场景下,可能我们的同步代码块的内容并不多,所以需要的执行时间也很短,如果我们仅仅为了这点时间就去切换线程状态,那么其实不如让线程不切换状态,而是让它自旋地尝试获取锁,等待其他线程释放锁,有时我只需要稍等一下,就可以避免上下文切换等开销,提高了效率。
用一句话总结自旋锁的好处,那就是自旋锁用循环去不停地尝试获取锁,让线程始终处于 Runnable 状态,节省了线程状态切换带来的开销。
1 2 3 4 5 6 7 8 9 10 11 KSPIN_LOCK mySpinLock; KeInitializeSpinLock (&mySpinLock);KIRQL oldIrql; KeAcquireSpinLock (&mySpinLock, &oldIrql);KeReleaseSpinLock (&mySpinLock, oldIrql);
总之这个线程没结束,系统不放的,适合代码量少的,频繁的同步
资源锁(Resource Lock) 特点
阻塞等待 :当一个线程尝试获取资源锁时,如果锁已经被其他线程持有,该线程会进入休眠状态,直到锁被释放。这种方式避免了忙等待,节省了CPU资源。
适用于长临界区 :由于线程在等待时会进入休眠状态,因此资源锁适用于那些持锁时间较长的临界区代码。
上下文切换 :当锁被持有时,线程会进入休眠状态,导致上下文切换。
1 2 3 4 5 6 7 8 9 10 11 12 13 ERESOURCE myResource; ExInitializeResourceLite (&myResource);ExAcquireResourceExclusiveLite (&myResource, TRUE);ExReleaseResourceLite (&myResource);ExDeleteResourceLite (&myResource);
IRQL(中断请求级别) IRQL(Interrupt Request Level,中断请求级别)是Windows操作系统内核中的一个关键概念,用于管理和控制中断和线程调度。IRQL决定了当前代码的执行优先级,影响了哪些中断和线程可以被调度和处理。
定义 :IRQL是一个整数值,表示当前处理器在特定时间点的中断请求级别。
作用 :IRQL用于控制中断的处理顺序和线程的调度,确保高优先级的中断能够及时处理,防止低优先级的任务干扰高优先级任务的执行。
3. IRQL的作用
中断处理 :IRQL控制中断的优先级,确保高优先级的中断能够抢占低优先级的中断处理。例如,硬盘中断通常具有高优先级,以确保数据的快速读写。
线程调度 :操作系统根据线程的优先级和当前IRQL来决定线程的调度顺序。高优先级的线程可以在高IRQL下运行,以确保关键任务的及时完成。
同步机制 :IRQL用于实现内核同步机制,例如自旋锁。提高IRQL可以防止当前代码被中断,从而保护临界区。
4. IRQL的管理
提升IRQL :在某些情况下,内核代码需要提升IRQL以防止被中断。例如,在处理关键数据结构时,代码可能会将IRQL提升到DISPATCH_LEVEL以防止上下文切换。
降低IRQL :代码在完成关键任务后需要恢复之前的IRQL,以允许其他中断和线程执行。
IRQL检查 :内核提供了一些API用于检查和操作当前的IRQL,如KeGetCurrentIrql
、KeRaiseIrql
和KeLowerIrql
。
微软设计了 0~31级别,在程序内,可以用代码把自己的级别提高或者降低。那么有没有可能软件的级别比硬件高?
微软设计了软件只能是02级,而硬件有331级。这样即使软件权限提到最高,也不至于影响到硬件操作
例如硬件上的电源级别就是POWER_LEVEL 30,几乎是最高的了,那么代表软件在运行的时候,如果按下了电源键,不会管代码有没有运行完,就执行关机的指令了
软件的三个级别,分别是
1 2 3 #define PASSIVE_LEVEL 0 #define APC_LEVEL 1 #define DISPATCH_LEVEL 2
在DISPATCH_LEVEL运行的代码不能处理PASSIVE_LEVEL的异常。DISPATCH_LEVEL是一个较高的中断请求级别,用于调度和处理高优先级的任务。此时,许多系统功能是受限的,比如内存分页,操作文件和大多数的阻塞操作。 (级别太高了,不能切换线程去拿,例如文件,要调硬件的驱动,但是线程切不过去了)。在DISPATCH_LEVEL,内核代码不应执行会导致线程阻塞或需要较长时间完成的操作。 级别为1的APC_LEVEL 不能操作文件
在MSDN查API也会有显示,需要满足的IRQL
级别不对找Bug有点难受,毕竟不是参数问题,不是权限问题
所以如何知道自己的代码在什么级别?用一个API KeGetCurrentIrql();
提升,降低等级
1 2 3 4 5 6 7 8 9 10 11 12 13 void DispatchLevelFunction () { KIRQL oldIrql; KeRaiseIrql (DISPATCH_LEVEL, &oldIrql); KeLowerIrql (oldIrql); }
一般在写驱动程序的时候,在程序入口会写一个
PAGED_CODE()
是一个内核宏,用于确保某段代码只在 IRQL 为 PASSIVE_LEVEL 时运行。 是一个断言宏,用来检测级别对不对
内核程序中,写下这个代码就会蓝屏:
1 2 3 KIRQL oldIrql; KeRaiseIrql (DISPATCH_LEVEL, &oldIrql);return STATUS_UNSUCCESSFUL;
因为提升到DISPATCH_LEVEL这个级别,就和I/O管理器一个级别了,返回的不能被处理
提升到高 IRQL 后立即返回会导致系统在高 IRQL 上运行不允许分页或等待的代码,从而引发蓝屏。