首先要区分的是,之前提过的critical region和critical section的区别,前者是主要是通过IRQL阻止线程被打断,而后者则是通过包装内核对象来防止线程被打断。
为了减少不必要的代码量,直接跳过一些函数的封装。下面是critical section的初始化实现。
NTSTATUS NTAPI RtlInitializeCriticalSectionAndSpinCount(PRTL_CRITICAL_SECTION CriticalSection, ULONG SpinCount) { PRTL_CRITICAL_SECTION_DEBUG CritcalSectionDebugData; CriticalSection->LockCount = -1; CriticalSection->RecursionCount = 0; CriticalSection->OwningThread = 0; CriticalSection->SpinCount = (NtCurrentPeb()->NumberOfProcessors > 1) ? SpinCount : 0; CriticalSection->LockSemaphore = 0; CritcalSectionDebugData = RtlpAllocateDebugInfo(); if (!CritcalSectionDebugData) { return STATUS_NO_MEMORY; } CritcalSectionDebugData->Type = RTL_CRITSECT_TYPE; CritcalSectionDebugData->ContentionCount = 0; CritcalSectionDebugData->EntryCount = 0; CritcalSectionDebugData->CriticalSection = CriticalSection; CriticalSection->DebugInfo = CritcalSectionDebugData; if ((CriticalSection != &RtlCriticalSectionLock) && (RtlpCritSectInitialized)) { RtlEnterCriticalSection(&RtlCriticalSectionLock); InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList); RtlLeaveCriticalSection(&RtlCriticalSectionLock); } else { InsertTailList(&RtlCriticalSectionList, &CritcalSectionDebugData->ProcessLocksList); } return STATUS_SUCCESS; }首先,critical section中的LockCount小于0的时候,表明criticalsection可以访问,反之,当LockCount大于0时,则不能访问。LockCount和下面的RecursionCount有关,LockCount初始化为-1,表明一次只能有一个线程访问。RecursionCount计数当前线程递归访问criticalsection的数目,一般等于LockCount+1,初始化为0.下面设置线程的句柄,后面是互斥访问criticalsection的信号量计数。最后一个是额外的调试数据,调试数据的处理分为两种情况,一种是在全局初始化时,另一种则是初始化普通的CRITICAL_SECTION结构体。
在初始化调试数据之后,就将critical secction插入到队列的尾部。这里之所以需要有一个判断,是因为RtlCriticalSectionLock是系统提供的专用于保护访问互斥;而这个数据同样也是经过这个函数初始化,所以需要有一个分支过程。(个人觉得这里在定义的时候直接进行初始化,然后定义一个特殊的函数进行初始化,毕竟分支影响处理器的流水线,个人理解,还望高手指点迷津)
NTSTATUS NTAPI RtlEnterCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { HANDLE Thread = (HANDLE)NtCurrentTeb()->ClientId.UniqueThread; if (InterlockedIncrement(&CriticalSection->LockCount) != 0) { if (Thread == CriticalSection->OwningThread) { CriticalSection->RecursionCount++; return STATUS_SUCCESS; } RtlpWaitForCriticalSection(CriticalSection); } CriticalSection->OwningThread = Thread; CriticalSection->RecursionCount = 1; return STATUS_SUCCESS; }这个函数实现进入到critical section,首先会利用原子操作自加LockCount。如果自加之后的数目不等于0,则表明已经有线程进入到critical section。如果已经有线程进入到critical section,则需要判断是否是当前线程递归访问critical section。如果是递归访问则自加RecursionCount,并返回函数执行成功。否则需要等待Critical Section可用。
NTSTATUS NTAPI RtlpWaitForCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { NTSTATUS Status; EXCEPTION_RECORD ExceptionRecord; BOOLEAN LastChance = FALSE; LARGE_INTEGER Timeout; Timeout.QuadPart = 150000L * (ULONGLONG)10000; Timeout.QuadPart = -Timeout.QuadPart; if (!CriticalSection->LockSemaphore) { RtlpCreateCriticalSectionSem(CriticalSection); } if (CriticalSection->DebugInfo) CriticalSection->DebugInfo->EntryCount++; for (;;) { if (CriticalSection->DebugInfo) CriticalSection->DebugInfo->ContentionCount++; Status = NtWaitForSingleObject(CriticalSection->LockSemaphore, FALSE, &Timeout); if (Status == STATUS_TIMEOUT) { LastChance = TRUE; } else { return STATUS_SUCCESS; } } }等待函数的实现非常简单,如果当前critical section为空,则创建一个新的信号量句柄。并在此事件上等待2.5分钟,不过等待只有两次机会,如果超过两次机会则会抛出异常。之前分配的调试信息结构体在此时需要手机调试相关的信息,便于在程序崩溃之后进行相应的分析。到这一步也可以看出,虽然CRITICAL_SECTION不是核心对象,然而其实现却依赖于核心对象。
<pre code_snippet_id="461785" snippet_file_name="blog_20140901_5_3253223" name="code" class="cpp">NTSTATUS NTAPI RtlLeaveCriticalSection(PRTL_CRITICAL_SECTION CriticalSection) { if (--CriticalSection->RecursionCount) { InterlockedDecrement(&CriticalSection->LockCount); } else { CriticalSection->OwningThread = 0; if (-1 != InterlockedDecrement(&CriticalSection->LockCount)) { RtlpUnWaitCriticalSection(CriticalSection); } } return STATUS_SUCCESS; }
退出critical section段的操作不需要加锁,因为可以执行这一语句的肯定是进入到critical section的线程。首先会递减递归访问计数,然后原子递减当前的LockCount(这里主要是对应于前面的原子操作自加)。如果RecursionCount自减之后等于0,则清除当前的线程句柄,然后递减LockCount。在这里有一种可能是Critical Section的线程句柄等于0。在前面进入到等待的时候,如果某个线程在OwingThread等于0的时候,请求进入到critical section,由于LockCount大于0,所以判断是否等于当前线程,肯定线程句柄是不等于0的,所以进入等待状态。唤醒等待的线程很简单,设置事件就可以了。
完整的源代码网址:http://doxygen.reactos.org/d0/d06/critical_8c_source.html