1.终止进程
进程可以通过以下4种方法终止:
- 主线程的入口点函数返回(强烈推荐的方法)
- 进程中的一个线程调用ExitProcess函数(要避免这种方式)
- 另一个进程中的线程调用TerminateProcess函数(要避免这种方式)
- 进程中的所有线程都"自然死亡"(这种情况几乎从来不会发生)
下面将讨论所有这四种方法,并描述进程终止时实际发生的情况。
1.1主线程的入口点函数返回
要设计一个应用程序时,应该保证在主线程的入口点函数返回之后,这个应用程序的进程才终止,这样才能保证主线程的所有资源都被正确清理。
让主线程的入口点函数返回,可以保证一下操作会被执行。
- 该线程创建的任何C++对象都将由这些对象的析构函数正确销毁。
- 操作系统将正确释放线程栈使用的内存。
- 系统将进程的推出代码(在进程内核对象中维护)设为入口点函数的返回值。
- 系统递减进程内核对象的使用计数。
1.2调用ExitProcess函数
进程会在该进程中的一个线程调用ExitProcess函数时终止:
void ExitProcess(UINT fuExitCode);
该函数将终止进程,并将进程的推出代码设置为fuExitCode。ExitProcess 不会返回值,因为进程已经终止了。如ExitProcess 之后还有别的代码,那这些代码永远不会执行。
当主线程的入口点函数(WinMain,wWinMian,main或者wmain)返回时,会返回到C/C++运行库启动代码,后者将清理进程使用的全部C运行时资源。释放了C运行时资源之后,C运行时启动代码将显式地调用ExitProcess,并将入口点函数返回值传给它。这便解释了为什么只需要从主线程的入口点函数返回,就会终止整个进程。注意,进程中运行的其他任何线程都会随进程一起终止。
Windows Platform SDK文档指出,一个进程在其所有线程终止之后才会终止。从操作系统的角度出发,这种说法是正确的。不过,C/C++运行库为应用程序采取了一个不同的策略:不管进程中是否还有其他的线程在运行,只要应用程序的主线程从它的入口点函数返回,C/C++运行库就会调用ExitProcess来终止进程。不过,如果入口点函数调用的是ExitThread,而不是调用ExitProcess或者入口点函数直接返回,但只要进程中还有其他线程正在运行,进程就不会终止。
注意:调用ExitProcess或者ExitThread会导致进程或者线程直接终止运行——再也不会返回当前函数调用。就操作系统而言,这样做是没有问题的,进程或线程的所有操作系统资源都会被正确清理。不过,C/C++应用程序应该避免调用这些函数,因为C/C++运行库也许不能正确执行清理工作。
1.3TerminateProcess函数
调用TerminateProcess也可以终止一个进程,如下所示:
BOOL TerminateProcess(HANDLE hProcess, UINT fuExitCode);
此函数和ExitProcess函数有一个明显的区别:任何线程都可以调用TerminateProcess来终止另一个进程或者它自己的进程。hProcess参数指定了要终止的进程的句柄。进程终止时,其退出代码的值就是传给fuExitCode参数的值。
只有在无法通过其他方法来强制进程退出时,才应使用TerminateProcess。被终止的进程得不到要被终止的通知——应用程序不能正确清理,也不能阻止它自己被强行终止(除非通过正常的安全机制)。例如,在这种情况下,进程无法将它的内存中的任何信息刷到磁盘上。
虽然进程没有机会执行自己的清理工作,但操作系统会在进程终止之后彻底进行清理,确保不会泄漏任何操作系统资源。这意味这进程使用的所有内存都会被释放,所有打开的文件都会被关闭,所有内核对象的使用计数都将被递减,所有的用户对象和GDI对象都会被销毁。
一旦进程终止(不管是如何终止的),系统会保证不留它的任何部分。绝对没有任何办法知道那个进程是否运行过。进程在终止后绝对不会泄漏任何东西。
1.4当进程终止时
一个进程终止时,系统会依次执行一下操作。
- 终止进程中遗留的任何线程
- 释放进程分配的所有用户对象和GDI对象,关闭所有的内核对象(如果没有其他进程打开这些内核对象的句柄,那么他们也会被销毁,不过如果其他进程打开了它们的句柄,那么他们就不会被销毁)
- 进程的退出代码从STILL_ACTIVE变为传给ExitProcess或者TerminateProcess函数的代码
- 进程内核对象的状态变成已触发状态,这就是为什么系统中的其他线程可以挂起他们自己,直到另一个进程终止运行
- 进程内核对象的使用计数减1
注意:进程内核对象的生命周期至少能像进程本身一样长。但是,进程内核对象存活的时间也许比进程本身存活的时间更久。一个进程终止时,系统会自动递减其内核对象的使用计数。如果计数减至0,表明没有其他进程打开了这个对象的句柄,所以在进程被销毁时对象也会被销毁。
但是,当一个进程终止的时候如果系统中还有另一个进程打开了这个进程内核对象的句柄,那么进程内核对象的使用计数就不会变成0。当父进程忘记关闭子进程的句柄时,往往会发生这种情况。这是windows的一个特性或者功能,而不是bug。