基于线程调度的同步机制

 基于线程调度的同步机制

1  线程进入等待

当一个线程的控制流到达一个等待函数时,若等待的条件不满足,则线程调度器会将处理器的执行权交给其他处于备用或就绪状态的线程。这些被等待的对象可以用来协调线程之间的行为,它们被称为同步对象或者分发器对象。头部以DISPATCH_HEADER开始的对象都市分发器对象,定义如下:

typedef struct _DISPATCHER_HEADER {
    union {
        struct {
            UCHAR Type;
            union {
                UCHAR Absolute;
                UCHAR NpxIrql;
            };

            union {
                UCHAR Size;
                UCHAR Hand;
            };

            union {
                UCHAR Inserted;
                BOOLEAN DebugActive;
            };
        };

        volatile LONG Lock;
    };

    LONG SignalState;
    LIST_ENTRY WaitListHead;
} DISPATCHER_HEADER;

DISPATCHER_HEADER 的第一个成员,要么是一个锁Lock,要么是Type 代表的类型。

SignalState : 代表该分发器对象的信号状态

WaitListHead : 是链表头 连接了所有等待此对象的线程的KWAIT_BLOCK,将所有等在该锁上的线程全部记录下来,等锁被释放后就从这个列表中挑选一个(或者多个)线程唤醒。

线程和分发器对象之间的关系。首先,线程对象的KTHREAD 包含一个WaitBlockList ,它指向一个链表,链表上的每个节点代表了该线程正在等待的一个分发器对象。其次,分发器对象也包含一个链表,即DISPATCHER_HEADER的WaitListHead,链表上的每个节点代表了正在等待该对象的一个线程。两个链表节点都是等待块对象,数据类型为KWAIT_BLOCK:

typedef struct _KWAIT_BLOCK {
    LIST_ENTRY WaitListEntry; //挂入WaitListHead,一个对象所有的等待块   struct _KTHREAD *Thread;

    PVOID Object; //指要等待的对象
    struct _KWAIT_BLOCK *NextWaitBlock; //挂入线程链表,这个链表组成的线程等待的所有对象,单循环链表,头是KTHREAD.WaitBlockList

    USHORT WaitKey;//当Thead等待Object 成功从而被解除等待时的完成状态值
    UCHAR WaitType; //WaitAny 任意对象满足后唤醒 WaitAll要等到所有都满足
    UCHAR SpareByte;
} KWAIT_BLOCK, *PKWAIT_BLOCK, *PRKWAIT_BLOCK;

线程、分发器对象和等待块之间的关系:

当一个线程通过某个Wait 函数进入等待状态时,线程对象的WaitBlockList 指向一个非空链表,链表上的每个等待块对象描述了它所等待的一个分发器对象。只要链表节点上的分发器对象时无信号状态,则该线程始终处于等待状态。

线程进入等待的状况,以KeWaitForSingleObject 为例。

