对于_beginthreadex函数有以下重点
1) 每个线程都有自己的专用_tiddata内存块,它们是从C/C++运行库的堆(heap)上分配的。
2)传给_beginthreadex的线程函数的地址保存在_tiddata内存块中。
3)_beginthreadex确实会在内部调用CreateThread,因为操作系统只知道用这种方式来创建一个新线程。
4)CreateThread函数被调用时,传给它的函数地址是_threadstartex(而非pfnStartAddr)。另外,参数地址是_tiddata结构的地址,而非pvParam。
5)如果一切顺利,会返回线程的句柄,就像CreateThread那样。任何操作失败,会返回0。
为新线程初始化_tiddata结构之后,接着来看看这个结构如何与线程关联的:
static unsigned long WINAPI _threadstartex (void* ptd) { // Note: ptd is the address of this thread's tiddata block. // _getptd() will be able to find it in _callthreadstartex. TlsSetValue(__tlsindex, ptd); // Initialize floating-point support (code not shown). // call helper function. static void _callthreadstartex(void) { // get the pointer to thread data from TLS // Wrap desired thread function in SEH frame to // The C run-time's exception handler deals with run-time errors // and signal support; we should never get it here. _exit(GetExceptionCode()); |
关于_threadstartex函数,要注意一下几大重点:
1)新的线程首先执行RtlUserThreadStart(在NTDLL.dll文件中),然后再跳转到_threadstartex;
2)_threadstartex唯一的参数就是新线程的_tiddata内存块的地址;
3)TlsSetValue是一个操作系统函数,它将一个值与主调函数关联起来。这就是所谓的线程局部存储(Thread Local Storage,TLS)。_threadstartex函数将_tiddata内存块与新建线程关联起来;
4)在无参数的辅助函数_callthreadstartex中,有一个SEH帧,它将预期要执行的线程函数包围起来。这个帧处理着与运行库有关的许多事情—比如运行时错误;
5)预期要执行的线程函数会被调用,并向其传递预期的参数。函数的地址和参数会被保存在TLS的_tiddata数据块中,并会在_callthreadstartex中从TLS中获取;
6)线程函数的返回值被认为是线程的退出代码;