分类: Windows平台
2014-01-03 20:56:35
一个进程是由一个进程内核对象和一个地址空间组成,而一个线程是由一个线程内核对象和一个线程栈组成。
线程函数
入口点函数形式如下:
DWORD WINAPI ThreadFunc(PVOID pvParam){
DWORD dwResult=0;
……
return dwResult
}
① 线程函数必须返回一个值,它会成为该线程的退出代码,这类似于C++运行库策略:令主线程的退出代码成为进程的退出代码
② 线程函数应该尽可能使用函数参数和局部变量,而少用全局和静态变量。
使用CreateThread函数创建线程
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,
DWORD cbStack,
LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpvThreadParam,
DWORD fdwCreate,
LPDWORD lpIDThread
);
cbStackSize参数指定线程可以为其线程栈使用多少地址空间。而创建主线程的时候进程使用的是/STACK开关来控制这个值
/STACK:[reserve][,commit]
reserve参数用于设置系统将为线程栈预留多少地址空间,默认是1MB,commit参数指定最初应为栈预留的地址空间域调拨多少内存空间给,默认是一个页面,32位系统为4KB。
如果cbStackSize参数传入非0值,函数会为线程栈预订空间并为之调拨所需要的所有的存储空间。如果传入0值,CreateThread函数就会根据/STACK来调拨存储器
尽量避免使用ExitThread与TerminateThread函数结束一个线程,因为这两个函数终止线程运行并清理该线程使用的所有操作系统资源,但是C/C++资源(比如对象)不会被销毁。所以最好的做法是直接从线程函数返回。
用TerminateThread终止线程运行,除非是拥有此线程的进程终止运行,否则系统不会销毁这个线程的堆栈。目的是使其它可能访问该线程栈的线程继续正常运行。
CreateThread函数创建一个线程内核对象,最初信息 使用计数为2,暂停计数为1,退出代码为STILL_ACTIVE,对象为未触发状态。
① 新线程内核对象创建时,系统会为线程分配内存,初始化线程栈供其使用。
② 将pvParam和pfnStartAddr两个值压入栈底。
③ 初始化一个线程上下文CONTEXT结构,并把栈指针SP设为pfnStartAddr在线程堆栈中的地址,指令指针IP值被设置为RtlUserThreadStart函数的入口地址。在函数RtlUserThreadStart中是执行线程函数。
④ 系统检查CREATE_SUSPENDED标志是否传给CreateThread函数,如果没有系统将线程扶起计数减到0,随后线程就可以执行。
⑤ 普通线程执行结束线程函数后将返回到RtlUserThreadStart函数内,RtlUserThreadStart会在随后调用ExitThread来结束线程运行,而主线程在线程函数(main或WinMain)函数返回时将永远不会返回到RtlUserThreadStart函数,而是C/C++运行时启动代码会调用ExitProcess来结束进程。
创建新线程的时候,一定不要调用操作系统的CreateThread函数,而必须调用C/C++运行库函数_beginthreadex。这是因为_beginthreadex做了很多CreateThread不能做的事情:
_beginthreadex函数创建的每个线程都有专用的_tiddata内存块,传给_beginthreadex的线程函数地址与参数都保存在_tiddata中,在_beginthreadex中会使用CreateThread函数创建线程,但是传给它的函数的地址却不是pfnStartAddr,而是_threadstartex,而且参数也不是pvParam,而是_tiddata结构的地址。所以CreateThread生成的新线程会从RtlUserThreadStart开始执行,然后再跳转到_threadstartex函数,_threadstartex将_tiddata内存块与新建线程关联起来(使用TLS关联)再调用_callthreadstartex辅助函数,在_callthreadstartex中,有一个SEH帧,它将要执行的pfnStartAddr函数包围起来,这个帧可以处理很多与运行库有关的事情,比如未被捕捉的C++异常和C/C++运行库的signal函数,而直接用CreateThread函数新建一个线程,再调用C/C++运行库的signal函数,它将不能工作。_callthreadstartex在pfnStartAddr返回到_endthreadex函数,_endthreadex函数将把_tiddata数据块释放,再调用ExitThread函数来实际销毁线程。.
注:
① 用_beginthreadex而不要用CreateThread创建线程。
② 绝对不应该用_beginthread和_endthread这两个C/C++运行库函数。
③ GetCurrentProcess和GetCurrentThread这两个函数返回的是主调线程的进程内核对象或线程内核对象的一个伪句柄。如果想获得真正的句柄,可以使用DuplicateHandle函数进行转换。