让我们来看看CreateThread都干了些什么。
上图显示了系统在创建线程和对线程进行初始化时必须做些什么工作。调用CreateThread可使系统创建一个线程内核对象。该对象的初始使用计数是2(在线程停止运行和从CreateThread返回的句柄关闭之前,线程内核对象不会被撤消)。线程的内核对象的其他属性也被初始化,暂停计数被设置为1,退出代码始终为STILL_ACTIVE(0 x 1 0 3),该对象设置为未通知状态。
一旦内核对象创建完成,系统就分配用于线程的堆栈的内存。该内存是从进程的地址空间分配而来的,因为线程并不拥有它自己的地址空间。然后系统将两个值写入新线程的堆栈的上端(线程堆栈总是从内存的高地址向低地址建立)。写入堆栈的第一个值是传递给CreateThread的pvParam参数的值。紧靠它的下面是传递给CreateThread的pfnStartAddr参数的值。每个线程都有它自己的一组C P U寄存器,称为线程的上下文。该上下文反映了线程上次运行时该线程的CPU寄存器的状态。线程的这组C P U寄存器保存在一个CONTEXT结构。CONTEXT结构本身则包含在线程的内核对象中。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器。线程总是在进程的上下文中运行的。因此,这些地址都用于标识拥有线程的进程地址空间中的内存。当线程的内核对象被初始化时,CONTEXT结构的堆栈指针寄存器被设置为线程堆栈上用来放置pfnStartAddr的地址。当线程完全初始化后,系统就要查看CREATE_SUSPENDED标志是否已经传递给CreateThread。如果该标志没有传递,系统便将线程的暂停计数递减为0,该线程可以调度到一个进程中。然后系统用上次保存在线程上下文中的值加载到实际的C
P U寄存器中。这时线程就可以执行代码,并对它的进程的地址空间中的数据进行操作。
在这里,我还要简单的描述一下CONTEXT结构,因为WIN32是抢占式操作系统,一个线程几乎不可能永远的占据CPU,也就是说,它会在一定时间后(在WINDOWS中,大概式20ms的时间),被CPU放在一边,一段时间之后,才可以重新获得CPU时间片,此时就有一个问题,线程现在执行到了那里,CPU在再次分配给它时间片执行的时候,必须知道这些信息,难道要从0开始吗?CONTEXT结构的作用就是用来解决这个问题。
在Platform SDK中,你可以看到下面的信息:
“CONTEXT结构包含了特定处理器的寄存器数据。系统使用CONTEXT结构执行各种内部操作。目前,已经存在为Intel、MIPS、Alpha和PowerPC处理器定义的CONTEXT结构。若要了解这些结构的定义,参见头文件WinNT.h”。
该文档并没有说明该结构的成员,也没有描述这些成员是谁,因为这些成员要取决于Windows在哪个CPU上运行。实际上,在Windows定义的所有数据结构中,CONTEXT结构是特定于CPU的唯一数据结构。那么CONTEXT结构中究竟存在哪些东西呢?它包含了主机C P U上的每个寄存器的数据结构。在x86计算机上,数据成员是Eax、Ebx、Ecx、Edx等等。如果是Alpha处理器,那么数据成员包括IntV0、IntT0、IntT1、IntS0、In tRa和IntZero等等。
Windows实际上允许查看线程内核对象的内部情况,以便抓取它当前的一组CPU寄存器。若要进行这项操作,只需要调用GetThreadContext函数。关于此函数的使用,我们下次再说。