Windows线程生灭 (二)
上节中介绍了几种Windows平台创建及删除线程的api及它们的差别,这节具体介绍以下信息:
1.线程内核对象(操作系统接口CreateThread内部实现)
2.线程数据块_tiddata(C/C++运行时库的实现 _beginthreadex与_beginthread)
3.线程结束_endthreadex
下面分别介绍
一、线程内核对象
线程创建时,会先创建一个线程内核对象(分配在进程的地址空间上),如上图,存储上下文context(一个数据结构)及一些统计信息,具体包括:
1.寄存器SP:指向栈中线程函数指针的地址
2.寄存器IP:指向装载的NTDLL.dll里RtlUserThreadStart函数地址
3.Usage Count:引用计数,初始化为2
4.Suspend Count:挂起数,初始化为1。
5.ExitCode:退出代码,线程在运行时为STILL_ACTIVE(且初始化为该值)
6.Signaled:初始化为未触发状态
RtlUserThreadStart(...)
函数原型如下:
RtlUserThreadStart函数是线程真正起始执行的地方,因为新线程的指令指针是指向这个函数 。RtlUserThreadStart函数原型使你认为它收到二个参数,但那不是真的,这只是意味着它被调用自另一个函数。新的线程只不过在这里生效和开始执行。
RtlUserThreadStart函数之所以认为被调用自另一个函数,是因为它获得了二个参数。但是获得这些参数的途径是由于操作系统把这些值直接写入了线程的堆栈(通常参数被传递到函数的方法)。
注意,一些CPU架构用CPU寄存器来传递参数,而不是堆栈。对于这些架构,系统在同意线程执行RtlUserThreadStart函数之前会对相应的寄存器进行正确的初始化。
(在32位Windows中用的是BaseThreadStart函数而不是64位Windows中的RtlUserThreadStart函数,BaseThreadStart函数来出自Kernel32.dll组件,而RtlUserThreadStart函数函数来出自于NTDLL.dll组件)
RtlUserThreadStart函数是线程真正开始执行的地方,在函数中
(1)设置了一个围绕线程函数的结构化异常处理SEH帧
(2)线程函数返回时,调用ExitThread,并将线程函数返回值作为参数传递进去。线程内核对象的使用计数递减,而后线程停止执行。
(3)执行期间若发生未被处理的异常,则调用异常处理块中的ExitProgress()关闭进程
当一个程序运行时,会生成一个主线程,之后RtlUserThreadStart开始执行,调用C/C++运行库的代码,后者初始化继而访问你的程序入口函数(_tmain,_tWinMain等);入口函数返回时,C/C++运行时启动代码会调用ExitProcess来结束进程。
因此使用CreateThread生成线程应有二步
(1)生成线程内核对象并初始化
(2)由内核对象指向的RtlUserThreadStart运行线程函数
二、线程数据块_tiddata
线程数据块是_beginthreadex函数维护的一个数据结构,存储了线程相关的一些信息。我们先来看_beginthreadex的源码(VS2008的存储在C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\threadex.c中):
_MCRTIMP uintptr_t __cdecl _beginthreadex (void *security,unsigned stacksize,unsigned (__CLR_OR_STD_CALL * initialcode) (void *),void * argument,unsigned createflag,unsigned *thrdaddr) {_ptiddata ptd; /* pointer to per-thread data */uintptr_t thdl; /* thread handle */unsigned long err = 0L; /* Return from GetLastError() */unsigned dummyid; /* dummy returned thread ID *//* validation section */_VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);/* Initialize FlsGetValue function pointer */__set_flsgetvalue();/** Allocate and initialize a per-thread data structure for the to-* be-created thread.*/if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )goto error_return;/** Initialize the per-thread data*/_initptd(ptd, _getptd()->ptlocinfo);ptd->_initaddr = (void *) initialcode;ptd->_initarg = argument;ptd->_thandle = (uintptr_t)(-1);#if defined (_M_CEE) || defined (MRTDLL)if(!_getdomain(&(ptd->__initDomain))){goto error_return;} #endif /* defined (_M_CEE) || defined (MRTDLL) *//** Make sure non-NULL thrdaddr is passed to CreateThread*/if ( thrdaddr == NULL )thrdaddr = &dummyid;/** Create the new thread using the parameters supplied by the caller.*/if ( (thdl = (uintptr_t)CreateThread( (LPSECURITY_ATTRIBUTES)security,stacksize,_threadstartex,(LPVOID)ptd,createflag,(LPDWORD)thrdaddr))== (uintptr_t)0 ){err = GetLastError();goto error_return;}/** Good return*/return(thdl);/** Error return*/ error_return:/** Either ptd is NULL, or it points to the no-longer-necessary block* calloc-ed for the _tiddata struct which should now be freed up.*/_free_crt(ptd);/** Map the error, if necessary.** Note: this routine returns 0 for failure, just like the Win32* API CreateThread, but _beginthread() returns -1 for failure.*/if ( err != 0L )_dosmaperr(err);return( (uintptr_t)0 ); }
其中被标红加粗的二部分是重点,即首先初始化了一个线程数据块(_ptiddata ptd),将线程函数地址及参数设置到线程数据块内,该块是分配在堆上的。之后调用CreateThread函数创建线程,要注意传入该函数的参数,即要运行的函数_threadstartex(注意不是线程函数), 其参数是线程数据块(LPVOID)ptd
_threadstartex的功能是
1.将新建线程与内存数据块关联(__fls_setvalue,该函数是操作系统函数,即所谓的线程局部存储(Thread Local Storage, TLS))
2.调用_callthreadstartex来执行及终结真正的线程函数
static unsigned long WINAPI _threadstartex (void * ptd) {_ptiddata _ptd; /* pointer to per-thread data *//* Initialize FlsGetValue function pointer */__set_flsgetvalue();/** Check if ptd is initialised during THREAD_ATTACH call to dll mains*/if ( ( _ptd = (_ptiddata)__fls_getvalue(__get_flsindex())) == NULL){/** Stash the pointer to the per-thread data stucture in TLS*/if ( !__fls_setvalue(__get_flsindex(), ptd) )ExitThread(GetLastError());/** Set the thread ID field -- parent thread cannot set it after* CreateThread() returns since the child thread might have run* to completion and already freed its per-thread data block!*/((_ptiddata) ptd)->_tid = GetCurrentThreadId();}else{_ptd->_initaddr = ((_ptiddata) ptd)->_initaddr;_ptd->_initarg = ((_ptiddata) ptd)->_initarg;_ptd->_thandle = ((_ptiddata) ptd)->_thandle; #if defined (_M_CEE) || defined (MRTDLL)_ptd->__initDomain=((_ptiddata) ptd)->__initDomain; #endif /* defined (_M_CEE) || defined (MRTDLL) */_freefls(ptd);ptd = _ptd;}/** Call fp initialization, if necessary*/ #ifndef MRTDLL #ifdef CRTDLL_fpclear(); #else /* CRTDLL */if (_FPmtinit != NULL &&_IsNonwritableInCurrentImage((PBYTE)&_FPmtinit)){(*_FPmtinit)();} #endif /* CRTDLL */ #endif /* MRTDLL */#if defined (_M_CEE) || defined (MRTDLL)DWORD domain=0;if(!_getdomain(&domain)){ExitThread(0);}if(domain!=_ptd->__initDomain){/* need to transition to caller's domain and startup there*/::msclr::call_in_appdomain(_ptd->__initDomain, _callthreadstartex);return 0L;} #endif /* defined (_M_CEE) || defined (MRTDLL) */_callthreadstartex();/** Never executed!*/return(0L); }
static void _callthreadstartex(void) {_ptiddata ptd; /* pointer to thread's _tiddata struct *//* must always exist at this point */ptd = _getptd();/** Guard call to user code with a _try - _except statement to* implement runtime errors and signal support*/__try {_endthreadex (( (unsigned (__CLR_OR_STD_CALL
*)(void *))(((_ptiddata)ptd)->_initaddr) ) ( ((_ptiddata)ptd)->
_initarg ) ) ;}__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ){/** Should never reach here*/_exit( GetExceptionCode() );} /* end of _try - _except */}
_callthreadstartex函数功能如下:
1. 如上标红地方运行真正线程函数
2.将真正线程函数运行完的返回值作为返回代码传递给_endthreadex结束该线程
至此,_beginthreadex就运行完毕了。
这里_callthreadstartex调用_endthreadex直接删除线程,而不是回退到_threadstartex,再到RtlUserThreadStart, 若直接返回的话,线程数据块并未删除,会造成内存泄露。
总结下_beginthreadex的运行过程
1.先生成并初始化_tiddata内存块,将线程函数地址及参数传递进去
2.调用CreateThread生成线程,运用RtlUserThreadStart函数运行线程函数(但要运行的函数为_threadstartex,参数为线程数据块地址)
3._threadstartex将通过线程局部存储(TLS)将线程数据块与运行线程绑定
4._threadstartex调用_callthreadstartex,运行真正的线程函数,当真正线程函数正确返回后用_endthreadex结束;若出错,返回0;
下面附上_tiddata的具体内容,可以参考下(VS2008,C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\mtdll.h)。
struct _tiddata {unsigned long _tid; /* thread ID */uintptr_t _thandle; /* thread handle */int _terrno; /* errno value */unsigned long _tdoserrno; /* _doserrno value */unsigned int _fpds; /* Floating Point data segment */unsigned long _holdrand; /* rand() seed value */char * _token; /* ptr to strtok() token */wchar_t * _wtoken; /* ptr to wcstok() token */unsigned char * _mtoken; /* ptr to _mbstok() token *//* following pointers get malloc'd at runtime */char * _errmsg; /* ptr to strerror()/_strerror() buff */wchar_t * _werrmsg; /* ptr to _wcserror()/__wcserror() buff */char * _namebuf0; /* ptr to tmpnam() buffer */wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */char * _namebuf1; /* ptr to tmpfile() buffer */wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */char * _asctimebuf; /* ptr to asctime() buffer */wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */void * _gmtimebuf; /* ptr to gmtime() structure */char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */unsigned char _con_ch_buf[MB_LEN_MAX];/* ptr to putch() buffer */unsigned short _ch_buf_used; /* if the _con_ch_buf is used *//* following fields are needed by _beginthread code */void * _initaddr; /* initial user thread address */void * _initarg; /* initial user thread argument *//* following three fields are needed to support signal handling and* runtime errors */void * _pxcptacttab; /* ptr to exception-action table */void * _tpxcptinfoptrs; /* ptr to exception info pointers */int _tfpecode; /* float point exception code *//* pointer to the copy of the multibyte character information used by* the thread */pthreadmbcinfo ptmbcinfo;/* pointer to the copy of the locale informaton used by the thead */pthreadlocinfo ptlocinfo;int _ownlocale; /* if 1, this thread owns its own locale *//* following field is needed by NLG routines */unsigned long _NLG_dwCode;/** Per-Thread data needed by C++ Exception Handling*/void * _terminate; /* terminate() routine */void * _unexpected; /* unexpected() routine */void * _translator; /* S.E. translator */void * _purecall; /* called when pure virtual happens */void * _curexception; /* current exception */void * _curcontext; /* current exception context */int _ProcessingThrow; /* for uncaught_exception */void * _curexcspec; /* for handling exceptions thrown from std::unexpected */ #if defined (_M_IA64) || defined (_M_AMD64)void * _pExitContext;void * _pUnwindContext;void * _pFrameInfoChain;unsigned __int64 _ImageBase; #if defined (_M_IA64)unsigned __int64 _TargetGp; #endif /* defined (_M_IA64) */unsigned __int64 _ThrowImageBase;void * _pForeignException; #elif defined (_M_IX86)void * _pFrameInfoChain; #endif /* defined (_M_IX86) */_setloc_struct _setloc_data;#ifdef _M_IX86void * _encode_ptr; /* EncodePointer() routine */void * _decode_ptr; /* DecodePointer() routine */ #endif /* _M_IX86 */void * _reserved1; /* nothing */void * _reserved2; /* nothing */void * _reserved3; /* nothing */int _cxxReThrow; /* Set to True if it's a rethrown C++ Exception */unsigned long __initDomain; /* initial domain used by _beginthread[ex] for managed function */};typedef struct _tiddata * _ptiddata;
三、线程终结
上文文中_callthreadstartex函数用_endthreadex来终结线程,源码如下:
void __cdecl _endthreadex (unsigned retcode) {_ptiddata ptd; /* pointer to thread's _tiddata struct *//** Call fp termination, if necessary*/ #ifdef CRTDLL_fpclear(); #else /* CRTDLL */if (_FPmtterm != NULL &&_IsNonwritableInCurrentImage((PBYTE)&_FPmtterm)){(*_FPmtterm)();} #endif /* CRTDLL */ptd = _getptd_noexit();if (ptd) {/** Free up the _tiddata structure & its subordinate buffers* _freeptd() will also clear the value for this thread* of the FLS variable __flsindex.*/_freeptd(ptd);}/** Terminate the thread*/ExitThread(retcode);}
所以,_endthreadex的功能如下:
1.删除与该线程相关的线程数据块
2.调用ExitThread(与CreateThread相对)终结并传递退出代码
参考:
<<Windows核心编程 第五版>>
Windows via C/C++ 中“RtlUserThreadStart函数”的翻译
_beginThreadex创建多线程解读
VC源码:C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\mtdll.h || threadex.c(VS2008)
转载于:https://www.cnblogs.com/t427/archive/2012/11/27/2790808.html
Windows线程生灭 (二)相关推荐
- linux线程篇,linux线程篇 (二) 线程的基本操作
线程 进程 标识符 pthread_t pid_t 获取ID pthread_self() getpid() 创建 pthread_create() fork 销毁 pthread_exit() ex ...
- 【操作系统】操作系统知识点整理;C++ 实现线程池与windows 线程池的使用;
文章目录 体系结构 冯诺依曼 存储结构 cache常见的组织结构 cache命中 缓存一致性 硬中断.软中断 操作系统结构 内核 Linux宏内核 内存管理 虚拟内存 内存管理 - 分段 - 分页 - ...
- MySql5.7.11 for Windows 安装(二)
原文:MySql5.7.11 for Windows 安装(二) 安装之后,首先创建data文件夹(旧版本本来就有),管理员打开cmd,cd到bin文件夹,输入 mysqld –initialize- ...
- 《Windows via C/C++》学习笔记 —— Windows 线程池
线程池(thread pool),允许有多个线程同时存在,并发执行,并且这些线程受到统一管理. 在Windows Vista中,提供了全新的线程池机制,一般这些线程池中的线程的创建的销毁是由操作系统自 ...
- Windows线程同步机制的区别与比较及进程通信方法
原文:http://blog.csdn.net/eulb/article/details/2177500 多线程同步机制 (Windows) 线程的同步机制: 1. Event 用事件(Event ...
- 进程线程004 Windows线程切换的三种方式
文章目录 主动切换(调用API) KiSwapContext函数分析 哪些API调用了SwapContext函数 总结 时钟中断切换 如何中断一个正在执行的程序 系统时钟 时钟中断的执行流程 总结 时 ...
- 9.Windows线程切换_TSS
SwapContext这个函数是Windows线程切换的核心,无论是主动切换还,是系统时钟导致的线程切换,最终都会调用这个函数. 在这个函数中除了切换堆栈以外,还做了很多其他的事情,了解这些细节对我们 ...
- Linux线程(二)
linux线程(二) 文章目录 linux线程(二) 一.线程终止 二.线程等待 三.线程分离 四.线程互斥 一.线程终止 1.pthread_exit函数: 只需终止某个线程而不需要终止整个进程的三 ...
- python线程(二)代码部分Threading模块
一.Thread类简介 1.Thread类参数简介 class threading.Thread(group=None, target=None, name=None, args=(), kwargs ...
- 使用.NET Core创建Windows服务(二) - 使用Topshelf方式
原文:Creating Windows Services In .NET Core – Part 2 – The "Topshelf" Way 作者:Dotnet Core Tut ...
最新文章
- 中国石化行业节能减排行业发展动态与运营前景规划展望报告2022年
- Raw264.7培养经验分享
- Power Transmission
- chkconfig 系统服务管理
- 绕过宝塔禁止的php函数,宝塔disable functions函数全被禁命令执行+加域服务器如何无限制执行命令...
- iMeta宏基因组生物信息期刊-创刊背景和简介
- python爬虫 点击下一页_python爬虫实现获取下一页代码
- 真正好的东西,就会脱颖而出
- Thinking in UML 学习笔记(一)——建立对象模型
- Gstreamer之gst_buffer_map()用法(十七)
- VB模拟按键终极教程
- 自信息量,熵及其性质
- UIView隐藏超出superview的部分
- 按关键字爬取百度图片
- 如何在python 设置输入字符的 颜色 背景色,前景色
- Jenkins流水线部署java项目
- Allegro-CRTL键的使用
- Jenkins配置定时任务
- element 合计
- AUTOSAR LIN Introduction