NTSTATUS
KeWaitForSingleObject (
    __in PVOID Object,
    __in KWAIT_REASON WaitReason,
    __in KPROCESSOR_MODE WaitMode,
    __in BOOLEAN Alertable,
    __in_opt PLARGE_INTEGER Timeout
    )
{
    Hand = 0;
    Thread = KeGetCurrentThread();
    Objectx = (PKMUTANT)Object;   //object 是参数,指定了要等待的对象
    OriginalTime = Timeout;   //Timeout 是指针参数,指定了超时设置
    Timer = &Thread->Timer;  //Timer指向线程内置的定时器对象,已初始化
    WaitBlock = &Thread->WaitBlock[0];  //WaitBlock 和WaitTimer是局部指针
    WaitTimer = &Thread->WaitBlock[TIMER_WAIT_BLOCK]; //最后一个用于超时TIMER_WAIT_BLOCK=3

    if (ReadForWriteAccess(&Thread->WaitNext) == FALSE) {
        goto WaitStart;
    }

    Thread->WaitNext = FALSE;
    /*InitializeWaitSingle();//以下代码系C宏InitializeWaitSingle 展开所得

    #define InitializeWaitSingle() 宏展开*/                                           
    Thread->WaitBlockList = WaitBlock;     //初始化等待块                                
    WaitBlock->Object = Object;                                            
    WaitBlock->WaitKey = (CSHORT)(STATUS_SUCCESS);                         
    WaitBlock->WaitType = WaitAny;                                         
    Thread->WaitStatus = 0;                                                
    if (ARGUMENT_PRESENT(Timeout)) { //如果有到期时间 ,初始化一个定时器                                

        KiSetDueTime(Timer, *Timeout, &Hand);                             
        DueTime.QuadPart = Timer->DueTime.QuadPart;                        
        WaitBlock->NextWaitBlock = WaitTimer;    //定时器等待块和要等待的等待块构成循环链表                            
        WaitTimer->NextWaitBlock = WaitBlock;                              
        Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;      
        Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;      
    } else {                                                               
        WaitBlock->NextWaitBlock = WaitBlock;                               
    }                                                                      

    Thread->Alertable = Alertable;                                         
    Thread->WaitMode = WaitMode;                                           
    Thread->WaitReason = (UCHAR)WaitReason;                                
    Thread->WaitListEntry.Flink = NULL;                                    
    StackSwappable = KiIsKernelStackSwappable(WaitMode, Thread);           
    Thread->WaitTime = KiQueryLowTickCount()

    do {
   //这个循环是为了能够在等待过程中处理APC
        Thread->Preempted = FALSE;
        if (Thread->ApcState.KernelApcPending &&
            (Thread->SpecialApcDisable == 0) &&
            (Thread->WaitIrql < APC_LEVEL)) {
    //irql提升到DISPATCH_LEVEL,其他cpu投递了一个apc,dispatcher数据库仍在锁定的情况
    //这样会请求一个中断派发掉apc
            KiUnlockDispatcherDatabase(Thread->WaitIrql);
        } else {  
    //MutantObject要特别对待 Mutant对应的是R3的mutex概念
            if (Objectx->Header.Type == MutantObject) {
                if ((Objectx->Header.SignalState > 0) ||
                    (Thread == Objectx->OwnerThread)) {
                    if (Objectx->Header.SignalState != MINLONG) {
                        KiWaitSatisfyMutant(Objectx, Thread);
                        WaitStatus = (NTSTATUS)(Thread->WaitStatus);
                        goto NoWait;
                    } else {
                        KiUnlockDispatcherDatabase(Thread->WaitIrql);
                        ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
                    }
                }
            } else if (Objectx->Header.SignalState > 0) {//条件已经满足
                KiWaitSatisfyOther(Objectx);
                WaitStatus = (NTSTATUS)(0);
                goto NoWait;
            }

            TestForAlertPending(Alertable);   //检测是否可以被叫醒(用户态APC神马的) 更改等待状态 WaitStatus

    /* 以下是宏定义 如果被叫醒,则提前结束循环

    #define TestForAlertPending(Alertable) \
    if (Alertable) { \
        if (Thread->Alerted[WaitMode] != FALSE) { \
            Thread->Alerted[WaitMode] = FALSE; \
            WaitStatus = STATUS_ALERTED; \
            break; \
        } else if ((WaitMode != KernelMode) && \
                  (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) == FALSE) { \
            Thread->ApcState.UserApcPending = TRUE; \
            WaitStatus = STATUS_USER_APC; \
            break; \
        } else if (Thread->Alerted[KernelMode] != FALSE) { \
            Thread->Alerted[KernelMode] = FALSE; \
            WaitStatus = STATUS_ALERTED; \
            break; \
        } \
    } else if (Thread->ApcState.UserApcPending & WaitMode) { \
        WaitStatus = STATUS_USER_APC; \
        break; \
    }
    */
            if (ARGUMENT_PRESENT(Timeout)) {// 检查定时是否到期
                if (KiCheckDueTime(Timer) == FALSE) {
                    WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
                    goto NoWait;
                }
            }
            InsertTailList(&Objectx->Header.WaitListHead, &WaitBlock->WaitListEntry);// 等待块插入对象的派发头

            Queue = Thread->Queue;
            if (Queue != NULL) {
                KiActivateWaiterQueue(Queue);
            }

            Thread->State = Waiting;    //设置线程进入等待状态
            CurrentPrcb = KeGetCurrentPrcb();

            if (StackSwappable != FALSE) {
                InsertTailList(&CurrentPrcb->WaitListHead, &Thread->WaitListEntry);
            }

            ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
            KiSetContextSwapBusy(Thread);
            if (ARGUMENT_PRESENT(Timeout)) {
                KiInsertOrSignalTimer(Timer, Hand);
            } else {
                KiUnlockDispatcherDatabaseFromSynchLevel();
            }

            WaitStatus = (NTSTATUS)KiSwapThread(Thread, CurrentPrcb); //放弃执行,切换线程,当从这里返回之后意味着

                   //条件已经被满足,或者超时,或者有内核APC插入
            if (WaitStatus != STATUS_KERNEL_APC) {//只要不是内核apc插入,就可以返回了
                return WaitStatus;
            }

            if (ARGUMENT_PRESENT(Timeout)) {//否则这不是一次真的等到了,而是被内核apc搞醒的 重新计算时间 重新构造一个等待 循环
                Timeout = KiComputeWaitInterval(OriginalTime, //减少时间
                                                &DueTime,
                                                &NewTime);
            }
        }

WaitStart:
        Thread->WaitIrql = KeRaiseIrqlToSynchLevel();
        InitializeWaitSingle();
        KiLockDispatcherDatabaseAtSynchLevel();
    } while (TRUE);
    KiUnlockDispatcherDatabase(Thread->WaitIrql);
    return WaitStatus;

NoWait:
    KiUnlockDispatcherDatabaseFromSynchLevel();
    KiAdjustQuantumThread(Thread);
    return WaitStatus;
}

