进程概述及创建,终止(VC_Win32)
目录
进程概述
创建进程函数详解
进程的创建
进程的终止
进程总体执行流程
进程线程优先级
(本章节中例子都是用 VS2005 编译调试的)
进程概述
进程定义:
通常被定义为一个正在运行的程序实例,是一个程序在其自身的地址空间中的一次执行活动
程序相关描述:
- 定义: 计算机指令集合,它以文件的形式存储在磁盘上
- 与进程关系: 一个程序可以对应多个进程
- windows支持两种类型的应用程序:GUI程序(Graphical User Interface 图形用户界面)和CUI程序(Console User Interface 控制台用户界面).
进程组成:
- 内核对象: 内核对象也是用系统用来存放进程的统计信息的地方.内核对象是操作系统内部分配的一个内存块,该内存块是一种数据结构,其成员维护该对象的各种信息.由于内核对象的数据结构只能被内核访问使用,因此应用程序在内存中无法找到该数据结构,并直接改变其内容,只能通过 windows 提供的一些函数来实现内核对象的操作,进程内核对象存活时间至少能和进程本身一样长.
- 地址空间: 它包含所有可执行模块(.exe)或动态链接库模块(.dll)的代码和数据,另外,它也包含动态分配的空间
地址空间:
系统赋予每个进程独立的虚拟的地址空间,每个进程都有自己的私有的地址空间,各自的线程都可以访问各自进程地址空间中的数据,但是一般情况下各个进程的线程是无法直接访问其他进程的地址空间的数据
注意:
- 进程重来不执行任何东西,它只是线程的容器,若要使进程完成某项操作,它必须拥有一个在它环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码,也就是说,真正完成代码执行的是线程,而进程只是线程的容器,或者说进程的执行环境.当创建一个进程时,操作系统会自动创建这个进程的第一个线程,也就是主线程,也就是执行 main 函数或 WinMain 函数的线程,可以把 main 函数或 WinMain 函数看做是主线程的进入点函数,此后,主线程可以创建其他线程.所以若没有线程要执行进程地址空间包含的代码,进程就失去了继续存在下去的意义.这时候系统就会自动销毁经常及其地址空间.
- 进程在终止后不会泄露任何东西
VC 的编译链接器
集成开发环境会设置各种链接器开关,使链接器将子系统的正确类型嵌入最终可执行文件.
-
- 对应CUI程序,这个连接器的开关是 /SUBSYSTEM:CONSOLE
- 对应GUI程序来说则是 /SUBSYSTEM:WINDOWS
- 当然可以完全从项目中移除/SUBSYSTEM链接器开个,一旦这样做,链接器会自动判断应用程序设置为哪一个子系统
对应程序类型和相应的入口函数
应用程序类型 入口函数 嵌入可执行文件的启动函数
处理ANSI字符和字符串的GUI应用程序 _tWinMain(WinMain) WinMainCRTStartup
处理Unicode字符和字符串的GUI应用程序 _tWinMain(wWinMain) wWinMainCRTStartup
处理ANSI字符和字符串的GUI应用程序 _tmain(main) mainCRTStartup
处理Unicode字符和字符串的GUI应用程序 _tmain(wmain) wmainCRTStartup
实例句柄(HINSTANCE)
加载到进程地址空间的每一可执行文件(EXE)或动态链接库(DLL)文件都被赋予了一个独一无二的实例句柄(HINSTANCE).(w)WinMain 的 hInstance 参数的实际值是一个内存基址,系统将可执行文件的映像加载到进程空间的这个位置
为了知道一个可执行文件(EXE)或动态链接库(DLL)被加载到进程地址空间的什么位置,可以使用 GetModuleHandle 函数来返回一个句柄/基址
还需注意的是事实上 HMOBULE 和 HINSTANCE 完全是一回事,如果某个文档指出需要一个HMODULE 参数,我们可以传入一个 HINSTANCE
进程标识(PID)
创建一个进程内核对象时,系统会为此对象分配一个独一无二的标识符.系统中没有别的进程内核对象会有相同的 ID 编号,这同样适用于线程内核对象,创建一个线程内核对象时,此对象会被分配一个独一无二的,系统级别的ID编号,进程 ID 和线程 ID 分享同一个号码池,这意味着线程和进程不可能有相同的 ID,此外,一个对象分配到的 ID 绝不可能是0.进程和线程ID会被系统立即重用(假定在创建一个进程之后,系统初始化可一个进程对象,并将 ID 值 124 分配给他,如果在建立一个新的进程对象,系统不会将同一个 ID 编号分配给它,但是,如果一个进程对象以及释放,系统可以将 124 分配给下一个创建的进程对象).
获得进程(或线程)标识的函数
-
- GetCurrentProcessId 来获得当前进程ID.
- GetCurrentThreadId 来获得当前正在运行的线程ID.
- GetProcessId 来获得与指定句柄对应的一个进程ID.
- GetThreadId 来获得与指定句柄对应的一个线程ID.
- GetProcessIdOfThread 来获得其线程所在进程的ID.
系统确实会记住每个进程父进程的 ID,但由于 ID 会立即被重用,所以等我们获得父进程的 ID 的时候,那个 ID 可能已经是系统运行的一个完全不同的进程.要保证一个进程或线程 ID 不被重用,唯一的办法就是保证进程或线程对象不被销毁,对应子进程,除非父进程复制了自己的进程或线程对象句柄,并允许子进程继承这些句柄,否则无法确保父进程的进程ID或线程ID的有效性.
获取当前进程句柄
获取当前进程句柄 GetCurrentProcess (这个函数都返回的是一个伪句柄.它不会在主调进程的句柄表中新建句柄.而且调用这个函数,不会影响进程内核对象的使用计数器.如果调用 CloseHandle 函数关闭一个伪句柄,CloseHandle 只是简单地忽略此调用,并返回 FALSE,将伪句柄转换为真正的句柄: DuplicateHandle)
创建进程函数详解(可以参看Windows核心编程第四章有详细介绍)
函数原型
BOOL CreateProcess( LPCTSTR lpApplicationName, // name of executable module LPTSTR lpCommandLine, // command line string LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD BOOL bInheritHandles, // handle inheritance option DWORD dwCreationFlags, // creation flags LPVOID lpEnvironment, // new environment block LPCTSTR lpCurrentDirectory, // current directory name LPSTARTUPINFO lpStartupInfo, // startup information LPPROCESS_INFORMATION lpProcessInformation // process information );
参数说明:
- lpapplicationname:
指向一个NULL结尾的、用来指定可执行模块的字符串(必须指定文件的扩展名,系统不会自动假设文件有一个 .exe 的扩展名).这个字符串可以使可执行模块的绝对路径,也可以是相对路径,在后一种情况下,函数使用当前驱动器和目录建立可执行模块的路径.
这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpcommandline 参数的最前面并由空格符与后面的字符分开.这个被指定的模块可以是一个win32应用程序.
如果适当的子系统在当前计算机上可用的话,它也可以是其他类型的模块(如ms-dos 或 os/2).在windows nt中.
如果可执行模块是一个16位的应用程序,那么这个参数应该被设置为NULL并且因该在lpcommandline参数中指定可执行模块的名称.16位的应用程序是以dos虚拟机或win32上的windows(wow) 为进程的方式运行. - lpcommandline:
指向一个NULL结尾的、用来指定要运行的命令行.这个参数可以为空,那么函数将使用参数指定的字符串当作要运行的程序的命令行.
如果lpapplicationname和lpcommandline参数都不为空,那么lpapplicationname参数指定将要被运行的模块,lpcommandline参数指定将被运行的模块的命令行.新运行的进程可以使用getcommandline函数获得整个命令行.c语言程序可以使用argc和argv参数.
如果lpapplicationname参数为空,那么这个字符串中的第一个被空格分隔的要素指定可执行模块名.
如果文件名不包含扩展名,那么.exe将被假定为默认的扩展名.
如果文件名以一个点(.)结尾且没有扩展名,或文件名中包含路径,.exe将不会被加到后面.
如果文件名中不包含路径,windows将按照如下顺序寻找这个可执行文件:- 当前应用程序的目录.
- 父进程的目录.
- windows 95:windows系统目录,可以使用getsystemdirectory函数获得. windows nt:32位windows系统目录.可以使用getsystemdirectory函数获得,目录名是system32.
- 在windows nt中:16位windows系统目录.不可以使用win32函数获得这个目录,但是它会被搜索,目录名是system.
- windows目录.可以使用getwindowsdirectory函数获得这个目录.
- 列在path环境变量中的目录.
如果被创建的进程是一个以ms-dos或16位windows为基础的应用程序,lpcommandline参数应该是一个以可执行文件的文件名作为第一个要素的绝对路径,因为这样做可以使32位windows程序工作的很好,这样设置lpcommandline参数是最强壮的.
- lpprocessattributes:
指向一个security_attributes结构体,这个结构体决定是否返回的句柄可以被子进程继承.
如果lpprocessattributes参数为空(NULL),那么句柄不能被继承.在windows nt中:security_attributes结构的lpsecuritydescriptor成员指定了新进程的安全描述符.
如果参数为空,新进程使用默认的安全描述符.在windows95中:security_attributes结构的lpsecuritydescriptor成员被忽略. - lpthreadattributes:
指向一个security_attributes结构体,这个结构体决定是否返回的句柄可以被子进程继承.
如果lpthreadattributes参数为空(NULL),那么句柄不能被继承.在windows nt中,security_attributes结构的lpsecuritydescriptor成员指定了主线程的安全描述符.
如果参数为空,主线程使用默认的安全描述符.在windows95中:security_attributes结构的lpsecuritydescriptor成员被忽略. - binherithandles:
指示新进程是否从调用进程处继承了句柄.
如果参数的值为真,调用进程中的每一个可继承的打开句柄都将被子进程继承.被继承的句柄与原进程拥有完全相同的值和访问权限. - dwcreationflags:
指定附加的、用来控制优先类和进程的创建的标志.以下的创建标志可以以除下面列出的方式外的任何方式组合后指定.
进程创建标志-
- create_default_error_mode: 新的进程不继承调用进程的错误模式.createprocess函数赋予新进程当前的默认错误模式作为替代.应用程序可以调用seterrormode函数设置当前的默认错误模式.这个标志对于那些运行在没有硬件错误环境下的多线程外壳程序是十分有用的.对于createprocess函数,默认的行为是为新进程继承调用者的错误模式.设置这个标志以改变默认的处理方式.
- create_new_console: 新的进程将使用一个新的控制台,而不是继承父进程的控制台.这个标志不能与detached_process标志一起使用.
- create_new_process_group: 新进程将使一个进程树的根进程.进程树种的全部进程都是根进程的子进程.新进程树的用户标识符与这个进程的标识符是相同的,由lpprocessinformation参数返回.进程树经常使用generateconsolectrlevent函数允许发送ctrl+c或ctrl+break信号到一组控制台进程.
- create_separate_wow_vdm: (只适用于windows nt)这个标志只有当运行一个16位的windows应用程序时才是有效的.
如果被设置,新进程将会在一个私有的虚拟dos机(vdm)中运行.另外,默认情况下所有的16位windows应用程序都会在同一个共享的vdm中以线程的方式运行.单独运行一个16位程序的优点是一个应用程序的崩溃只会结束这一个vdm的运行;其他那些在不同vdm中运行的程序会继续正常的运行.同样的,在不同vdm中运行的16位windows应用程序拥有不同的输入队列,这意味着如果一个程序暂时失去响应,在独立的vdm中的应用程序能够继续获得输入. - create_shared_wow_vdm: (只适用于windows nt)这个标志只有当运行一个16位的windows应用程序时才是有效的.如果win.ini中的windows段的defaultseparatevdm选项被设置为真,这个标识使得createprocess函数越过这个选项并在共享的虚拟dos机中运行新进程.
- create_suspended: 新进程的主线程会以暂停的状态被创建,直到调用resumethread函数被调用时才运行.
- create_unicode_environment: 如果被设置,由lpenvironment参数指定的环境块使用unicode字符.如果为空,环境块使用ansi字符.
- debug_process: 如果这个标志被设置,调用进程将被当作一个调试程序,并且新进程会被当作被调试的进程.系统把被调试程序发生的所有调试事件通知给调试器.如果你使用这个标志创建进程,只有调用进程(调用createprocess函数的进程)可以调用waitfordebugevent函数.
- debug_only_this_process: 如果此标志没有被设置且调用进程正在被调试,新进程将成为调试调用进程的调试器的另一个调试对象.如果调用进程没有被调试,有关调试的行为就不会产生.
- detached_process: 对于控制台进程,新进程没有访问父进程控制台的权限.新进程可以通过allocconsole函数自己创建一个新的控制台.这个标志不可以与create_new_console标志一起使用.
进程优先级
dwcreationflags参数还用来控制新进程的优先类,优先类用来决定此进程的线程调度的优先级.如果下面的优先级类标志都没有被指定,那么默认的优先类是normal_priority_class,除非被创建的进程是idle_priority_class.在这种情况下子进程的默认优先类是idle_priority_class.可以下面的标志中的一个:-
- high_priority_class : 指示这个进程将执行时间临界的任务,所以它必须被立即运行以保证正确.这个优先级的程序优先于正常优先级或空闲优先级的程序.一个例子是windows任务列表,为了保证当用户调用时可以立刻响应,放弃了对系统负荷的考虑.确保在使用高优先级时应该足够谨慎,因为一个高优先级的cpu关联应用程序可以占用几乎全部的cpu可用时间.
- idle_priority_class : 指示这个进程的线程只有在系统空闲时才会运行并且可以被任何高优先级的任务打断.例如屏幕保护程序.空闲优先级会被子进程继承.
- normal_priority_class : 指示这个进程没有特殊的任务调度要求.
- realtime_priority_class : 指示这个进程拥有可用的最高优先级.一个拥有实时优先级的进程的线程可以打断所有其他进程线程的执行,包括正在执行重要任务的系统进程.例如,一个执行时间稍长一点的实时进程可能导致磁盘缓存不足或鼠标反映迟钝.
-
- lpenvironment:
指向一个新进程的环境块.
如果此参数为空,新进程使用调用进程的环境.一个环境块存在于一个由以NULL结尾的字符串组成的块中,这个块也是以NULL结尾的.每个字符串都是name=value的形式.因为相等标志被当作分隔符,所以它不能被环境变量当作变量名.与其使用应用程序提供的环境块,不如直接把这个参数设为空,系统驱动器上的当前目录信息不会被自动传递给新创建的进程.对于这个情况的探讨和如何处理,请参见注释一节.环境块可以包含unicode或ansi字符.
如果lpenvironment指向的环境块包含unicode字符,那么dwcreationflags字段的create_unicode_environment标志将被设置.
如果块包含ansi字符,该标志将被清空.请注意一个ansi环境块是由两个零字节结束的:一个是字符串的结尾,另一个用来结束这个快.一个unicode环境块石油四个零字节结束的:两个代表字符串结束,另两个用来结束块. - lpcurrentdirectory:
指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径.这个字符串必须是一个包含驱动器名的绝对路径.如果这个参数为空,新进程将使用与调用进程相同的驱动器和目录.这个选项是一个需要启动启动应用程序并指定它们的驱动器和工作目录的外壳程序的主要条件.
- lpstartupinfo:
指向一个用于决定新进程的主窗体如何显示的startupinfo结构体.
//用于指定新进程的主窗口如何显示,成员参见msdn typedef struct _STARTUPINFO { DWORD cb; LPTSTR lpReserved; LPTSTR lpDesktop; LPTSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;
- lpprocessinformation:
指向一个用来接收新进程的识别信息的process_information结构体.
typedef struct _PROCESS_INFORMATION { HANDLE hProcess; //进程句柄 HANDLE hThread; //主线程句柄 DWORD dwProcessId; //进程标识id DWORD dwThreadId; //主线程标识id } PROCESS_INFORMATION;
返回值:
- 如果函数执行成功,返回非零值.
- 如果函数执行失败,返回零,可以使用getlasterror函数获得错误的附加信息
说明:
createprocess函数用来运行一个新程序.winexec和loadmodule函数依旧可用,但是它们同样通过调用createprocess函数实现.
另外createprocess函数除了创建一个进程,还创建一个线程对象.这个线程将连同一个已初始化了的堆栈一起被创建,堆栈的大小由可执行文件的文件头中的描述决定.线程由文件头处开始执行.
新进程和新线程的句柄被以全局访问权限创建.对于这两个句柄中的任一个,如果没有安全描述符,那么这个句柄就可以在任何需要句柄类型作为参数的函数中被使用.当提供安全描述符时,在接下来的时候当句柄被使用时,总是会先进行访问权限的检查,如果访问权限检查拒绝访问,请求的进程将不能使用这个句柄访问这个进程.
这个进程会被分配给一个32位的进程标识符.直到进程中止这个标识符都是有效的.它可以被用来标识这个进程,或在openprocess函数中被指定以打开这个进程的句柄.进程中被初始化了的线程一样会被分配一个32位的线程标识符.这个标识符直到县城中止都是有效的且可以用来在系统中唯一标识这个线程.这些标识符在process_information结构体中返回.
当在lpapplicationname或lpcommandline参数中指定应用程序名时,应用程序名中是否包含扩展名都不会影响运行,只有一种情况例外:一个以.com为扩展名的ms-dos程序或windows程序必须包含.com扩展名.
调用进程可以通过waitforinputidle函数来等待新进程完成它的初始化并等待用户输入.这对于父进程和子进程之间的同步是极其有用的,因为createprocess函数不会等待新进程完成它的初始化工作.举例来说,在试图与新进程关联的窗口之前,进程应该先调用waitforinputidle.
首选的结束一个进程的方式是调用exitprocess函数,因为这个函数通知这个进程的所有动态链接库(dlls)程序已进入结束状态.其他的结束进程的方法不会通知关联的动态链接库.注意当一个进程调用exitprocess时,这个进程的其他县城没有机会运行其他任何代码(包括关联动态链接库的终止代码).
exitprocess, exitthread, createthread, createremotethread,当一个进程启动时(调用了createprocess的结果)是在进程中序列化进行的.在一段地址空间中,同一时间内这些事件中只有一个可以发生.这意味着下面的限制将保留:
-
- 在进程启动和dll初始化阶段,新的线程可以被创建,但是直到进程的dll初始化完成前它们都不能开始运行.
- 在dll初始化或卸下例程中进程中只能有一个线程. *直到所有的线程都完成dll初始化或卸下后,exitprocess函数才返回.
- 在进程中的所有线程都终止且进程所有的句柄和它们的线程被通过调用closehandle函数终止前,进程会留在系统中.进程和主线程的句柄都必须通过调用closehandle函数关闭.如果不再需要这些句柄,最好在创建进程后立刻关闭它们.
当进程中最后一个线程终止时,下列的事件发生:
-
- 所有由进程打开的对象都会关闭.
- 进程的终止状态(由getexitcodeprocess函数返回)从它的初始值still_active变为最后一个结束的线程的结束状态.
- 主线程的线程对象被设置为标志状态,供其他等待这个对象的线程使用.
- 进程对象被设置为标志状态,供其他等待这个对象的线程使用.
假设当前在c盘上的目录是/msvc/mfc且有一个环境变量叫做c:,它的值是c:/msvc/mfc,就像前面lpenvironment中提到过的那样,这样的系统驱动器上的目录信息在createprocess函数的lpenvironment参数不为空时不会被自动传递到新进程里.一个应用程序必须手动地把当前目录信息传递到新的进程中.为了这样做,应用程序必须直接创建环境字符串,并把它们按字母顺序排列(因为windows nt和windows 95使用一种简略的环境变量),并把它们放进lpenvironment中指定的环境块中.类似的,他们要找到环境块的开头,又要重复一次前面提到的环境块的排序.
一种获得驱动器x的当前目录变量的方法是调用getfullpathname("x:",..).这避免了一个应用程序必须去扫描环境块.如果返回的绝对路径是x:/,就不需要把这个值当作一个环境数据去传递了,因为根目录是驱动器x上的新进程的默认当前目录.
由createprocess函数返回的句柄对于进程对象具有process_all_access的访问权限.
由lpcurrentdirectory参数指定的当前目录室子进程对象的当前目录.lpcommandline参数指定的第二个项目是父进程的当前目录.
对于windows nt,当一个进程在指定了create_new_process_group的情况下被创建时,一个对于setconsolectrlhandler(NULL,true)的调用被用在新的进程上,这意味着对新进程来说ctrl+c是无效的.这使得上层的外科程序可以自己处理ctrl+c信息并有选择的把这些信号传递给子进程.ctrl+break依旧有效,并可被用来中断进程/进程树的执行.
附加说明
CreateProcess 时,系统将会创建一个进程内核对象,其初始化使用计数器为1,经常内核对象不是进程本身,而是操作系统用来管理进程的一个小型数据结构(可以把进程内核对象想象成有进程统计信息构成的一个小心的数据结构).然后系统为新进程创建一个虚拟地址空间.并将可执行文件(和所有必有的动态链接库(DLL))的代码以及数据加载到进程的地址空间中.
CreateProcess 在进程完全初始化好之前就返回 TRUE.这意味着操作系统加载程序尚未尝试定位所有必要的动态链接库(DLL).如果有一个动态链接库(DLL)找不到或者不能正确初始化.进程就会终止.因为 CreateProcess 返回 TRUE.所以父进程不会注意到子进程的任何初始化问题
进程的创建
创建过程:
- 定义两个结构体变量,用于新进程的主界面出现的样式和存放进程创建后的相关信息
- 创建进程
- 在进程创建后进行后续的清理工作
具体流程图
注意:
在创建一个进程时,系统会为该进程建立一个警察内核对象和一个线程内核对象,而该内核对象有有一个计数器,系统会为这两个对象赋予初始的计数为 1,在 CreateProcess 函数返回之前,它将打开创建的进程对象和线程对象,并将每个对象与进程和线程相关的句柄放在其最后一个参数 PROCESS_INFORMATION 结构体变量的对应成员中.当 CreateProcess 函数在其内部打开这些对象时,每个对象的使用计数就变为2,如果在父进程中不需要使用子进程的这两个句柄则可以调用 CloseHandle 函数关闭它们(关闭一个进程或线程的句柄.是不会强迫系统"杀死"此进程或线程的.关闭句柄只是告诉系统我们队进程或线程的统计数据不再感兴趣了.进程或线程会继续运行直到自行终止),系统会将子进程的进程内核对象和线程对象的计数器减1,当子进程终止运行时,系统会将这些使用计数器减 1,这时子进程的进程内核对象和线程内核对象都为 0,这两个内核对象就能够被释放了,所以在编程中,当不需要这些内核对象时,总应该调用 CloseHandle 函数关闭它们
代码样例:
定义必要结构体变量:
//指定新进程的主界面出现的样式 STARTUPINFO sui; //用于接收创建新进程后新进程的一些信息 PROCESS_INFORMATION pi;
初始化 startupinfo:
ZeroMemory(&sui,sizeof(STARTUPINFO));
创建进程:
//打开 vim 编辑器 CreateProcess("c:\\program files\\vim\\vim73\\gvim.exe",NULL,NULL,NULL, true,0,NULL,NULL,&sui,&pi);
进程创建后的清理操作:
//关闭进程句柄 CloseHandle(pi.hProcess); //关闭主线程句柄 CloseHandle(pi.hThread);
程序源码:
#include<windows.h> #include<cstdlib> using namespace std; void main() { STARTUPINFO sui; PROCESS_INFORMATION pi; ZeroMemory(&sui,sizeof(STARTUPINFO)); if(!CreateProcess("c:\\program files\\vim\\vim73\\gvim.exe",NULL,NULL,NULL, true,0,NULL,NULL,&sui,&pi)) { MessageBox(NULL,"创建子进程失败!","警告",MB_OK); return; } else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } system("pause"); }
运行结果:
进程的终止
终止进程的 4 中方式
- 主线程的入口点函数返回(推荐)
- 进程中的一个线程调用 ExitProcess 函数(避免)
- 另一个进程中的线程调用 TerminateProcess 函数(避免)
(被终止的进程得不到自己要被终止的通知,而且应用程序不能正确清理,也不能阻止它自己被强行终止(除非通过正常的安全机制)) - 进程中所有的线程都自然死亡
ExitProcess / ExitProcess 说明:
调用 TerminateProcess 或 ExitProcess 会导致进程或线程直接终止运行,C/C++ 应用程序应该避免调用这些函数,因为C/C++ 运行库也许不能正确执行清理工作
调获取进程退出代码
用 GetExitCodeProcess 来获得已经终止的一个进程退出代码
设置进程响应严重错误
每个进程都关联了一组标志,这些标志的作用是让系统知道进程如何响应严重错误(磁盘介质错误,未处理异常,文件查找错误,数据对其错误等),进程可以调用 SetErrorMode 函数来告诉系统如何处理这些错误.默认情况下子进程是继承父进程的错误模式的标志
代码样例
用 ExitProcess / ExitProcess 退出进程样例
#include <windows.h> #include <iostream> #include <cstdlib> using namespace std; class TEST{ public: ~TEST(){cout<<"this is TEST destructor!"<<endl;} }; void main() { TEST test; //通过 TerminateProcess 杀死进程 //DWORD nID; //HANDLE hPro; //获得进程 ID,后通过进程 ID 获得进程句柄 //nID = GetCurrentProcessId(); //hPro = OpenProcess(PROCESS_ALL_ACCESS,FALSE,nID); //TerminateProcess(hPro,0); //通过 ExitProcess 杀死进程 ExitProcess(0); //下面这句话永远无法执行 system("pasue"); }
参考资料
进程执行流程
执行流程(下图是看Windows核心编程的个人理解,若发现者错误若发现恳请提出)
执行流程相关解释
启动函数用途简单总结:(即上图的C/C++运行库启动代码)
- 获取执行新进程的完整命令行的一个指针
- 获取指向新进程的环境变量的一个指针
- 初始化C/C++运行库的全局变量(如果包含了stdlib.h,代码就可以访问到这些变量<Windows 核心编程的 P69 页有详细介绍>)
- 初始化C运行库内存分配函数
- 调用所有全局变量和静态C++类的构造函数
主函数返回以后,启动函数将调用 C 运行库函数 exit,向其传递返回值(nMainRetVal).exit 函数执行以下任务
- 调用 _onexit 函数调用所注册的任何一个函数
- 调用所有全局和静态 C++ 类对象的析构函数
- 清理进程使用的全部 C 运行时资源
(C/C++ 运行库为应用程序采取了一个不同的策略:不管进程中是有其他线程在运行,只要应用程序主线程从它的入口函数返回,C/C++ 运行库就会调用 ExitProcess 来终止进程.不管如果在入口函数中调用的是 ExitThread,而不是 ExitProcess 或者入口点函数直接返回,应用程序的主线程将停止执行,但是进程中还有其他线程正在运行,进程就不会终止.) - 在 DEBUG 生成中.如果设置了_CRTDBG_LEAK_CHECK_DF 标志,就通过调用 _CtrDumpMemoryLeaks 函数来生成内存泄露报告
- 调用操作系统的 ExitProcess 函数,向其传入 nMainRetVal.这会导致操作系统"杀死"我们的进程.并设置它的退出代码
一个进程终止时,系统会依次执行以下操作:(即为上图的进程清理工作)
- 终止进程中遗留的任何线程
- 释放经常分配的所有用户对象和 GDI 对象,关闭所有内核对象(如果没有其他进程打开这些内核对象的句柄,那么他们也会被销毁,不过,如果其他进程打开了它们的句柄,那么他们就不会被销毁)
- 操作系统正确释放线程所栈使用的内存
- 进程的退出代码从 STILL_ACTIVE 变为传给 ExitProcess 或 TerminateProcess 函数的代码
- 经常内核对象的状态变为以触发状态
- 进程计数器对象递减 1