这是继上一篇博文关于进程共享内核对象的3种方法的下篇。谁让我们一直做这万恶的课程设计呢!一位MM问我为什么调用CloseHandle后,进程仍然再继续运行。正好我前些天看到了这个问题,果断助M为乐,这里也和大家分享一下。

首先我们得明白CloseHandle到底做了些什么,我们在创建进程的时候,操作系统建立了一个进程内核对象和一个线程内核对象,如果说进程是一个静态的概念,它只是一个地址空间,存放了很多地址,拥有很多资源,那么线程才是真正干活的动态概念,任何一个进程都至少拥有一个线程,否则它就什么事都没干,这样的进程系统是会把它结束掉的,也就是说我们看到的在运行的进程都至少有一个线程,但这个进程对象时可以产生子进程的,每产生一个子进程,这个进程对象的引用计数就会加一,线程内核对象也是同样的,我们可以有一个线程,也可以有多个线程,这些线程都由那个最初的线程对象来创建,每创建一个新的线程,这个线程对象的引用计数就增一。而我们在调用CloseHandle关闭线程句柄或进程句柄时,系统会先将该进程或线程对象的引用计数减一,然后判断它的引用计数时候为0了,如果为0才会结束这个进程或线程。也就是说调用CloseHandle并不一定能马上关闭进程或线程对象,而会交由操作系统来判断。

如果要终止进程的运行,有4种方法可以使用:

  • 主线程的进入点函数返回(最好使用这个方法)。

这是最正常的结束进程的方法了,由程序的进入点函数(别告诉我你不知道什么叫进入点函数,那就看我这篇博文吧:http://rangercyh.blog.51cto.com/1444712/499933)返回,它的进程才终止,这样能保证所有线程资源得到正确的清除。可以确保以下四点:

所有的析构函数都能正确执行;

操作系统能正确释放线程的堆栈;

系统将进程的退出代码设置为进入点函数的返回值;

将进程的内核对象的返回值减一。

所以大家都该使用这种方法,但确实还有其他的方法可以使用。

  • 进程中的一个线程调用ExitProcess函数(应该避免使用这种方法)。

在进程的一个线程中调用VOID ExitProcess(UNIT fuExitCode)函数来终止进程的运行,并将进程的退出代码设置为fuExitCode,这个函数调用后,后面写的代码将都不会执行,包括析构函数,所以使用这个方法来结束进程一般会造成内存泄漏,最好不要用这种方法。

  • 另一个进程中的线程调用TerminateProcess函数(应该避免使用这种方法)。

这个函数很强大,我写的一个ICMP***就利用过这个函数,详细请看我的这篇博文:http://rangercyh.blog.51cto.com/1444712/500862

函数原型是这样的:

BOOL TerminateProcess(

        HANDLE hProcess,

        UINT fuExitCode);

这个函数允许任何线程终止另一个进程或是它自己的进程。hProcess参数用于标识要终止运行的进程句柄,fuExitCode是退出代码。通过这种方法结束进程的程序不是病毒就是***,不过有些特殊的应用还是会使用这种远程结束进程的方法。这种方法结束进程比上面那个ExitProcess函数给加给力,同样更加危险,什么都没来得及善后进程就直接结束了,所以这才会被写进很多***和病毒的代码中。但是TerminateProcess函数是一个异步运行的函数,也就是说你需要等待操作系统收到你终止进程的要求时并不一定会立马终止进程,如果你需要了解进程是否真的终止了,就需要调用WaitForSingleObject函数。

  • 进程中的所有线程自行终止运行(这种情况几乎从未发生)。

这是指当进程中所有线程都终止运行了,操作系统就会认为该进程没有作用了,就会终止该进程,事实上这基本上没有可能,因为任何一个进程都至少有一个线程在运行,不过如果你调用ExitThread函数来结束线程时就有可能出现这种情况。

很多人不知道进程终止运行后操作系统还干了些什么,这里我就告诉大家当进程终止时会发生下面的事情:

  1. 进程中剩余的所有线程全部终止运行。
  2. 进程指定的所有用户和GDI对象被释放,所有内核对象被关闭,如果没有其他进程引用这些对象,它们就会被撤销。
  3. 进程的退出代码由STILL_ACTIVE改为传递给ExitProcess或TerminateProcess函数的值。
  4. 进程内核对象的状态变成收到通知的状态(这是从书上查的,目前还不是特理解,以后再说吧~~看我多乐观,读书不求甚解)。
  5. 进程内核对象的使用计数递减一。