4.WaitForSingleObject函数分析
无论可等待对象是何种类型,线程都是通过:
WaitForSingleObject
WaitForMultipleObjects
进入等待状态的,这两个函数是理解线程等待与唤醒进制的核心
WaitForSingleObject参数说明
WaitForSingleObject对应的内核函数:
NTSTATUS stdcall NtWaitForSingleObject
(
HANDLE Handle,
BOOLEAN Alertable,
PLARGE_INTEGER Timeout
)
Handle 用户层传递的等待对象的句柄 (具体细节参加句柄表专题)
Alertable 对应 KTHREAD 结构体的 Alertable 属性如果为1在插入用户APC时,该线程将被吵醒
Timeout 超时时间
NtWaitForSingleObject
- 调用ObReferenceObjectByHandle函数,通过对象句柄找到等待对象结构体地址。
- 调用KeWaitForSingleObject函数,进入关键循环。
KeWaitForSingleObject:上半部分
!process 89316020
dt _KTHREAD 892db020
dt _KWAIT_BLOCK 892db020+70
每个等待块大小为0x18。
如果等待3个对象的话它就会占用前3个等待块,最后一个是给定时器用的。
如果你有4个等待对象它就不会用这个位置了,它会一次性分配新的空间。
- 向KTHREAD(+70)位置的等待块赋值。
- 如果超时时间不为0, KTHREAD(+70)第四个等待块与第一个等待块关联起来:第一个等待块指向第四个等待块,第四个等待块指向第一个等待块。
- KTHREAD(+5C)指向第一个KWAIT_BLOCK
- 进入关键循环
KeWaitForSingleObject的关键循环
while(true)//每次线程被其他线程唤醒,都要进入这个循环
{
if(符合**条件)//1超时 2等待对象SignalState > 0
{
//1修改SignalState
//2退出循环
}
else//SignalState不大于0 也没超时
{
if(第一次执行)
{
//将当前线程的等待块挂到等待对象的链表 (WaitListHead) 中;
//将自己挂入等待队列(KiaitListHead)
//切换线程...再次获得CPU时,从这里开始执行
}
}
}
1)线程将自己+5c位置清0
2)释放_KWAIT_BLOCK所占内存
WaitForSingleObject参数说明
kd> dt _DISPATCHER_HEADER
nt!_DISPATCHER_HEADER
+0x000 Type //类型
+0x001 Absolute
+0x002 Size
+0x003 Inserted
+0x004 SignalState //是否有信号 (值大于0就是有信号)
+0x008 WaitListHead //双向链表头,圈着所有等待块
不同的等待对象,用不同的方法来修改_DISPATCHER_HEADER(SignalState)比如:如果可等待对象是EVENT,其他线程通常使用SetEvent来设置SignalState= 1并且,将正在等待该对象的其他线程唤醒,也就是从等待链表(KiWaitListHead)中摘出来。但是, SetEvent函数并不会将线程从等待网上摘下来,是否要下来,由当前线程自己来决定。
关于强制唤醒
在APC专题讲过,当我们插入一个用户APC时(Alertable=1),当前线程是可以被唤醒的,但并不是真正的唤醒。
因为,如果当前的线程在等待网上,执行完用户APC后,仍然要进入等待状态。