本文主要介绍线程的一些基础知识,包括:线程内核对象、线程的创建和退出、线程的初始化和线程上下文等,可以用下面这张思维导图来总结。
一、线程VS进程
内核对象 | 地址空间 | |
进程 | 进程内核对象 | 进程地址空间 |
线程 | 线程内核对象 | 线程栈 |
进程是线程的容器,线程依赖于进程的上下文。
线程要在某个进程的地址空间内执行代码和处理数据。进程内的所有线程共享进程的地址空间(进程上下文)和进程的句柄表。
二、创建线程
一个进程的主线程一般由系统创建,它的入口函数是 main() 或 WinMain() 及其 unicode 版本的函数。
除“主线程”外的其他线程,都是通过 Windows API 创建的。线程的创建过程如下:
1. CreateProcess/_beginthreadex 等函数创建线程内核对象
2. 系统从**进程的地址空间**中分配内存给**线程栈**使用。
3. 新线程与负责创建的那个线程在相同的进程上下文中运行。(创建远程线程例外)
Jeffrey 推荐使用 Microsoft C++ 运行时库中的 _beginthreadex 函数来创建线程,而不是 CreateThread 函数。
uintptr_t _beginthreadex( // NATIVE CODE
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
uintptr_t _beginthreadex( // MANAGED CODE
void *security,
unsigned stack_size,
unsigned ( __clrcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
其中的重要参数包括:
1. Security Descriptor (安全描述符)
2. stack size (线程栈的大小)
3. 线程函数指针(地址)
4. 传递给线程函数的参数(地址)
5. 控制位(CREATE_SUSPENDED or not)
6. 存储线程ID
三、线程的内幕
强烈推荐认真阅读“线程的内幕”这一小节,特别是“图6-1 如何创建和初始化一个线程”。
线程创建和初始化的步骤大致如下:
1. CreateThread 创建线程内核对象
2. 系统检测到新的线程内核对象后,在进程地址空间中分配**线程栈**
3. CPU 加载线程上下文
4. 执行 RtlUserThreadStart
5. RtlUserThreadStart 调用 threadFunction
每个线程都有其自己的一组 CPU 寄存器,称为线程的上下文(context)。上下文反映了当线程上一次执行时,线程的 CPU 寄存器的状态。线程的 CPU 寄存器全部保存在一个 CONTEXT 结构(WinNT.h)中。CONTEXT 结构本身保存在线程内核对象中。
指令指针寄存器(IP)和栈指针寄存器(SP)是线程上下文中最重要的两个寄存器。记住,线程始终在进程的上下文中运行。
四、将伪句柄转换为真正的句柄
GetCurrentProcess 和 GetCurrentThread 函数返回的都是“伪句柄”,而通过 DuplicateHandle 函数可以将伪句柄转换为真正的句柄。