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关闭。

时间: 2024-10-11 08:47:25

Windows核心编程笔记(4)----线程的相关文章

windows核心编程之使用线程APC回调安全退出多个等待线程

前言 程序开发中经常遇到需要这些情况:辅助线程正在等待内核对象的触发,主线程需要强制终止辅助线程.我们常常做的就是使用:TerminateThread来强制终止线程.这样做当然是不太好的,强制终止线程后系统不会销毁此线程的堆栈,长久下去内存泄露问题就会很严重了.线程最安全的退出方式当然还是让它自己返回了.本文主要介绍windows核心编程中介绍的一种安全退出线程方式:使用可等待API等待内核对象触发,添加线程APC回调. API介绍 首先得简单介绍下一个重要的windows API DWORD

Windows核心编程笔记(1)

最近工作比较闲了,一直没来得及看的核心编程最近开始看了,分享下笔记. 1.内核句柄用完不释放一定会造成内存泄漏吗? 不一定,内核句柄在进程退出时会被系统释放掉(遍历内核句柄表,只要每个句柄指向的内核对象的引用计数为0,内核就会销毁该对象,适用于所有的内核对象.资源(GDI对象在内).内存块): 2.内核对象如何关闭? 调用CloseHandle(),内核会查找该进程的句柄表,如果没找到该句柄,返回FALSE(Debug下抛出异常);如果找到,则使该句柄指向的内核对象引用计数减一,若引用计数为0,

Windows核心编程笔记(5)----线程调度,优先级

1.操作系统线程调度过程 每个线程都有一个上下文CONTEXT结构体,保存在线程的内核对象中,这个上下文中保存了线程上一次执行时CPU寄存器 的状态.每隔固定时间,Windows会查看所有当前存在的线程内核对象,其中只有一些是可调度的.Windows在可调度的 线程中选择一个,并将上次保存到线程上下文中的数据载入CPU寄存器中.(上下文切换) CPU时间片到后,Windows移出这个线程,把CPU寄存器信息保存到线程上下文中,切换到另一个线程,如此循环. 2.线程的挂起和恢复 调用CreateP

Windows核心编程笔记(7)----内核模式下的线程同步

1.内核对象同步与用户模式下同步对比 使用内核对象的唯一缺点就是性能,调用内核对象函数时,调用线程必须从用户模式切换到内核模式,这种切换是相当 耗时的. 内核对象(进程.线程.作业)要么处于触发态,要么处于未触发状态.进程内核对象在创建时总是处于未触发状态, 当进程终止时,操作系统会自动使进程内核对象变成触发状态.当进程内核对象处于触发状态后,将永远保持这种状态, 再也不能变回未触发状态. 2.等待内核对象 WaitForSingleObject等待单个内核对象,WaitForMultipleO

Windows核心编程笔记(6)----用户模式下的线程同步

1.原子锁 使用InterlockedExchangeAdd函数来实现原子增长,InterlockedExchange\InterlockedExchangePointer用来交换两个变 量的值,InterlockedCompareExchange对比数值,相等则交换(对应的InterlockedCompareExchangePointer).对应的 还有64位函数. InterlockedIncrement\InterlockedDecrement是比较老的函数,只能增加或递减1,Interl

Windows核心编程笔记(2)

6 进程实例句柄 6.1 每一个EXE或者DLL被加载到内存中后,都会被赋予一个独一无二的句柄(HINSTANCE),该句柄在WinMain函数调用时传入.获取应用程序相关信息(资源.路径)时,有的需要传入HINSTANC有的需要传入HMODULE,实际上HINSTANC与HMODULE完全是一回事,这是16位Windows系统上不同数据类型造成的. WinMain函数的第一个参数:实例句柄是如何传递进来的呢?查看crtexe.c源码,我们会看到如下代码 #ifdef WPRFLAG mainr

Windows核心编程笔记(3)--作业

/*1.如果进程已经与一个作业相关联,就无法将当前进程及其任何子进程从作业中移除,这个安全特性可以保证 /* 进程无法摆脱对它施加的限制. /*2.在调试程序时,调试器是从资源管理器启动的,程序会从调试器继承带"PCA"前缀的作业.因此,调试程序 /* 时总是显示进程已经加入了作业.使用命令行来运行程序时就不会有这个问题了. /*3.关闭一个作业对象,并不会终止作业内所有的进程.作业对象实际上只是加了一个删除标记,只有在作业中 /* 所有进程都终止后,才会自动销毁. /*4.可以向作业

python核心编程笔记4--多线程

单线程程序: import time def Sorry(): print "I'm sorry!" time.sleep(1) if __name__=='__main__': for i in range(5): Sorry() 添加线程 import threading def thread_job(): print 'this is an added Thread,number is %s' % threading.current_thread() if __name__ ==

【转】《windows核心编程》读书笔记

这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁,往往是很多单独的字句,后面的内容更为连贯. 海量细节. 第1章    错误处理 1.         GetLastError返回的是最后的错误码,即更早的错误码可能被覆盖. 2.         GetLastError可能用于描述成功的原因(CreatEvent)