【原文:http://blog.chinaunix.net/uid-20476365-id-1942505.html】
1.进程终止的方式:
(1) 主线程的入口函数返回(推荐方式);
(2) 进程主动退出:一个线程调用ExitProcess;
(3) 进程被动终止:另一个进程调用了TerminateProcess;例如通过任务管理器中结束进程。
(4) 进程中的所有线程都终止了。
2. 线程:
(1) 创建于一个进程的上下文中,在进程的地址空间中运行;
(2) 线程是动态的,一个进程至少要有一个线程;
(3) 进程中的所有线程共享进程的地址空间;
2.1 线程的组成部分:
(1) 一个核心态的对象,操作系统用来保存线程的信息;
(2) 线程堆栈,用来存放线程运行时的函数参数和局部变量等。
2.2 线程的运行:
(1) 当一个进程初始化时,系统会创建这个进程的主线程;
(2) 这个线程最初执行C/C++运行时间库的启动代码,这些启动代码会调用程序的入口函数(main,wmain,WinMain,wWinMain);
(3) GUI进程有一个主UI线程,处理消息循环和用户交互。如果这个线程被挂起或忙于处理任务,应用程序会失去相应;
(4) 工作线程没有界面,在后台工作,例如分析数据、数据库操作等。
2.3 线程的终止:
(1) 线程函数返回(推荐方式);
(2) 线程调用ExitThread;
(3) 其他线程(可以是其他进程中的线程)调用TerminateThread;
(4) 该线程所在的进程被终止。
2.4 线程调度:下面的事件会触发操作系统的线程调度(线程调度时,OS会执行上下文切换Context Switch,把执行权从当前线程转移到下一个线程):
(1) 一个线程进入ready状态,例如这个线程被创建或这个线程从等待状态释放;
(2) 一个线程离开运行状态,例如这个线程的时间片结束了,或终止,或放弃执行(sleep),或进入等待状态;
(3) 一个线程的优先级改变;
(4) 一个线程的processor affinity改变。
2.5 Context Switch:
(1) 保存当前线程的上下文(Context);
(2) 把当前线程放在同一优先级的线程队列的尾部;
(3) 找到最高优先级的ready线程;
(4) 把这个线程从它所在的线程中移出,装载它的上下文(Context),开始执行这个线程。
2.6 线程上下文的内容:
(1) Instruction pointer,记录线程在被挂起前执行到的位置;
(2) 用户和核心态堆栈指针;
(3) 指向这个线程所在的地址空间的指针(Page table diroctory)(e.g:当从进程A中的线程B切换到进程C中的线程D执行时,需要保存该值)。
2.7 线程的优先级:
Windows使用32个优先级别,从0到31,数值越大优先级越高。
(1) 进程priority class:Real-time,High,Above Normal,Normal,Below Normal,Idle;
(2) 线程relative thread priority:Time-critical,Highest,Above normal,Highest,Above normal,Normal,Below normal,Lowest,Idle;实际运行时,线程的优先级是由所属进程的prioriy class(参考级别)和relative thread priority(偏移级别)两者结合起来来计算的,
(3) 一个系统级别(0),保留给zero page thread;
2.8 线程同步(下面的情况需要做线程同步):
(1) 多个线程访问共享资源,在一个时间只有一个线程可以访问共享资源;否则,可能出现的问题是应用程序崩溃;多CPU的情况下尤其要注意这一点;
(2) 一个线程需要通知另一个线程一项工作已经完成。
3 进程的虚拟地址空间:
在32位第系统上,进程的虚拟地址空间是4G;缺省情况下,用户态和核心态虚拟地址空间各占2GB;各个进程的用户态地址空间是相互独立的,如下图所示:
3.1 调节用户态空间大小:
(1) Boot.ini的/3GB选项可以用来调节用户态虚拟地址空间的大小,把用户态的地址空间增加到3GB(Win2k,WinXP,Win2k3);
(2) Boot.ini的/USERVA可以指定用户态地址空间为2GB和3GB之间的一个值(WinXP,Win2K3);
(3) 应用程序的二进制文件头上要有/LARGEADDRESSAWARE标志,才能访问高于2GB的地址空间,否则将不能访问。
3.2 用户态虚拟地址空间的状态:
(1) Free:这段地址自由,可被分配;
(2) Reserving:这段地址被预留,不会在分配做他用,没有影射物理内存,不能访问,只有被Commit之后再可以使用;
(3) Commintted:被影射到物理内存。
3.3 进程使用的内存:
(1) Private Bytes:进程的虚拟地址空间中已分配的内存(包括物理内存和虚拟内存Paging File中使用的内存的综合),不包括核其他进程共享的内存(e.g:二进制文件dll和exe文件的代码);
(2) Virtual Bytes:进程所使用的虚拟地址空间的大小,包括核其他进程共享的内存,例如共享动态链接库;
(3) Working Set:进程所使用的物理内存的大小,包括核其他进程共享的内存。
3.4 内存映射文件:
(1) 进程保留一段虚拟地址空间,把一个文件中的一部分映射到这段地址空间;可以用这种方式来处理大文件(size>4GB),使用内存映射文件来处理大文件,每次只把一部分文件内容映射到进程的地址空间;
(2) 操作系统使用内存映射文件来装载.exe和.dll文件;如果多个进程使用同一个dll文件,物理内存里只有一份拷贝;多个进程之间共享内存:多个进程映射到同一个文件的用一部分。
3.5 物理内存和虚拟内存(paging file):
(1) paging file用来保存进程正在使用的修改过的内存,例如当系统物理内存不足时或者这部分内存在很长一段时间内没有被使用;
(2) 当这部分内容被访问时,这些内容再被从paging file转移到物理内存(缺页处理过程)。
3.6 缺页处理(page fault):
(1) 被访问的页面不在物理内存内,但是在硬盘文件里或paging file里或内存映射文件里;
(2) 操作系统分配物理内存,把所需的访问的内容从硬盘文件或paging file或内存映射文件里读到物理内存里;
(3) 这个过程对应用程序时透明的。
3.7 内存访问流程图:
3.8 线程堆栈(stack):
堆栈是线程运行时用来保存函数参数、局部变量、函数返回地址等数据的一块内存;系统给每个线程reserve一块用户态线程堆栈,并commit其中的一部分内存。缺省情况下,reserve 1MB堆栈,并commit其中的2个page(2*4KB),如下图所示(丛高地址向低地址方向写):
3.9 堆(Heap):
分配小块的内存;进程的缺省堆(1MB);创建更多的堆(HeapCreate、HeapAlloc、HeapReAlloc);
3.10 进程使用内存的类型总结:
(1) 虚拟文件:用于存储大块的数组和数据结构;
(2) 内存映射文件:用于映射可执行文件、大文件或多进程之间共享内存;
(3) 堆:主要用于大量的小块内存,进程申请内存一般是从堆中分配,进程使用的内存大部分来自堆,堆实际上是操作系统的堆管理器从虚拟内存中分配的;
(4) 堆栈:堆栈保存线程运行时的一些变量、寄存器信息等,堆栈的空间有限,如果一个线程的堆栈用完而且不能再增长,应用程序会崩溃。
4 动态链接库(DLL):
(1) DLL里的内容:函数、数据结构、资源等;
(2) DLL不能直接运行,必须被装载到一个进程中才能运行;
(3) DLL的image被映射到进程的地址空间,然后进程中的所有线程可以调用DLL中的函数。
4.1 DLL的入口函数:
一个DLL可以指定一个入口函数(DllMain),系统在特定情况下会调用DLL的入口函数;下面的情况下系统调用DLL的入口函数:
(1) 进程装载DLL(DLL_PROCESS_ATTACH);
(2) 进程卸载DLL(DLL_PROCESS_DETACH);
(3) DLL被装载之后创建一个新线程(DLL_THREAD_ATTACH);
(4) DLL被装在之后一个线程终止(DLL_THREAD_DETACH);
4.2 Load-time dynamic linking:
(1) 编译器把可执行文件所需的DLL的名字放在可执行文件头的Import部分中;
(2) 可执行文件开始运行时操作系统检测可执行文件头的Import部分,然后定位和装载所需的Dll。
(3) 如果操作系统不能找到合适的DLL,会给出一个错误消息对话框;
4.3 Run-time dymnamic linking:
(1) 应用程序运行之后调用LoadLibrary或LoadLibraryEx装载DLL,操作系统定位并装载DLL;
(2) 如果不能找到DLL或DLL入口函数,则返回false,LoadLibrary或LoadLibrary失败;
(3) 用GetProcAddress获得DLL的输出函数地址,并使用这些函数。
4.4 DLL的搜索顺序:
注册表键:HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode决定DLL的搜索顺序(Windows Vista、Win2K3、WinXP+SP2中,该键值为1;在WinXP和Win2K+SP4 中,该键值为0):
(1) SafeDllSearchMode=1
->可执行文件所在的目录;
->系统目录,%SystemRoot%system32;
->16位系统目录;
->Windows目录,%SystemRoot%
->进程的当前目录;
->环境变量Path中的目录;
(2) SafeDllSearchMode=0或Legacy Search Order:
->可执行文件所在的目录;
->进程的当前目录;
->系统目录,%SystemRoot%system32;
->16位系统目录;
->Windows目录,%SystemRoot%
->环境变量Path中的目录;
4.5 Known DLLs
Known DLLS是保证用LoadLibrary装载系统DLL时只从特定的系统目录装载,防止装载错误的系统DLL。Known DLLs列表保存在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs键值中;操作系统在装载一个DLL时会检查KnownDLLs注册表下是否有一样的注册表键名,如果是,则装载%SystemRoot%\System32目录下对应注册表键值的DLL。
4.6 DLL Hook:
(1) 应用程序注册hook过程,用于监视一些特定的事件,例如键盘按键、鼠标移动、窗口激活、最小化、最大化、对话框里的输入等;
(2) Hook DLL被装载到其他进程里,一旦遇到上述事件,Hook DLL会执行相应的Hook过程;
(3) 一些恶意软件会用这种方法窃取用户输入的用户名和密码。
5 异常处理:
应用程序运行时可能遇到异常,如果应用程序没有处理这些异常,应用程序可能崩溃;常见的异常有访问非法内存地址、线程堆栈溢出、遇到断点(程序里写有断点)、执行非法指令(线程堆栈被破坏)等。
5.1 未处理异常:
如果线程遇到一个未处理异常,Windows unhandled exception filter会被调用;读取HKLM\Software\Microsoft\WindowsNT\CurrentVersion\AeDebug下的注册表键值Auto和Debugger(如果Auto为1,则自动运行Debugger指定的调试程序;缺省的Debugger是drwtsn32,生成dump文件);
如果发生未处理异常,以及Auto是0和Debugger包含"Drwtsn32",系统会检查HKLM\Software\Microsoft\PCHealth\ErrorReporing决定是否显示报告错误对话框。