线程结构

如上一篇文章所述,系统创建线程时,会分配一个内核对象与线程栈。如下图

 

线程内核对象如图左侧,其初始为

1、引用计数为2

2、挂起计数为1(此时线程无法运行,当线程初始化好后,若未设置CREATE_SUSPENDED标志,则系统会自动将挂起计数减至0,线程为可调度状态)

3、退出代码为STILL_ACTIVE状态

4、内核对象未触发状态

5、记录线程上下文的CONTEXT结构为初始值(所谓线程切换,其实就是根据CONTEXT结构数据更新CPU寄存器内容)。注意其中的SP(栈指针寄存器)与IP(指令指针寄存器)。

SP指向pfnStartAddr而IP指向NTDLL.dll导出的

RtlUserThreadStart函数。这说明,其实每个新建的线程,其运行入口并不是我们传入的线程函数,而是统一会由系统调用RtlUserThreadStart。

RtlUserThreadStart函数定义如下

 

VOID RtlUserStartThread(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParm){    __try    {        ExitThread((pfnStartAddr)(pvParm));    }    __except(UnhandledExceptionFilter(GetExceptionInformation()))    {        ExitProcess(GetExceptionCode());    }    // Note: We never get here}

观察RtlUserThreadStart函数,可得到如下事实:

 

1、RtlUserThreadStart函数最终调用ExitThread函数退出线程并设置退出码。

2、若线程运行期由任何异常,则会被捕获并结束整个进程。

3、RtlUserThreadStart只会被操作系统调用来开启线程。

4、RtlUserThreadStart会为线程的返回地址压栈,让线程可返回。但RtlUserThreadStart本身永远不会返回,因为在函数返回前,其线程已经结束(如代码中注释一样)。

5、当进程运行主线程时,RtlUserThreadStart会调用C/C++的运行库启动代码,并有启动代码调用main函数,当线程由main返回时,C/C++启动代码会调用ExitProcess退出进程。

线程栈如图右侧所示。

1、线程栈空间来自进程空间。

2、线程栈空间由高向低扩展。

3、线程栈系统会默认写入两个值,分别是CreateThread时传入的线程参数与线程函数地址。