02 線程的等待與喚醒

1、等待與喚醒機制

在Windows中,一個線程可以通過等待一個或多個可等待對象,從而進入等待狀態,另一些線程可以在某些時刻喚醒等待這些對象的其他線程。
在這裏插入圖片描述
2、可等待對象

在Windbg中查看如下結構

Windbg指令 名稱
dt _KPROCESS 進程
dt _KTHREAD 線程
dt _KTIMER 定時器
dt _KSEMAPHORE 信號量
dt _KEVENT 事件
dt _KMUTANT 互斥體
dt _FILE_OBJECT 文件
kd> dt _KPROCESS
ntdll!_KPROCESS
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 ProfileListHead  : _LIST_ENTRY
   +0x018 DirectoryTableBase : [2] Uint4B
   +0x020 LdtDescriptor    : _KGDTENTRY
   +0x028 Int21Descriptor  : _KIDTENTRY
   +0x030 IopmOffset       : Uint2B
   +0x032 Iopl             : UChar
   +0x033 Unused           : UChar
   +0x034 ActiveProcessors : Uint4B
   +0x038 KernelTime       : Uint4B
   +0x03c UserTime         : Uint4B
   +0x040 ReadyListHead    : _LIST_ENTRY
   +0x048 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x04c VdmTrapcHandler  : Ptr32 Void
   +0x050 ThreadListHead   : _LIST_ENTRY
   +0x058 ProcessLock      : Uint4B
   +0x05c Affinity         : Uint4B
   +0x060 StackCount       : Uint2B
   +0x062 BasePriority     : Char
   +0x063 ThreadQuantum    : Char
   +0x064 AutoAlignment    : UChar
   +0x065 State            : UChar
   +0x066 ThreadSeed       : UChar
   +0x067 DisableBoost     : UChar
   +0x068 PowerState       : UChar
   +0x069 DisableQuantum   : UChar
   +0x06a IdealNode        : UChar
   +0x06b Flags            : _KEXECUTE_OPTIONS
   +0x06b ExecuteOptions   : UChar

kd> dt _KMUTANT
nt!_KMUTANT
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListEntry  : _LIST_ENTRY
   +0x018 OwnerThread      : Ptr32 _KTHREAD
   +0x01c Abandoned        : UChar
   +0x01d ApcDisable       : UChar

查看這些結構體,發現除了_FILE_OBJECT第一個成員不是_DISPATCHER_HEADER外,其他的第一個成員都是_DISPATCHER_HEADER

kd> dt _FILE_OBJECT
ntdll!_FILE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Vpb              : Ptr32 _VPB
   +0x00c FsContext        : Ptr32 Void
   +0x010 FsContext2       : Ptr32 Void
   +0x014 SectionObjectPointer : Ptr32 _SECTION_OBJECT_POINTERS
   +0x018 PrivateCacheMap  : Ptr32 Void
   +0x01c FinalStatus      : Int4B
   +0x020 RelatedFileObject : Ptr32 _FILE_OBJECT
   +0x024 LockOperation    : UChar
   +0x025 DeletePending    : UChar
   +0x026 ReadAccess       : UChar
   +0x027 WriteAccess      : UChar
   +0x028 DeleteAccess     : UChar
   +0x029 SharedRead       : UChar
   +0x02a SharedWrite      : UChar
   +0x02b SharedDelete     : UChar
   +0x02c Flags            : Uint4B
   +0x030 FileName         : _UNICODE_STRING
   +0x038 CurrentByteOffset : _LARGE_INTEGER
   +0x040 Waiters          : Uint4B
   +0x044 Busy             : Uint4B
   +0x048 LastLock         : Ptr32 Void
   +0x04c Lock             : _KEVENT
   +0x05c Event            : _KEVENT
   +0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT

但是其在5C位置由一個_KEVENT

kd> dt _KEVENT
ntdll!_KEVENT
   +0x000 Header           : _DISPATCHER_HEADER

正常的可等待對象都是以_DISPATCHER_HEADER開頭,但是Windows希望某些對象也可以是可等待對象,所以在其結構體內部嵌入了_DISPATCHER_HEADER結構體,所以只要結構體中包含_DISPATCHER_HEADER我們就可以將其看成是可等待對象。

3、可等待對象的差異

WaitForSingleObject(3環)–>NtWaitForSingleObject(內核)–>KeWaitForSigleObject(內核)

NtWaitForSingleObject執行流程

<1>通過3環用戶提供的句柄,找到等待對象的內核地址

<2>如果是_DISPATCHER_HEADER開頭的直接使用

<3>如果不是_DISPATCHER_HEADER開頭的則找到其嵌入位置

4、一個線程等待一個對象

當一個線程只等待一個對象時線程跟對象建立聯繫的方式
在這裏插入圖片描述

實驗代碼:

#include <stdio.h>
#include <windows.h>

HANDLE hEvent[2];

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	WaitForSingleObject(hEvent[0], -1);
	printf("ThreadProc函數開始執行\n");
	return 0;
}

int main()
{
	hEvent[0] = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);
	getchar();
	return 0;
}

首先,我們運行程序,然後切換到Windbg中查找我們的運行的進程。

