Windows核心编程读书笔记2——线程结构分解
线程结构如上一篇文章所述,系统创建线程时,会分配一个内核对象与线程栈。如下图
线程内核对象如图左侧,其初始为 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时传入的线程参数与线程函数地址。 |