ReactOS分析CriticalSection

首先要区分的是,之前提过的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

你可能感兴趣的:(windows,内核)