指令:!process 0 0
在這裏插入圖片描述
然後觀察一下這個進程的情況:

kd> !process 866fc990  
Failed to get VadRoot
PROCESS 866fc990  SessionId: 0  Cid: 0590    Peb: 7ffd7000  ParentCid: 00d4
    DirBase: 0ee86000  ObjectTable: e1042580  HandleCount:  34.
    Image: WaitForObject.exe
    VadRoot 00000000 Vads 0 Clone 0 Private 45. Modified 0. Locked 0.
    DeviceMap e1b180b0
    Token                             e238f708
    ElapsedTime                       00:33:59.734
    UserTime                          00:00:00.015
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         0
    QuotaPoolUsage[NonPagedPool]      0
    Working Set Sizes (now,min,max)  (187, 50, 345) (748KB, 200KB, 1380KB)
    PeakWorkingSetSize                187
    VirtualSize                       8 Mb
    PeakVirtualSize                   8 Mb
    PageFaultCount                    183
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      54

        THREAD 868cab98  Cid 0590.05d4  Teb: 7ffdf000 Win32Thread: 00000000 WAIT: (WrLpcReply) UserMode Non-Alertable
            868cad8c  Semaphore Limit 0x1
        Waiting for reply to LPC MessageId 0000bc13:
        Current LPC port e104e3d8
        Not impersonating
        DeviceMap                 e1b180b0
        Owning Process            00000000       Image:         
        Attached Process          866fc990       Image:         WaitForObject.exe
        Wait Start TickCount      63232          Ticks: 130536 (0:00:33:59.625)
        Context Switch Count      29             IdealProcessor: 0             
        UserTime                  00:00:00.000
        KernelTime                00:00:00.000
*** WARNING: Unable to verify timestamp for ntoskrnl.exe
*** ERROR: Module load completed but symbols could not be loaded for ntoskrnl.exe
        Win32 Start Address nt_400000 (0x00401570)
        Stack Init ed997000 Current ed996b94 Base ed997000 Limit ed994000 Call 00000000
        Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 0 PagePriority 0
        Kernel stack not resident.
*** ERROR: Module load completed but symbols could not be loaded for Hookport.sys
*** ERROR: Module load completed but symbols could not be loaded for intelppm.sys
        ChildEBP RetAddr  
        ed996bac 804dd0f7 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
        ed996bb8 804dd143 nt!KiSwapThread+0x46 (FPO: [0,0,0])
        ed996be0 8056efe6 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
        ed996c94 f759438e nt!NtRequestWaitReplyPort+0x63d (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
        ed996d50 804df7ec Hookport+0x338e
        ed996d50 7c92e514 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ ed996d64)
        0012fce4 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

        THREAD 86951580  Cid 0590.03f4  Teb: 7ffde000 Win32Thread: 00000000 WAIT: (UserRequest) UserMode Non-Alertable
            8698aee8  NotificationEvent
        Not impersonating
        DeviceMap                 e1b180b0
        Owning Process            00000000       Image:         
        Attached Process          866fc990       Image:         WaitForObject.exe
        Wait Start TickCount      63232          Ticks: 130536 (0:00:33:59.625)
        Context Switch Count      3              IdealProcessor: 0             
        UserTime                  00:00:00.000
        KernelTime                00:00:00.000
        Win32 Start Address nt_400000 (0x00401005)
        Stack Init ed98f000 Current ed98eca0 Base ed98f000 Limit ed98c000 Call 00000000
        Priority 11 BasePriority 8 PriorityDecrement 2 IoPriority 0 PagePriority 0
        Kernel stack not resident.
        ChildEBP RetAddr  
        ed98ecb8 804dd0f7 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
        ed98ecc4 804dd143 nt!KiSwapThread+0x46 (FPO: [0,0,0])
        ed98ecec 80567310 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
        ed98ed50 804df7ec nt!NtWaitForSingleObject+0x9a (FPO: [Non-Fpo])
        ed98ed50 7c92e514 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ ed98ed64)
        0062ff44 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

同樣我們可以在線程等待鏈表中找到該線程

接下來我們看一下線程結構體

dt _KTHREAD 86951580

