多线程安全 总结就是
DISPATCH_LEVEL
:SpinLock
APC/PASSIVE
: 互斥:ERESOURCE/FAST_MUTEX 同步:KEVENT/KSEMAPHORE
R3/R0同步通讯:
KEVENT
常见函数的线程
调用源
运行环境
原因
DriverEntry,DriverUnload
单线程
这两个函数由系统进程的单一线程调用,不会出现多线程同时调用的情况
各种分发函数
多线程
分发函数不会和DriverEntry并发,但是可能和DriverUnload并发
完成函数
多线程
完成例程函数随时被未知的线程调用
各种NDIS回调函数
多线程
和完成函数相同
PsCreateSystemThread
PsCreateSystemThread
是 Windows 操作系统内核中的一个应用程序编程接口(API)。它主要用于在系统进程(System Process)的环境中创建一个新的线程。
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
:这是一个输出参数。当函数成功返回时,这个参数将包含新创建线程的句柄。这个句柄可以用于后续对线程的操作,比如等待线程结束、控制线程的优先级等。
DesiredAccess
:用于指定对新创建线程的访问权限。例如,可以指定THREAD_ALL_ACCESS
来获取对线程的完全访问权限,包括读取和修改线程的上下文、终止线程等操作。
ObjectAttributes
:这是一个可选参数,用于指定新线程对象的属性。它是一个指向OBJECT_ATTRIBUTES
结构的指针。如果设置为NULL
,系统将使用默认的属性来创建线程对象。
ProcessHandle
:也是一个可选参数。它用于指定新线程所属的进程句柄。如果设置为NULL
,新线程将在系统进程(System Process)中创建。
ClientId
:这是一个输出参数,用于接收新创建线程的客户端 ID。客户端 ID 是一个在系统范围内唯一标识线程的标识符,它包含了进程 ID 和线程 ID。
StartRoutine
:这是一个非常关键的参数,它是一个指向线程起始例程的指针。这个例程将在新创建的线程中执行,它定义了线程的主要功能。例如,可以在这里编写代码来执行一些后台任务,如数据收集、设备监控等。
StartContext
:这个参数用于向线程起始例程传递一个上下文参数。起始例程可以根据这个参数来获取一些初始化信息,从而正确地执行任务。
互斥体和事件的区别 此前一直对互斥体和事件有什么分别都是很懵逼的,今天查阅了相关资料,怕忘记赶紧记录一下
首先事事件能做到的事,互斥体做不到的事
场景1:
起两个线程,要轮流执行,此时就要使用事件
之前我天真的以为,用互斥体也可以实现,代码如下:
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 #include <windows.h> #include <iostream> HANDLE hMutex; int g_Max = 10 ;int g_Number = 0 ;DWORD WINAPI ThreadProduct (LPVOID pM) { for (int i = 0 ; i < g_Max; i++) { WaitForSingleObject (hMutex, INFINITE); g_Number = 1 ; DWORD id = GetCurrentThreadId (); printf ("生产者%d将数据%d放入缓冲区\n" , id, g_Number); ReleaseMutex (hMutex); } return 0 ; } DWORD WINAPI ThreadConsumer (LPVOID pM) { for (int i = 0 ; i < g_Max; i++) { WaitForSingleObject (hMutex, INFINITE); g_Number = 0 ; DWORD id = GetCurrentThreadId (); printf ("----消费者%d将数据%d放入缓冲区\n" , id, g_Number); ReleaseMutex (hMutex); } return 0 ; } int main (int argc, char * argv[]) { hMutex = CreateMutex (NULL , FALSE, NULL ); HANDLE hThread[2 ]; hThread[0 ] = ::CreateThread (NULL , 0 , ThreadProduct, NULL , 0 , NULL ); hThread[1 ] = ::CreateThread (NULL , 0 , ThreadConsumer, NULL , 0 , NULL ); WaitForMultipleObjects (2 , hThread, TRUE, INFINITE); CloseHandle (hThread[0 ]); CloseHandle (hThread[1 ]); CloseHandle (hMutex); return 0 ; }
虽然大部分情况是顺序的,但是多运行几次还是出现了这样的画面
出现这个的原因还得是因为线程释放了互斥体,但是由于时间片又在这个线程手里,马上又去获取了互斥体,所以造成以上的错误
但是事件却不会出现这个情况,应该是操作系统调度机制的问题,这里就不去深究了,反正确实会出现这个结果,所以遇到顺序执行的,用事件就对了
场景2:
假设有多个线程,它们在某个时刻需要同时开始工作,但是工作可以在同一个条件下开始。比如,当某个资源加载完毕后,所有的工作线程都可以同时开始工作。
此时,用事件能做到,But互斥体做不到,下面是实现的代码
注意CreateEvent(NULL, TRUE, FALSE, NULL);
,第二个参数 如果此参数为 TRUE ,则函数将创建手动重置事件对象
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 #include <windows.h> #include <iostream> #include <thread> HANDLE eventHandle; void worker (int id) { std::cout << "Thread " << id << " is waiting for the signal.\n" ; WaitForSingleObject (eventHandle, INFINITE); std::cout << "Thread " << id << " is now working.\n" ; } int main () { eventHandle = CreateEvent (NULL , TRUE, FALSE, NULL ); std::thread threads[5 ]; for (int i = 0 ; i < 5 ; ++i) { threads[i] = std::thread (worker, i); } std::cout << "All threads are now waiting for the signal.\n" ; Sleep (1000 ); std::cout << "Triggering the event to allow all threads to work...\n" ; SetEvent (eventHandle); for (int i = 0 ; i < 5 ; ++i) { threads[i].join (); } CloseHandle (eventHandle); return 0 ; }
事件 对象在此场景下充当了一个信号源,所有线程都等待这个事件。它们会在事件信号被设置后同时开始工作。
SetEvent
方法设置事件为有信号状态,使得所有等待的线程可以同时被唤醒。
这在互斥体 中做不到。互斥体是用来保证一个线程独占资源的,无法用于多个线程等待同一个信号并同时开始工作。
互斥体能做到,但是事件做不到
场景:
互斥体允许一个线程多次获取同一个互斥体,而不会死锁,这是事件对象做不到的。你可以通过互斥体的递归锁定来在递归函数中多次获得锁,而不需要担心死锁。
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 #include <windows.h> #include <iostream> HANDLE hMutex; void RecursiveFunction (int count) { WaitForSingleObject (hMutex, INFINITE); std::cout << "Entering function with count: " << count << std::endl; if (count > 0 ) { RecursiveFunction (count - 1 ); } std::cout << "Exiting function with count: " << count << std::endl; ReleaseMutex (hMutex); } int main () { hMutex = CreateMutex (NULL , FALSE, NULL ); RecursiveFunction (3 ); CloseHandle (hMutex); return 0 ; }
内核事件对象常用API 1.KeInitializeEvent
功能 :初始化 KEVENT
对象。
1 2 3 4 5 VOID KeInitializeEvent ( IN OUT PRKEVENT Event, IN EVENT_TYPE Type, IN BOOLEAN State ) ;
Event
: 指向 KEVENT
对象的指针。
Type
: 指定事件类型,通常为 NotificationEvent
(自动重置事件)或 SynchronizationEvent
(手动重置事件)。
State
: 初始状态,TRUE
表示事件对象初始时为已信号状态,FALSE
为未信号状态。
2. KeSetEvent
功能 :将 KEVENT
对象的状态设置为 已信号 (signaled),通知等待该事件的线程继续执行。
3.KeClearEvent
功能 :将 KEVENT
对象的状态设置为 无信号 (non-signaled),即清除事件的信号。
4. KeWaitForSingleObject
功能 :等待一个内核对象(如 KEVENT
)进入已信号状态,阻塞当前线程,直到事件发生。
5. KeWaitForMultipleObjects
功能 :等待多个内核对象中的任意一个进入已信号状态。
6. KeResetEvent
功能 :与 KeClearEvent
类似,KeResetEvent
也将 KEVENT
对象的状态设置为无信号。
利用事件进行R0 R3通讯 一般来说,会用DeviceIoControl进行通讯,但是用轮询的方式太消耗资源,所以我们一般创建一个同步对象,这样三环阻塞,让出CPU,等待同步对象激活,唤醒三环,这样可以提高CPU的利用率
代码示例 驱动代码:
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 #pragma once extern "C" { #include <ntifs.h> #include <ntddk.h> NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) ; VOID DriverUnloadRoutine (IN PDRIVER_OBJECT DriverObject) ; } #include "header.h" #define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__) UNICODE_STRING EventName; PDEVICE_OBJECT g_pDeviceObject = NULL ; #define CTRLCODE_BASE 0x8000 #define MYCTRL_CODE(i) \ CTL_CODE(FILE_DEVICE_UNKNOWN,CTRLCODE_BASE + i, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_PROCWATCH MYCTRL_CODE(0) typedef struct _DEVICE_EXTENSION { HANDLE hProcessHandle; PKEVENT ProcessEvent; HANDLE hParentId; HANDLE hProcessId; BOOLEAN bCreate; } DEVICE_EXTENSION, * PDEVICE_EXTENSION; DEVICE_EXTENSION Device_extension = { 0 }; VOID InitEvent (DEVICE_EXTENSION* pDeviceExtension) { RtlInitUnicodeString (&EventName, L"\\BaseNamedObjects\\ProcEvent" ); pDeviceExtension->ProcessEvent = IoCreateNotificationEvent (&EventName, &pDeviceExtension->hProcessHandle); if (pDeviceExtension->ProcessEvent) { DebugPrint ("创建对象成功!" ); } else { DebugPrint ("创建对象失败!" ); } KeClearEvent (pDeviceExtension->ProcessEvent); } VOID ProcessCreateMon (IN HANDLE hParentId, IN HANDLE PId, IN BOOLEAN bCreate) { PDEVICE_EXTENSION deviceExtension = (PDEVICE_EXTENSION)g_pDeviceObject->DeviceExtension; deviceExtension->hParentId = hParentId; deviceExtension->hProcessId = PId; deviceExtension->bCreate = bCreate; KeSetEvent (deviceExtension->ProcessEvent, 0 , FALSE); } NTSTATUS CommonDispatch (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { DeviceObject; Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0L ; IoCompleteRequest (Irp, 0 ); return Irp->IoStatus.Status; } typedef struct _ProcMonData { HANDLE hParentId; HANDLE hProcessId; BOOLEAN bCreate; }ProcMonData, * PProcMonData; NTSTATUS IoctrlDispatch (IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) { pDeviceObject; NTSTATUS ntStatus = STATUS_SUCCESS; PVOID pUserBuffer = NULL ; ULONG ulInputSize = 0 ; ULONG ulOutputSize = 0 ; PIO_STACK_LOCATION pIrpStack = NULL ; ULONG ulIoCtrlCode = 0 ; PProcMonData pProcMonData = NULL ; PDEVICE_EXTENSION pDeviceExtension = NULL ; pIrpStack = IoGetCurrentIrpStackLocation (pIrp); pUserBuffer = pIrp->AssociatedIrp.SystemBuffer; pProcMonData = (PProcMonData)pUserBuffer; ulIoCtrlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; ulInputSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; ulOutputSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; switch (ulIoCtrlCode) { case IOCTL_PROCWATCH: pDeviceExtension = (PDEVICE_EXTENSION)g_pDeviceObject->DeviceExtension; pProcMonData->bCreate = pDeviceExtension->bCreate; pProcMonData->hParentId = pDeviceExtension->hParentId; pProcMonData->hProcessId = pDeviceExtension->hProcessId; ulOutputSize = sizeof (DEVICE_EXTENSION); break ; default : ntStatus = STATUS_INVALID_PARAMETER; ulOutputSize = 0 ; break ; } pIrp->IoStatus.Status = ntStatus; pIrp->IoStatus.Information = ulOutputSize; PDEVICE_EXTENSION deviceExtension = (PDEVICE_EXTENSION)g_pDeviceObject->DeviceExtension; KeClearEvent (deviceExtension->ProcessEvent); DebugPrint ("ProcessCreateMon triggered event: ParentId = %p, ProcessId = %p, bCreate = %d\n" , pProcMonData->hParentId, pProcMonData->hProcessId, pProcMonData->bCreate); IoCompleteRequest (pIrp, IO_NO_INCREMENT); return ntStatus; } NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER (RegistryPath); DebugPrint (("Create!" )); KdBreakPoint (); DriverObject->DriverUnload = DriverUnloadRoutine; 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 ); DeviceObject->Flags |= DO_BUFFERED_IO; g_pDeviceObject = DeviceObject; if (!NT_SUCCESS (status)) { KdPrint (("Failed to create device: %X\n" , status)); return status; } KdPrint (("Device created successfully\n" )); UNICODE_STRING symbolicLink; RtlInitUnicodeString (&symbolicLink, L"\\??\\MyDevice_Link" ); status = IoCreateSymbolicLink (&symbolicLink, &DeviceName); if (!NT_SUCCESS (status)) { KdPrint (("Failed to create device: %X\n" , status)); IoDeleteDevice (DriverObject->DeviceObject); return status; } DebugPrint (("Device created successfully\n" )); InitEvent (&Device_extension); DeviceObject->DeviceExtension = &Device_extension; status = PsSetCreateProcessNotifyRoutine (ProcessCreateMon, FALSE); if (!NT_SUCCESS (status)) { IoDeleteDevice (DriverObject->DeviceObject); DebugPrint ("PsSetCreateProcessNotifyRoutine()\n" ); return status; } for (int i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = CommonDispatch; } DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoctrlDispatch; return STATUS_SUCCESS; } VOID DriverUnloadRoutine (IN PDRIVER_OBJECT DriverObject) { if (DriverObject->DeviceObject != NULL ) { PsSetCreateProcessNotifyRoutine (ProcessCreateMon, TRUE); UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING (L"\\??\\MyDevice_Link" ); IoDeleteSymbolicLink (&symbolicLink); IoDeleteDevice (DriverObject->DeviceObject); } DbgPrint ("Driver unloaded\n" ); }
三环代码:
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 #include <iostream> #include <windows.h> #include <psapi.h> #pragma comment(lib, "Psapi.lib" ) using namespace std;#define CTRLCODE_BASE 0x8000 #define MYCTRL_CODE(i) \ CTL_CODE(FILE_DEVICE_UNKNOWN,CTRLCODE_BASE + i, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_PROCWATCH MYCTRL_CODE(0) typedef struct _ProcMonData { HANDLE hParentId; HANDLE hProcessId; BOOLEAN bCreate; }ProcMonData, * PProcMonData; void PrintProcessStatus (ProcMonData procmondata) { if (procmondata.bCreate == TRUE) { printf ("有进程创建,PID:%llu\n" , procmondata.hProcessId); } else { printf ("有进程退出,PID:%llu\n" , procmondata.hProcessId); } } int main () { HANDLE hEvent = OpenEvent (SYNCHRONIZE, FALSE, L"Global\\ProcEvent" ); if (hEvent == NULL ) { printf ("Failed to open event: %lu\n" , GetLastError ()); system ("pause" ); return 1 ; } HANDLE hDevice = CreateFile (L"\\\\.\\MyDevice_Link" , GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf ("can't open the device!\n" ); printf ("Failed to open device: %lu\n" , GetLastError ()); system ("pause" ); return 0 ; } while (1 ) { printf ("等待事件\n" ); WaitForSingleObject (hEvent, INFINITE); printf ("获得事件\n" ); DWORD dwBytesReturned; ProcMonData procmondata = { 0 }; BOOL bResult = DeviceIoControl ( hDevice, IOCTL_PROCWATCH, nullptr , NULL , &procmondata, sizeof (ProcMonData) + 1 , &dwBytesReturned, NULL ); printf ("%llu %llu %llu\n" , procmondata.bCreate, procmondata.hParentId, procmondata.hProcessId); printf ("收到%d个字节\n" , dwBytesReturned); PrintProcessStatus (procmondata); procmondata = { 0 }; } CloseHandle (hEvent); system ("pause" ); return 0 ; }
效果图
ERESOURCE(读写共享锁) 共享/独占锁概念 基于windows api实现的共享锁/独占锁 - mthoutai - 博客园
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 #ifdef __cplusplus extern "C" {#endif struct SELock { RTL_CRITICAL_SECTION sec_shared, sec_exclusive; HANDLE exclusive_evt; HANDLE shared_evt; volatile long shared_count; }; _inline void InitializeSELock (SELock* lock) { InitializeCriticalSection (&lock->sec_shared); InitializeCriticalSection (&lock->sec_exclusive); lock->exclusive_evt = CreateEventW (NULL , TRUE, TRUE, NULL ); lock->shared_evt = CreateEventW (NULL , TRUE, TRUE, NULL ); lock->shared_count = 0 ; } _inline void DeleteSELock (SELock* lock) { DeleteCriticalSection (&lock->sec_shared); DeleteCriticalSection (&lock->sec_exclusive); CloseHandle (lock->exclusive_evt); CloseHandle (lock->shared_evt); lock->shared_count = 0 ; } _inline void AcquireSELockShared (SELock* lock) { EnterCriticalSection (&lock->sec_exclusive); EnterCriticalSection (&lock->sec_shared); WaitForSingleObject (lock->exclusive_evt, INFINITE); ++lock->shared_count; if (lock->shared_count) ResetEvent (lock->shared_evt); LeaveCriticalSection (&lock->sec_shared); LeaveCriticalSection (&lock->sec_exclusive); } _inline void ReleaseSELockShared (SELock* lock) { EnterCriticalSection (&lock->sec_shared); --lock->shared_count; if (!lock->shared_count) SetEvent (lock->shared_evt); LeaveCriticalSection (&lock->sec_shared); } _inline void AcquireSELockExclusive (SELock* lock) { EnterCriticalSection (&lock->sec_exclusive); WaitForSingleObject (lock->exclusive_evt, INFINITE); WaitForSingleObject (lock->shared_evt, INFINITE); ResetEvent (lock->exclusive_evt); LeaveCriticalSection (&lock->sec_exclusive); } _inline void ReleaseSELockExclusive (SELock* lock) { SetEvent (lock->exclusive_evt); } #ifdef __cplusplus } #endif
相关API: ExAcquireResourceSharedLite
该函数用于获取共享资源锁。共享资源锁允许多个线程并发访问,但在该资源上不允许进行写操作。
函数声明:
1 2 3 4 VOID ExAcquireResourceSharedLite ( _Inout_ PERSOURCE Resource, _In_ BOOLEAN Wait ) ;
参数 :
Resource
:指向 ERESOURCE
对象的指针。
Wait
:如果为 TRUE
,线程将在无法获取资源时等待。如果为 FALSE
,则不会阻塞线程,若资源不可用,函数将立即返回。
功能 :请求共享资源锁。多个线程可以同时获取共享资源,但不能进行修改操作,通常用于读操作。
示例:
1 ExAcquireResourceSharedLite(&Resource, TRUE);
ExAcquireResourceExclusiveLite
该函数用于获取独占资源锁。独占资源锁只允许一个线程访问资源,其他线程必须等待。通常用于写操作。
函数声明:
1 2 3 4 VOID ExAcquireResourceExclusiveLite ( _Inout_ PERSOURCE Resource, _In_ BOOLEAN Wait ) ;
参数 :
Resource
:指向 ERESOURCE
对象的指针。
Wait
:如果为 TRUE
,线程将在无法获取资源时等待。如果为 FALSE
,则不会阻塞线程,若资源不可用,函数将立即返回。
功能 :请求独占资源锁。只有一个线程可以获取独占资源锁,因此适合用于写操作。
示例:
1 ExAcquireResourceExclusiveLite(&Resource, TRUE);
ExAcquireSharedStarveExclusive
该函数获取共享资源锁,但会优先等待并抢占任何当前的独占锁请求。它允许共享访问,但如果有独占锁的请求,它会“饿死”独占锁的请求,直到所有共享锁请求释放。
函数声明:
1 2 3 4 VOID ExAcquireSharedStarveExclusive ( _Inout_ PERSOURCE Resource, _In_ BOOLEAN Wait ) ;
参数 :
Resource
:指向 ERESOURCE
对象的指针。
Wait
:如果为 TRUE
,线程将在无法获取资源时等待。如果为 FALSE
,则不会阻塞线程,若资源不可用,函数将立即返回。
功能 :请求共享资源锁。该函数的行为是”优先共享”,即如果当前有独占锁的请求,它会“饿死”独占锁,直到共享锁释放。
示例:
1 ExAcquireSharedStarveExclusive(&Resource, TRUE);
ExAcquireSharedWaitForExclusive
该函数类似于 ExAcquireSharedStarveExclusive
,但是它会在获取共享锁之前等待独占锁的请求。也就是说,线程在获取共享锁之前会先等到所有独占锁请求都释放,这有助于防止过多的线程在进行写操作时产生不一致的状态。
函数声明:
1 2 3 4 VOID ExAcquireSharedWaitForExclusive ( _Inout_ PERSOURCE Resource, _In_ BOOLEAN Wait ) ;
参数 :
Resource
:指向 ERESOURCE
对象的指针。
Wait
:如果为 TRUE
,线程将在无法获取资源时等待。如果为 FALSE
,则不会阻塞线程,若资源不可用,函数将立即返回。
功能 :请求共享资源锁,并且会在获取共享锁之前等待独占锁请求。
示例:
1 ExAcquireSharedWaitForExclusive(&Resource, TRUE);
总结:这四个 API 的区别
ExAcquireResourceSharedLite :请求共享锁,允许多个线程并发读取资源。
ExAcquireResourceExclusiveLite :请求独占锁,确保只有一个线程能够访问资源,通常用于写操作。
ExAcquireSharedStarveExclusive :请求共享锁,并优先等待并阻止独占锁请求。适用于“共享优先”的情况。(读优先)
ExAcquireSharedWaitForExclusive :请求共享锁,但在获取共享锁之前会等待所有的独占锁释放。(写优先)
在微软的官方文档我们可以知道:
ExAcquireResourceExclusiveLite 函数 (wdm.h) - Windows drivers | Microsoft Learn
在调用此例程之前,必须禁用正常的内核 APC 传递。 通过调用 KeEnterCriticalRegion 禁用正常的内核 APC 传递。 在释放资源之前,传递必须保持禁用状态,此时可以通过调用 KeLeaveCriticalRegion 重新启用它。 有关详细信息,请参阅 禁用 APC 。
所以在使用这些 ERESOURCE(读写共享锁)
API之前,我们需要使用KeEnterCriticalRegion
和 KeLeaveCriticalRegion
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 VOID MyFunction () { ERESOURCE Resource; ExInitializeResource (&Resource); KeEnterCriticalRegion (); ExAcquireResourceSharedLite (&Resource, TRUE); ExReleaseResourceLite (&Resource); KeLeaveCriticalRegion (); }
FAST_MUTEX
常用的 FAST_MUTEX
API
**ExInitializeFastMutex
**:初始化 FAST_MUTEX
变量。
**ExAcquireFastMutex
**:获取 FAST_MUTEX
。
**ExReleaseFastMutex
**:释放 FAST_MUTEX
。
**ExTryToAcquireFastMutex
**:尝试获取 FAST_MUTEX
(如果无法获取,则立即返回)。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 FAST_MUTEX FastMutex; ExInitializeFastMutex (&FastMutex);void SomeFunction () { ExAcquireFastMutex (&FastMutex); ... ExReleaseFastMutex (&FastMutex); }
KSEMAPHORE KSEMAPHORE
适用于以下情况:
限制资源的并发访问数量 :当多个线程想访问一个资源(比如缓冲区或设备)时,使用 KSEMAPHORE
可以确保同一时刻只能有固定数量的线程进入该资源。
管理资源池 :比如在驱动程序中,控制同时可以访问设备的请求数量,防止系统负载过高。
生产者-消费者模型 :在有多个生产者和消费者的情况下,信号量可以用于控制资源的可用数量,避免资源竞争。
KSEMAPHORE
的初始化和使用方法
1 2 3 4 5 VOID KeInitializeSemaphore ( PKSEMAPHORE Semaphore, LONG Count, LONG Limit ) ;
Semaphore
:指向信号量对象的指针。
Count
:初始化的计数值,表示当前可以访问资源的线程数。
Limit
:信号量计数器的上限值,表示最大允许同时访问资源的线程数。
示例 :初始化信号量,限制最多3个线程同时访问
1 2 KSEMAPHORE Semaphore; KeInitializeSemaphore (&Semaphore, 3 , 3 );
获取信号量 调用 KeWaitForSingleObject
获取信号量。当信号量计数大于 0 时,当前线程可以继续执行,计数值会减 1;如果计数为 0,线程会等待直到有其他线程释放信号量。
1 2 3 4 5 6 7 NTSTATUS status = KeWaitForSingleObject ( &Semaphore, Executive, KernelMode, FALSE, NULL );
释放信号量 当线程完成对资源的访问后,调用 KeReleaseSemaphore
释放信号量,并将计数值加 1。
1 2 3 4 5 6 7 LONG PreviousCount; KeReleaseSemaphore ( &Semaphore, IO_NO_INCREMENT, 1 , FALSE );xxxxxxxxxx LONG PreviousCount;KeReleaseSemaphore ( &Semaphore,
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 KSEMAPHORE Semaphore; VOID InitializeResourceControl () { KeInitializeSemaphore (&Semaphore, 3 , 3 ); } VOID AccessResource () { NTSTATUS status = KeWaitForSingleObject (&Semaphore, Executive, KernelMode, FALSE, NULL ); if (NT_SUCCESS (status)) { KeReleaseSemaphore (&Semaphore, IO_NO_INCREMENT, 1 , FALSE); } }
SPIN_LOCK 自旋锁适用于短时间的临界区操作,特别是以下场景:
小代码段 :操作简单且执行时间短的临界区。
高优先级代码 :中断处理程序等无法进行线程休眠的场合。
高性能要求 :希望避免进入等待队列和上下文切换开销的情况。
API
1 2 3 4 5 KeInitializeSpinLock KeAcquireSpinLock KeReleaseSpinLock KeAcquireSpinLockAtDpcLevel KeReleaseSpinLockFromDpcLevel
初始化自旋锁
在使用自旋锁前,必须初始化它。
1 2 3 4 5 6 KSPIN_LOCK SpinLock; VOID InitializeLock () { KeInitializeSpinLock(&SpinLock); }
2. 获取自旋锁
获取自旋锁会阻止其他线程访问该资源。获取时可以保存当前 IRQL 值,以便后续恢复。
1 2 KIRQL oldIrql; KeAcquireSpinLock(&SpinLock, &oldIrql);
注意:获取自旋锁时会提升当前处理器的 IRQL 到 DISPATCH_LEVEL
,确保在持有锁期间不会被中断打断。
3.释放自旋锁
完成对共享资源的操作后,释放自旋锁并恢复原来的 IRQL。
1 KeReleaseSpinLock (&SpinLock, oldIrql);
高级用法:在 DPC 级别操作自旋锁
有时在 DPC 级别下使用自旋锁,可以使用 KeAcquireSpinLockAtDpcLevel
和 KeReleaseSpinLockFromDpcLevel
函数来避免多余的 IRQL 处理。
1 2 3 KeAcquireSpinLockAtDpcLevel(&SpinLock); KeReleaseSpinLockFromDpcLevel(&SpinLock);
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 KSPIN_LOCK SpinLock; VOID Initialize () { KeInitializeSpinLock (&SpinLock); } VOID AccessResource () { KIRQL oldIrql; KeAcquireSpinLock (&SpinLock, &oldIrql); KeReleaseSpinLock (&SpinLock, oldIrql); }
死锁 死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
例如现在有A,B两把锁,1,2线程想要去获取这俩锁 现在线程1有A锁,线程2有B锁,然后线程1又想去获取B锁,线程2想去获取A锁,于是就造成了死锁
死锁产生的四个必要条件 (1)互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
(2)不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
(3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
(4)循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。
windbg分析锁:3:25:00