Windows核心编程笔记(4)----线程

1、进程与线程
进程是惰性的,从来不执行任何东西,它只是一个线程的容器。线程必定是在某个进程的上下文中创建的,
而且其生命周期都在该进程中。因为句柄表是针对每一个进程的,因此同一个进程中的多个线程可以共享
内核对象句柄。进程运行需要占用许多的内存资源(加载DLL等),进程只需要一个内核对象和一个进程栈,
无需占用多少内存。
2、终止线程的几种方式:
2.1线程函数返回(强烈推荐)
2.2通过ExitThread函数杀死自己(自杀,不推荐)
终止线程运行, 操作系统清理线程使用的系统资源,但是C/C++对象不会被析构。
2.3同一个进程或者另一个进程中的线程调用TerminateThread函数终止进程(他杀,不推荐)
和杀进程一样,TerminateThread是异步执行的, 通知系统要终止线程,但在函数返回时并不能确定线程
已经被终止,可以使用WaitForSingleObject来等待;
如果是ExitThread终止线程,该线程的堆栈是会被清理掉的。但是如果使用TerminateThread,那么除非
拥有该线程的进程终止,否则系统不会销毁这个线程的堆栈(很重要,对于长时间运行的程序,如果我们
一直重复创建线程、杀死线程可能会造成内存不断的增长,最终程序可能崩溃)。被杀死线程的堆栈没有
被清理,其他线程依然可以正常访问。

另外,DLL通常在线程正常终止时收到通知,释放相关资源。但是,线程被TerminateThread强制杀死时,
DLL不会收到通知,也就不能进行正常的清理工作。
2.4线程所在的进程终止运行
程序运行过程:C\C++运行库调初始化后调用main\WinMain函数,mian函数返回后,C\C++运行库调用ExitProcess
退出进程。在多个线程乐观运行时,需要在主线程返回之前处理好每个线程的终止过程。否则,其他正在
运行的线程会在C\C++运行库调用ExitProcess后突然死亡。
3、线程正常终止过程
(1)线程所拥有的用户对象句柄会被释放掉,一个线程有两个用户对象:窗口和挂钩。一旦线程退出,系统会销毁
其创建的窗口,并卸载由线程创建或者挂载的挂钩。其他对象只有在拥有线程的进程终止时才会被销毁;
(2)线程的退出码从STILL_ACTIVE百年城传递给退出线程函数的代码;
(3)线程的内核对象状态变为触发状态;
(4)如果线程是进程中的最后一个活动线程,系统认为进程也终止了;
(5)线程内核对象的应用计数-1。
其他线程可以通过GetExitCodeThread()来检查hThread所标识的那个线程是否已经终止运行,如果线程未终止则
返回STILL_ACTIVE。
4、内核中线程执行过程
调用内核API RtlUserThreadStart,传入线程函数地址fun和线程执行参数param,具体过程如下:
(1)设置结构化异常处理帧;
(2)系统调用线程函数fun传入参数param;
(3)线程函数返回后,调用ExitThread并将函数返回值传给它。线程内核对象引用计数递减,线程停止执行;
(4)
5、线程的内核对象何时会被销毁
众所周知,调用CloseHandle后内核对象引用计数递减,引用计数为0时内核对象将会被销毁。但是我们常常在
创建一个线程后立即用CloseHandle,然后线程还可以继续执行,内核对象没有被销毁吗?
原因在于, 线程的内核对象创建时最初的引用计数为2,CloseHandle后只表示我们不再关心这个线程的句柄,
引用计数为1,所以内核对象并没被销毁。
除非线程终止,而且从CreateThread返回的线程句柄关闭,否则线程的内核对象不会被销毁!!!(这就告诉
我们,当我们不再需要使用一个线程句柄时,早点关闭它吧)

6、创建线程使用_beginthreadex而不是CreateThread
CreateThread创建线程后,当线程需要调用含有_tiddata结构的函数时,C\C++运行库尝试通过TlsGetValue获取
线程数据块的地址,CreateThread并不初始化_tiddata结构因此返回NULL。这时C\C++会为线程分配并初始化一
个_tiddata块,然后通过TlsSetValue将这个结构与该线程相关联。然后,该线程调用任何的C\C++运行库函数都
可以使用该结构。

那么,问题来了。由于线程没有初始化异常处理帧,当线程使用了C\C++的signal函数,整个进程都会终止。还有
就是,如果线程不是通过_endthreadex来终止运行,数据块就不能被销毁,从而导致内存泄漏。(对于一个用
CreateThread创建的线程,我们真的不会用_endthreadex来终止)
当模块链接到C\C++的DLL版本库时,这个库会在线程终止时收到一个DLL_THREAD_DETACH通知,并会释放_tiddata
内存块(如果分配了的话)。
7、不要使用_beginthread()、_endthread(),早期函数局限性比较多,不建议使用。
8、了解自己身份
8.1
GetCurrentProcess 返回伪句柄 0xffffffff
GetCurrentThread 返回伪句柄 0xfffffffe
调用CloseHandle关闭伪句柄时,返回FALSE,没有必要去关闭。
8.2伪句柄转真实句柄
使用DuplicateHandle函数,转换后的句柄使用完后需要CloseHandle关闭。

你可能感兴趣的:(Windows核心编程笔记(4)----线程)