我们已经了解如何定义线程入口点函数、调用系统API创建执行指定函数的线程。本节将揭示这一切在系统内部是如何完成的。
图6-1描述了线程创建并完成初始化后的状态。调用CreateThread会使系统产生一个线程内核对象,其引用计数(Usage count)被初始化为2(创建线程的进程和线程本身都引用了该内核对象),其它属性也完成了初始化:暂停计数(Suspend count)被设置为1,线程退出码(Exit code)被设置为STILL_ACTIVE,内核对象状态被置为nonsignaled(Signaled)。
内核对象创建完成后,系统在当前进程中为新线程的线程栈分配内存,并在线程栈的高位写入两个值。第1个指是CreateThread函数的pvParam参数,然后是pfnStartAddr参数。
每个线程有自己的CPU寄存器组,被称为线程上下文(Context),线程的上下文反映了线程最后一次执行时其CPU寄存器的状态。WinNT.h中定义了CONTEXT结构对应线程的CPU寄存器组,线程的CONTEXT实例保存在其内核对象中。
线程上下文中最重要的两个寄存器是指令指针寄存器(Instruction pointer)和栈指针寄存器(Stack pointer)。由于线程是运行在进程地址空间内的,因此其栈中的地址就是进程地址空间中的地址。线程内核对象初始化时,其CONTEXT结构中的栈指针寄存器内容被设置为pfnStartAddr在线程栈中的地址,指令指针寄存器内容被设置为名为RtlUserThreadStart的函数的地址,该函数在NTDLL.dll中定义,基本框架如下所示:
VOID RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) { __try { ExitThread((pfnStartAddr)(pvParam)); } __except(UnhandledExceptionFilter(GetExceptionInformation())) { ExitProcess(GetExceptionCode()); } // NOTE: We never get here. }
线程初始化完成后,系统会检查CreateThread是否传递了CREATE_SUSPENDED参数,如果没有的话,系统会把线程内核对象的暂停计数设置为0并马上调度线程,然后将线程上下文中的值加载到CPU的寄存器组中,由于线程指令指针寄存器指向RtlUserThreadStart,因此线程将以RtlUserThreadStart开始执行,RtlUserThreadStart又调用了CreateThread为线程设置的入口点函数pfnStartAddr,并在pfnStartAddr返回时调用ExitThread退出线程。要注意假如在pfnStartAddr执行时发生了未处理的异常,RtlUserThreadStart中的结构化异常处理框架将执行ExitProcess,导致整个进程的退出。