KeWaitForSingleObject 使用当前线程得WaitBlock 数组的第0项作为所等待对象的KWAIT_BLOCK;如果Timeout 不为NULL,则用WaitBlock的第3项作为定时器对象的KWAIT_BLOCK。While 循环在每次执行时都会检查自己等待的同步对象是否满足等待条件。

针对WaitAny的情况,当发现某一个同步对象有信号时就会直接结束,而针对WaitAll的情况,就需要所有对象都就绪。当线程满足等待条件时,会执行相应的Satisfy函数调整相应的信号量。即,当对象为事件(自动重置)时设置信号为0,阻止其他线程获取这个事件;当对象为信号量时,执行信号量的P操作。

如果线程不满足条件,则会准备好超时计时器,调度到等待状态,直到被唤醒。

注意到唤醒时的判断条件,STATUS_KERNEL_APC是一个特殊的条件,此时线程会重新检查自己等待的对象是否满足条件,否则会直接返回等待结果,结束等待。

因为如果不考虑等待多个对象,单个对象就绪就可以通知线程等待的结果,而需要等待多个对象时,单一对象的就绪并不能代表最终结果,这就要求线程重新检查自己等待的每个对象是否满足要求。此时通过STATUS_KERNEL_APC这个状态码就起到这样一个通知的作用。

线程解除等待:

它们调用KiWaitTest、KiWaitTestSynchronizationObject、KiWaitTestWithoutSideEffects 来测试一个等待是否已经满足。这些函数从分发器对象的WaitListHead出发,遍历KWAIT_BLOCK链表,逐个检查。然后,调用KiUnwaitThread和KiReadyThread 函数,使线程进入延迟的就绪状态。在KiReadThread 函数之前调用KiUnlinkThread ,将一个满足等待条件的线程从它的等待块链表中移除处理。

FORCEINLINE VOID FASTCALL KiUnlinkThread (IN PRKTHREAD Thread,IN LONG_PTR WaitStatus )
{
    PKQUEUE Queue;
    PKTIMER Timer;
    PRKWAIT_BLOCK WaitBlock;
    //设置线程的等待完成状态,将线程的等待块从相应对象的等待块链表中移除
    //同时也将线程从处理器的全局等待链表中移除
    Thread->WaitStatus |= WaitStatus;
    WaitBlock = Thread->WaitBlockList;

    do {
        RemoveEntryList(&WaitBlock->WaitListEntry);
        WaitBlock = WaitBlock->NextWaitBlock;
    } while (WaitBlock != Thread->WaitBlockList);

    if (Thread->WaitListEntry.Flink != NULL) {
        RemoveEntryList(&Thread->WaitListEntry);
    }

    //如果用于超时控制的线程定时器还在起作用,则取消线程定时器
    Timer = &Thread->Timer;

    if (Timer->Header.Inserted != FALSE) {
        KiRemoveTreeTimer(Timer);
    }

    //如果线程正在处理一个队列项,则递增该队列的当前活动线程的技术
    Queue = Thread->Queue;
    if (Queue != NULL) {
        Queue->CurrentCount += 1;
    }
    return;
}

进入等待和解除等待的典型过程,如图:

WaitForSingleObject在拿到Event后会直接结束循环返回WaitStatus。

WaitForMultipleObjects在拿到Event后不会直接退出,而是去检查是否满足所有被等待对象的条件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值