内核中的同步机制(三)

★.可等待对象一览

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 就是分发器头部对象,只不过开头被解释为一个锁

VOID
FASTCALL
KeWaitForGate (
    __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 两位。


你可能感兴趣的:(内核研究)