线程的组成:
线程内核对象和线程堆栈(系统从进程的地址空间中分配内存给线程栈使用,因为线程没有自己的地址空间)。
线程还有自己的一组CPU寄存器。
线程1M的栈空间是从进程栈空间中分配的,每个线程占用不同的一块栈空间。如下图:
线程的创建:
CreateThread()的参数如下:
psa:内核对象的安全属性(传入NULL,表示使用默认安全属性)。
cbStackSize:指定线程栈可使用多少地址空间。
(
线程栈的空间大小,除了通过cbStackSize来设置,也可以通过链接器的/STACK开关来设置。格式如下:
/STACK: [reserve] [,commit]:
reserve:线程栈的内存空间(默认为1MB);
commit:线程栈的物理存储器大小(默认为1个页面)。
链接器会把该参数嵌入到.exe或.dll的PE头文件中。
l 若cbStackSize参数设置为0,系统将根据/STACK来设置默认的栈空间大小;
l 若cbStackSize参数为非0值,分配栈空间的时候,按照cbStatckSize和/STACK中比较大的值来分配。
)
pfnStartAddr:线程函数的地址;
pvParam:线程开始执行时,将该参数传给线程函数(给线程函数传一个初始值);
dwCreateFlags:
l 值为0:线程创建后立即调度;
l 值为CREATE_SUSPENDED:线程创建后挂起(可以在线程函数执行之前,更改线程属性,再唤醒它);
pdwThreadID:线程ID,线程创建成功,将ID传给该参数(一般传入NULL,表示不想知道线程的ID)。
线程的终止:
ExitThread()、TerminateThread()函数。
l 若调用ExitThread来终止线程,该线程的线程栈会被销毁;
l 若调用TerminateThread来终止线程,只有当创建该线程的进程退出时,线程栈才被销毁。
线程终止时,将执行以下操作:
(1) 释放属于该线程的用户对象句柄(线程有两个用户对象:窗口和挂钩);
(2) 设置线程退出代码;
(3) 线程内核对象变为触发状态;
(4) 如果它是进程的最后一个活动线程,那么进程也随之终止;
(5) 线程内核对象计数减1。
线程的CPU寄存器:
又称线程上下文(context),上下文反映了当线程上一次执行时,线程的CPU寄存器状态。(线程的CPU寄存器,保存在一个CONTEXT结构中,而CONTEXT结构本身保存在线程的内核对象中)。
上下文中最重要的两个寄存器:指令指针寄存器、栈指针寄存器。
RtlUserThreadStart()函数:
原型:VOID RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam);
线程内核对象初始化的时候,会调用该函数:
l CONTEXT结构的堆栈指针寄存器被设定为pfnStartAddr在线程堆栈中的地址。
l 指令指针寄存器设定为RtlUserThreadStart函数的地址。
(
RtlUserThreadStart函数就是线程的启动函数,执行过程类似于进程的启动函数。
pvParam参数:就是CreateThread()函数传入的那个参数;
如果线程出现一个未处理异常,将会弹出消息框,并最终通过ExitProcess来终止整个进程,而不只是终止本线程。
)
_beginthreadex()函数:
l 每个线程有自己专用的_tiddata内存块,是从C/C++运行库堆栈上分配的。线程函数的地址保存在_tiddata中。
l _beginthreadex函数内部会调用CreateThread函数。
(建议使用_beginthreadex函数,而不用CreateThread来创建线程)
_endthreadex()函数:
l 获取_tiddata内存块地址,并将其释放;
l 调用ExitThread销毁线程。
(不鼓励使用_endthreadex函数来终止线程,但至少比ExitThread要好点。因为ExitThread不释放_tiddata内存)
伪句柄:
所谓“真正的句柄”,是指能明确、无歧义地标示一个线程的句柄。但有些函数返回的是伪句柄(如GetCurrentProcess、GetCurrentThread)。
伪句柄有以下特点:
l 伪句柄指向的对象会根据调用者的不同而改变。因此,不能从一个调用者中获取伪句柄,然后传给另一个调用者调用,因为这将导致两个调用者调用的并不是同一个对象。
l 调用伪句柄,并不会增加内核对象的使用计数。调用CloseHandle()函数并不会关闭内核对象,只是简单的忽略,并返回FALSE。
l 传递伪句柄的方法是:将该句柄复制,再传递(用DuplicateHandle)。