kd> dt _KTHREAD 86951580
ntdll!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY [ 0x86951590 - 0x86951590 ]
   +0x018 InitialStack     : 0xed98f000 Void
   +0x01c StackLimit       : 0xed98c000 Void
   +0x020 Teb              : 0x7ffde000 Void
   +0x024 TlsArray         : (null) 
   +0x028 KernelStack      : 0xed98eca0 Void
   +0x02c DebugActive      : 0 ''
   +0x02d State            : 0x5 ''
   +0x02e Alerted          : [2]  ""
   +0x030 Iopl             : 0 ''
   +0x031 NpxState         : 0xa ''
   +0x032 Saturation       : 0 ''
   +0x033 Priority         : 11 ''
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : 3
   +0x050 IdleSwapBlock    : 0 ''
   +0x051 VdmSafe          : 0 ''
   +0x052 Spare0           : [2]  ""
   +0x054 WaitStatus       : 0n0
   +0x058 WaitIrql         : 0 ''
   +0x059 WaitMode         : 1 ''
   +0x05a WaitNext         : 0 ''
   +0x05b WaitReason       : 0x6 ''
   +0x05c WaitBlockList    : 0x869515f0 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY [ 0x0 - 0x8055b108 ]
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : 0xf700
   +0x06c BasePriority     : 8 ''
   +0x06d DecrementCount   : 0x10 ''
   +0x06e PriorityDecrement : 2 ''
   +0x06f Quantum          : 16 ''
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : (null) 
   +0x0d4 KernelApcDisable : 0
   +0x0d8 UserAffinity     : 1
   +0x0dc SystemAffinityActive : 0 ''
   +0x0dd PowerState       : 0 ''
   +0x0de NpxIrql          : 0 ''
   +0x0df InitialNode      : 0 ''
   +0x0e0 ServiceTable     : 0x8055b320 Void
   +0x0e4 Queue            : (null) 
   +0x0e8 ApcQueueLock     : 0
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x120 SoftAffinity     : 1
   +0x124 Affinity         : 1
   +0x128 Preempted        : 0 ''
   +0x129 ProcessReadyQueue : 0 ''
   +0x12a KernelStackResident : 0 ''
   +0x12b NextProcessor    : 0 ''
   +0x12c CallbackStack    : (null) 
   +0x130 Win32Thread      : (null) 
   +0x134 TrapFrame        : 0xed98ed64 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] 0x869515b4 _KAPC_STATE
   +0x140 PreviousMode     : 1 ''
   +0x141 EnableStackSwap  : 0x1 ''
   +0x142 LargeStack       : 0 ''
   +0x143 ResourceIndex    : 0 ''
   +0x144 KernelTime       : 0
   +0x148 UserTime         : 0
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : 0 ''
   +0x165 ApcStateIndex    : 0 ''
   +0x166 ApcQueueable     : 0x1 ''
   +0x167 AutoAlignment    : 0x4 ''
   +0x168 StackBase        : 0xed98f000 Void
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY [ 0x866fc9e0 - 0x868cad48 ]
   +0x1b8 FreezeCount      : 0 ''
   +0x1b9 SuspendCount     : 0 ''
   +0x1ba IdealProcessor   : 0 ''
   +0x1bb DisableBoost     : 0 ''

當前線程就是通過5C位置的_KWAIT_BLOCK跟被等待線程建立聯繫的。

kd> dt _KWAIT_BLOCK 0x869515f0 
ntdll!_KWAIT_BLOCK
   +0x000 WaitListEntry    : _LIST_ENTRY [ 0x8698aef0 - 0x8698aef0 ]
   +0x008 Thread           : 0x86951580 _KTHREAD //指明等待塊時誰的
   +0x00c Object           : 0x8698aee8 Void //被等待對象的地址
   +0x010 NextWaitBlock    : 0x869515f0 _KWAIT_BLOCK //等待塊鏈表,只有一個等待對象指向自己
   +0x014 WaitKey          : 0 //等待索引
   +0x016 WaitType         : 1 //等待的時候一個對象符合條件就執行時這裏是1,如果需要全部等待對象都符合時才執行這裏就是0

5、一個線程等待多個對象
在這裏插入圖片描述
實驗代碼:

#include <stdio.h>
#include <windows.h>

HANDLE hEvent[2];

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	::WaitForMultipleObjects(2, hEvent, FALSE, -1);
	printf("ThreadProc函數開始執行\n");
	return 0;
}

int main()
{
	DWORD ThreadID;
	hEvent[0] = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	hEvent[1] = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, (LPDWORD)&ThreadID);
	getchar();
	return 0;
}

重複上面的步驟進行觀察

kd> dt _KWAIT_BLOCK 0x86ac72c8 
ntdll!_KWAIT_BLOCK
   +0x000 WaitListEntry    : _LIST_ENTRY [ 0x869e0610 - 0x869e0610 ]
   +0x008 Thread           : 0x86ac7258 _KTHREAD
   +0x00c Object           : 0x869e0608 Void
   +0x010 NextWaitBlock    : 0x86ac72e0 _KWAIT_BLOCK
   +0x014 WaitKey          : 0
   +0x016 WaitType         : 1
kd> dt _KWAIT_BLOCK 0x86ac72e0 
ntdll!_KWAIT_BLOCK
   +0x000 WaitListEntry    : _LIST_ENTRY [ 0x86960928 - 0x86960928 ]
   +0x008 Thread           : 0x86ac7258 _KTHREAD
   +0x00c Object           : 0x86960920 Void
   +0x010 NextWaitBlock    : 0x86ac72c8 _KWAIT_BLOCK
   +0x014 WaitKey          : 1
   +0x016 WaitType         : 1

6、等待網
在這裏插入圖片描述
總結:

等待中的線程一定在等待鏈表中(KiWaitListHead),同時也一定在這張網上(KTHREAD + 5C的位置不爲空);

線程通過調用WaitForSingleObject/WaitForMultipleObjects將將自己掛到這張網上;

線程什麼時候再執行取決於其他何時調用相關函數,等待對象不同,調用函數不同

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章