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将将自己挂到这张网上;
线程什么时候再执行取决于其他何时调用相关函数,等待对象不同,调用函数不同
上一篇: Java 线程池
下一篇: 四大网络抓包神器,总有一款适合你~