内核中的同步机制(三)

来源:互联网 发布:绝地易语言辅助源码 编辑:程序博客网 时间:2024/05/01 15:12

★.可等待对象一览

1. Kevent 就是一个等待头 没啥说的 以上的例子也是举得这个KMUTANT 对应的是r3的mutex

typedef struct _KMUTANT {    DISPATCHER_HEADER Header;    LIST_ENTRY MutantListEntry; KTHREAD.MutantListHead    struct _KTHREAD *OwnerThread;   //一个互斥体属于某个线程    BOOLEAN Abandoned;    //是否已经弃用    UCHAR ApcDisable;    // 是否禁用某种apc 会影响到线程的响应} KMUTANT, *PKMUTANT, *PRKMUTANT, KMUTEX, *PKMUTEX, *PRKMUTEX;

只有所属线程才能释放这个对象,所以这个对象可以算是特别为线程访问资源同步设计的
2. 信号量

只是在DISPATCHER_header 后面加上一个Limit 数 限制了共享资源的最大值~ 差不对

3. 队列

KQUEUE 用来实现线程池 ,也就是实现的一组线程 限制了最大的活动线程数,超出最大线程数之后,线程只能进入等待,等到其他线程不活动了之后自己再活动

typedef struct _KQUEUE {    DISPATCHER_HEADER Header;    LIST_ENTRY EntryListHead; 队列中有待处理的项    ULONG CurrentCount;   活动线程数    ULONG MaximumCount;    LIST_ENTRY ThreadListHead; 节点是KTHREAD.QueueListHead} KQUEUE, *PKQUEUE, *PRKQUEUE;

插入一个队列时,如果活动线程数小于最大数,并且有线程在等待队列 立即释放,否则就被插入到EntryListHead

4. 进程线程 挺简单不多说
5. 定时器

最后的tips
1.等待的线程解除时,优先级上升1(定时器除外),并且时限-1(不同系统环境未必一样)

2.在处理分发对象头时都要提升IRQL并且锁住分发数据库,这表明进入了一段线程调度器的代码 线程调度分布在 中断 异常 以及各种处理线程相关代码逻辑的位置

一般情况下都是在处理过之后退出,单象 KeSetEvent 有一个Wait变量,如果指明为true 证明可以保证有线程在等待 这时不退出调度器,而是

    if (Wait != FALSE) {       Thread = KeGetCurrentThread();       Thread->WaitNext = Wait;       Thread->WaitIrql = OldIrql;    } else {       KiUnlockDispatcherDatabase(OldIrql);}

   然后由等待的线程来处理,不知道这样做的优化到底有多少,但我知道我当年由于不好好看文档,用错这个参数,反正是没少蓝屏。。。。。。

★.门等待

绕过繁琐的线程调度,实现快速等待。实际上就是把门对象从线程等待对象的机制中抽离出来,自己实现一套专门的等待机制,

对象是一个KGATE 就是分发器头部对象,只不过开头被解释为一个锁

VOIDFASTCALLKeWaitForGate (    __inout PKGATE Gate,    __in KWAIT_REASON WaitReason,    __in KPROCESSOR_MODE WaitMode    ){    PKTHREAD CurrentThread;    KLOCK_QUEUE_HANDLE LockHandle;    PKQUEUE Queue;    PKWAIT_BLOCK WaitBlock;    NTSTATUS WaitStatus;    CurrentThread = KeGetCurrentThread();    do {        KeAcquireInStackQueuedSpinLockRaiseToSynch(&CurrentThread->ApcQueueLock,                                                   &LockHandle);   //先交付一下apc看看        if (CurrentThread->ApcState.KernelApcPending &&            (CurrentThread->SpecialApcDisable == 0) &&            (LockHandle.OldIrql < APC_LEVEL)) {            KeReleaseInStackQueuedSpinLock(&LockHandle);            continue;        }   //有队列才去锁?        if ((Queue = CurrentThread->Queue) != NULL) {            KiLockDispatcherDatabaseAtSynchLevel();        }        KiAcquireThreadLock(CurrentThread);//Thread->ThreadLock         KiAcquireKobjectLock(Gate);    //对象分发头的锁        if (Gate->Header.SignalState != 0) { //有信号就立即满足            Gate->Header.SignalState = 0; //立即重置            KiReleaseKobjectLock(Gate);            KiReleaseThreadLock(CurrentThread);            if (Queue != NULL) {                KiUnlockDispatcherDatabaseFromSynchLevel();            }            KeReleaseInStackQueuedSpinLock(&LockHandle);            break;            } else {            WaitBlock = &CurrentThread->WaitBlock[0];//构造等待块            WaitBlock->Object = Gate;            WaitBlock->Thread = CurrentThread;            CurrentThread->WaitMode = WaitMode;            CurrentThread->WaitReason = WaitReason;            CurrentThread->WaitIrql = LockHandle.OldIrql;            CurrentThread->State = GateWait;    //传说中的门等待状态~~现身~~ 不再放入等待对象中            CurrentThread->GateObject = Gate;               InsertTailList(&Gate->Header.WaitListHead, &WaitBlock->WaitListEntry); //这边的链表还是要弄好            KiReleaseKobjectLock(Gate);            KiSetContextSwapBusy(CurrentThread);            KiReleaseThreadLock(CurrentThread);            //            // If the current thread is associated with a queue object, then            // activate another thread if possible.            //            if (Queue != NULL) {                          //线程池,如果有 把执行权让给其他线程                if ((Queue = CurrentThread->Queue) != NULL) {                    KiActivateWaiterQueue(Queue);                }                KiUnlockDispatcherDatabaseFromSynchLevel();            }            KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);            WaitStatus = (NTSTATUS)KiSwapThread(CurrentThread, KeGetCurrentPrcb()); //线程切换            if (WaitStatus == STATUS_SUCCESS) {                return;            }        }    } while (TRUE);    return;}        KeSignalGateBoostPriority这个函数获取到等待门对象的线程,直接放入延迟准备中去Entry = Gate->Header.WaitListHead.Flink;                WaitBlock = CONTAINING_RECORD(Entry, KWAIT_BLOCK, WaitListEntry);                WaitThread = WaitBlock->Thread; //取得等待的线程                if (KiTryToAcquireThreadLock(WaitThread)) {                    RemoveEntryList(Entry);                      WaitThread->WaitStatus = STATUS_SUCCESS;                    WaitThread->State = DeferredReady; //设置成延迟准备 直接调用                    WaitThread->DeferredProcessor = KeGetCurrentPrcb()->Number;                    KiReleaseKobjectLock(Gate);                    KiReleaseThreadLock(WaitThread);                    Priority = CurrentThread->Priority;                    if (Priority < LOW_REALTIME_PRIORITY) {   //调整优先级!                        Priority = Priority - CurrentThread->PriorityDecrement;                        if (Priority < CurrentThread->BasePriority) {                            Priority = CurrentThread->BasePriority;                        }                        if (CurrentThread->PriorityDecrement != 0) {                            CurrentThread->PriorityDecrement = 0;                            CurrentThread->Quantum = CLOCK_QUANTUM_DECREMENT;                        }                    }                    WaitThread->AdjustIncrement = Priority;                    WaitThread->AdjustReason = (UCHAR)AdjustBoost;                    if (WaitThread->Queue != NULL) {                        KiLockDispatcherDatabaseAtSynchLevel();                        if ((Queue = WaitThread->Queue) != NULL) {                            Queue->CurrentCount += 1;                        }                        KiUnlockDispatcherDatabaseFromSynchLevel();                    }                    KiDeferredReadyThread(WaitThread);                    KiExitDispatcher(OldIrql);                    return;                } else {//继续尝试获取                    KiReleaseKobjectLock(Gate);                    KeLowerIrql(OldIrql);                    continue;                }

守护互斥体KGUARD_MUTEX是用互斥实现的 还有快速互斥 x86基于事件 x64基于门等待

★.两种执行体层面的同步机制——执行体互斥&push lock

两种同步机制基于上述的一些基本同步机制而实现,提供独占资源和共享资源的读取能力,这里只简单的看一下逻辑。

先看执行体资源的结构定义

typedef struct _ERESOURCE {    LIST_ENTRY SystemResourcesList;   执行体资源由系统的一个链表ExpSytemResourceList统一管理     POWNER_ENTRY OwnerTable;   动态数组 保存共享属性和等待这个资源的线程的一些信息    SHORT ActiveCount;      当前获得这个资源的线程总数    USHORT Flag;       状态标志位    PKSEMAPHORE SharedWaiters;   信号量对象,利用信号量实现共享资源计数的统计    PKEVENT ExclusiveWaiters;    事件对象,用来实现独占    OWNER_ENTRY OwnerThreads[2]; 自己本身有两个owner_entry 提升效率    ULONG ContentionCount;    发生竞争的次数    USHORT NumberOfSharedWaiters; 共享等待数    USHORT NumberOfExclusiveWaiters; 独占等待数    union {        PVOID Address;        ULONG_PTR CreatorBackTraceIndex;    };    KSPIN_LOCK SpinLock;     保证处理这个结构时的同步} ERESOURCE, *PERESOURCE;typedef struct _OWNER_ENTRY {    ERESOURCE_THREAD OwnerThread; 所有者线程    union {        LONG OwnerCount;     引用计数        ULONG TableSize;    };} OWNER_ENTRY, *POWNER_ENTRY;

这个对象在非换页池分配,读访问时可以共享读,写的时候独占写,这是一种优化策略

以独占方式释放后,共享请求和独占请求都被发出,先满足共享在满足独占

共享被释放后,只接受独占请求

1. 请求独占访问时,
如果ActiveCount为0,证明没有人在访问这个资源 给予分配资源,否则就进入等待ExclusiveWaiters,
如果等待超时,试图提升正在独占或者所有正在共享这个资源的优先级,以便使这些线程能够尽快释放这个资源

2. 请求共享访问
如果ActiveCount为0,则填充一个OwnerThreads返回

如果当前线程已经独占该资源,增加第一个owner_entry的引用计数

如果该资源已经共享,就查看当前线程是否在OwnerThreads 如果不在且没有其他线程在等待独占,则加入;如果在数组中只增加相应的引用计数

以下是需要等待或者失败的情况(这取决于调用者悬着是否等待 wait参数):

该资源已经被其他线程独占

该资源已经共享,当前线程不再owner的数组中,并且现在有一个正在请求独占的线程

这两种情况,都要把自己添加到OwnerTread中,然后等待信号量的释放

3. 释放资源

如果是独占方式访问,减少引用计数,如果已经是0,清除owner thread,检查是否有共享请求如果有就释放信号量,如果没有共享请求就看是否有独占请求、有就设置设置事件

如果之前是共享方式访问 处理相应的owner thread 如果引用计数是0 则清除掉owner thread。如果ActiveCount为0,就检查是否有独占访问请求,设置事件满足等待者。

下面看一下push lock

typedef struct _EX_PUSH_LOCK {union {   struct {    ULONG_PTR Locked         : 1;   表示已经被锁    ULONG_PTR Waiting        : 1;   表示有先线程正在等待    ULONG_PTR Waking         : 1; 一个线程试图唤醒另外一个线程    ULONG_PTR MultipleShared : 1;   共享线程数    ULONG_PTR Shared         : sizeof (ULONG_PTR) * 8 - 4; w位是0, 后28是共享计数, 否则后28指向一个等待块   };   ULONG_PTR Value;   PVOID Ptr;};} EX_PUSH_LOCK, *PEX_PUSH_LOCK;后28指向一个等待块typedef struct DECLSPEC_ALIGN(16) _EX_PUSH_LOCK_WAIT_BLOCK {    union {        KGATE WakeGate;        KEVENT WakeEvent;    };    PEX_PUSH_LOCK_WAIT_BLOCK Next;    PEX_PUSH_LOCK_WAIT_BLOCK Last;    PEX_PUSH_LOCK_WAIT_BLOCK Previous;    LONG ShareCount;    LONG Flags;} DECLSPEC_ALIGN(16) EX_PUSH_LOCK_WAIT_BLOCK;

Push lock在无竞争情况下(w始终为0),可以有一下情况

独占获取 前提:所有位全是0 获取之后成功: L位变为1

共享获取 前提:L 1 shared存在 或者所有位是0,获取之后成功:L为1 shared++

独占释放 L变回0

共享释放 shared--,如果没有块之后 L变为0

在有竞争情况下:

独占获取 前提:L=1 W=0 动作:构造一等待块 放入shared 然后置w位 1
    前提:L=1 W=1 动作:构造一等待块 放入shared
   然后等待门对象

共享获取
   前提:L=1 shared=0 意味着这是一个独占的 动作:构造等待块 w=1
   前提 L=1 W= 1 shared有值 意味独占中 并且有等待共享的线程 动作:构造一个等待块放入

独占释放
   尝试找到一个独占式请求,释放门对象唤醒线程,如果没有,再唤醒共享的线程

共享释放
   释放请求独占的线程

实际在win的实现中 还要处理K m 两位。