(2)线程内幕
最后更新于:2022-04-01 21:44:45
## 线程结构
如上一篇文章所述,系统创建线程时,会分配一个内核对象与线程栈。如下图
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/10c78ca2c0bb6ff71b24c89438335509_616x298.jpg)
**线程内核对象如图左侧,其初始为**
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时传入的线程参数与线程函数地址。
';