内核态线程同步(2)利用内核对象同步

最后更新于:2022-04-01 21:44:56

## 事件对象 event对象常用来多个线程间进行工作的同步,如线程A先执行一些初始化工作,触发evnet,通知线程B初始化工作已经完成,可以进行接下来的工作。 ### 创建event对象 ~~~ HANDLE WINAPI CreateEvent( _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, // 设置安全属性 _In_ BOOL bManualReset, // 是否人工重置状态(人工重置则不会自动改变事件状态, 自动重置则会自动将事件恢复为未触发) _In_ BOOL bInitialState, // 事件初始状态(触发/未触发) _In_opt_ LPCTSTR lpName // 事件名称 ); ~~~ 值得注意的是 ~~~ BOOL bManualReset, ~~~ 若 为人工重置,那么当事件触发时,所有等待线程均能够获得事件对象,且不会自动重置事件状态。若为自动重置,则仅有一个线程wait获得该事件,同时置事件为未触发状态。 另外一点,其他线程若想获得该事件对象句柄,可以也调用CreateEvent函数,并传入事件名称。若该事件已经存在,则直接返回句柄,若未存在则会创建该事件并返回句柄。 注意,若事件已经存在,再调用CreateEvent只会获取其句柄,但该函数的其他参数会忽略。 对于自动重置事件,若multiplewait函数为全部等待状态,则对于仅自动重置事件触发时,multiplewait函数会忽略该event,同时不会重置事件,只有当所有的等待对象都触发时,multiplewait才会获取自动重置事件并自动重置为未触发状态。 **若想在创建事件时指定的可以访问事件的权限,可以用** ~~~ HANDLE WINAPI CreateEventEx( _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, _In_opt_ LPCTSTR lpName, _In_ DWORD dwFlags, // 可以是两种flags的任意组合CREATE_EVENT_INITIAL_SETCREATE_EVENT_MANUAL_RESET _In_ DWORD dwDesiredAccess // 设置事件权限 ); ~~~ ### 获取事件句柄函数 ~~~ HANDLE WINAPI OpenEvent( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ LPCTSTR lpName ); ~~~ ### 改变事件触发状态 ### 设置事件为触发状态 ~~~ BOOL WINAPI SetEvent( _In_ HANDLE hEvent ); ~~~ ### 设置事件未触发状态 ~~~ BOOL WINAPI ResetEvent( _In_ HANDLE hEvent ); ~~~ ## 可等待计时器内核对象 可等待计时器对象会在一定时后或每间隔一段时间触发,可用在某个时间的操作。 ### 创建或获取可等待计时器 ~~~ HANDLE WINAPI CreateWaitableTimer( _In_opt_ LPSECURITY_ATTRIBUTES lpTimerAttributes, _In_ BOOL bManualReset, // 是否人工重置 _In_opt_ LPCTSTR lpTimerName ); ~~~ ### 获取可等待计时器句柄 ~~~ HANDLE WINAPI OpenWaitableTimer( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ LPCTSTR lpTimerName ); ~~~ 不像事件对象,可等待计时器创建后总是未触发的。 需要调用函数 SetWaitableTimer ~~~ BOOL WINAPI SetWaitableTimer( _In_ HANDLE hTimer, // 计时等待对象 _In_ const LARGE_INTEGER *pDueTime, // 何时触发对象(用负值表示相对于调用SetWaitableTimer后的时间 100纳秒为单位) _In_ LONG lPeriod, // 触发后间隔的触发频率(0 表示仅触发一次) _In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine, // APC调用函数 _In_opt_ LPVOID lpArgToCompletionRoutine, // APC调用参数 _In_ BOOL fResume // 在可挂起的计算机系统中,是否恢复计算机来使等待线程执行CPU时间。 // 若传入FALSE,则会触发对象,但等待线程不会执行,除非直到计算机系统重新执行 ); ~~~ ### 取消计时等待对象的时间设置 ~~~ 该函数会取消一切的SetWaitableTimer的计时设置。 但是该函数不会更改timer对象的触发状态,若已经触发,则该对象仍会处于触发状态。 BOOL WINAPI CancelWaitableTimer( _In_ HANDLE hTimer ); ~~~ ### 计时等待对象 VS 用户计时器(SetTimer) 1、内核对象,用户对象 2、用户计时器会产生WM_TIMER消息,该消息会被送到调用SetTimer线程或创建窗口线程,同一时间仅有一个线程得到通知。 计时等待对象可多个线程同时被通知。 ## 信号量内核对象 信号能够灵活的限制可被激活的线程数目,并确保线程数目不会超过设定的最大值。 具体使用流程为: 1、创建信号量对象,并指定最大资源数目与当前可用数目(常为0)。 2、创建多个资源请求线程,因为当前可用数目为0,线程等待。 3、当符合某种条件时,调用[**ReleaseSemaphore**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685071%28v=vs.85%29.aspx)函数释放资源,这时候可用资源数目递增。 4、可以资源数目不再为0,等待线程获得资源,同时可以资源数目递减。 windows系统会确保当前可用资源数目大于等于0,同时不会超过最大值。 ### 创建(或获取)信号量对象 ~~~ HANDLE WINAPI CreateSemaphore( _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, _In_ LONG lInitialCount, // 初始当前可用资源数目 _In_ LONG lMaximumCount, // 最大可用资源数目 _In_opt_ LPCTSTR lpName ); ~~~ [**CreateSemaphoreEx**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682446%28v=vs.85%29.aspx) ### 获取信号量对象句柄 ~~~ HANDLE WINAPI OpenSemaphore( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ LPCTSTR lpName ); ~~~ ### 递增信号量可用资源 ~~~ BOOL WINAPI ReleaseSemaphore( _In_ HANDLE hSemaphore, _In_ LONG lReleaseCount, _Out_opt_ LPLONG lpPreviousCount ); ~~~ ## 互斥量内核对象 互斥量内核对象用于确保资源被唯一的线程访问,即互斥访问。 ### 创建(获取)互斥量对象 ~~~ HANDLE WINAPI CreateMutex( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, _In_ BOOL bInitialOwner, _In_opt_ LPCTSTR lpName ); ~~~ 互斥量内核对象 有 引用计数器,线程ID已经递归计数器组成。 线程ID用来记录当前获取互斥量对象的线程ID,0表示没人获取,互斥量处于触发状态。一旦,有个线程wait到互斥量,其内核对象线程ID为该线程ID,同时内核对象变为未触发状态,其他线程只能继续等待。但对于已经获得互斥量线程,其仍可以等待成功,这时候内核对象会递增其递归计数器。 调用[**ReleaseMutex**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685066%28v=vs.85%29.aspx) 释放互斥量。对于多次递归进入的互斥量,要相应的多次调用release函数。 注意,当线程在获取了互斥量对象,而在调用[**ReleaseMutex**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms685066%28v=vs.85%29.aspx)之前结束的话,会产生遗弃问题。 ## 关于内核态同步对象的一些事项 1、一般的,通过内核对象来进行同步,其获取的内核对象句柄都是具有所有权限的(访问,改变触发状态等),但是我们可以在Create内核对象时,通过扩展函数ex函数设置可访问权限,那么当访问该内核对象句柄时,仅能够进行指定的权限访问。 2、内核对象命名与多用户系统 我们有多种方法可以在多个进程空间访问同一个内核对象(继承,dumplicatehandle,命名的内核对象)。 在使用命名内核对象访问时,需要注意在多用户系统中内核对象名称的前缀。 MSDN原话是: The name can have a "Global\" or "Local\" prefix to explicitly create the object in the global or session namespace. 即加上“Global\”前缀,可以在多个用户间通过名称访问该内核对象,而"Local\"前缀仅能够当前用户通过名称访问内核对象。
';