翻译《The Old New Thing》- Quick overview of how processes exit on Windows XP

Quick overview of how processes exit on Windows XP - The Old New Thing (microsoft.com)icon-default.png?t=N7T8https://devblogs.microsoft.com/oldnewthing/20070503-00/?p=27003

Raymond Chen 2007年05月03日


Windows XP上进程退出过程的快速概览

简要

        Raymond Chen详细讨论了Windows XP中进程退出时发生的情况,包括线程的强制终止、关键部分和同步原语的处理,以及对进程堆的影响。他强调了在DLL_PROCESS_DETACH处理过程中应该避免执行任何复杂操作的重要性,并指出了即使在进程终止后,线程创建仍然被允许的原因。最后,他预示了在后续文章中将讨论的由进程终止方式引起的问题。

正文

        退出是进程生命周期中最令人害怕的时刻之一。(有点像着陆是航空旅行中最令人害怕的时刻。)

        Win32上进程退出的许多细节未被指定,因此不同的Win32实现可以遵循不同的机制。例如,Win32s、Windows 95和Windows NT都以不同的方式关闭进程。(如果Windows CE使用另一种不同的机制,我也不会惊讶。)因此,请注意,我在本系列文章中所写的是实现细节,并且可以在不发出警告的情况下随时更改。我之所以写这些,是因为这些细节可能会突出你的代码中潜藏的错误。特别是,我将讨论Windows XP上进程退出的方式。

        我得先声明,我对Windows XP上进程退出的许多步骤并不同意。本系列文章的目的不是证明进程退出的方式,而是仅仅为了向你介绍一些幕后活动,以便在你必须调查退出期间的神秘崩溃或挂起时,你能够更好地武装自己。(注意,我只是将其称为Windows XP上进程退出的方式,而不是说这是进程退出的设计。正如我的一位同事所说,“用[设计]这个词来描述这个,就像用[游泳池]这个词来指你花园里的一个水坑。”)

        当你的程序调用ExitProcess时,一大堆机制开始运作。首先,进程中的所有线程(除了调用ExitProcess的线程)都被强制终止。这可以追溯到关于进程应该如何退出的旧理论:在旧理论下,当你的进程决定是时候退出时,它应该已经清理了它所有的线程。因此,线程的终止只是一个安全网,以捕捉你可能遗漏的东西。它甚至不会先等两秒钟。

        现在,我们不是在谈论像ExitThread那样愉快的终止;那是不可能的,因为线程可能正在做某事的中间。注入一个ExitThread的调用将导致在线程没有准备好的时候发送DLL_THREAD_DETACH通知。不,这些线程是以TerminateThread的风格被终止的:直接从下面把地毯拉出来。再见了。这是一个前线程。

        好吧,这是一个相当激烈的行动,对吧。而且,在MSDN上那些可怕的警告之后,TerminateThread是一个坏函数,应该避免使用!

        等等,事情变得更糟了。

        那些被强制终止的线程可能拥有临界区、互斥体、自制的同步原语(如自旋锁),所有这些东西在剩余线程在其DLL_PROCESS_DETACH处理期间可能需要访问。好吧,互斥体有点被覆盖了;如果你尝试进入那个互斥体,你会得到神秘的WAIT_ABANDONED返回代码,它告诉你“呃哦,事情有点乱套了。”

        临界区呢?临界区没有“呃哦”返回值;EnterCriticalSection没有返回值。相反,内核只是说“对临界区开放!”我脑海中浮现出所有停车场的门都打开,让任何人进出的画面。[见更正。]

        至于自制的东西,嗯,你得自己想办法了。

        这意味着,如果你的代码在某人调用ExitProcess时恰好拥有一个临界区,临界区所保护的数据结构很可能处于不一致的状态。(毕竟,如果它是一致的,你可能会退出临界区!嗯,假设你进入临界区是因为你正在更新该结构,而不是读取它。)你的DLL_PROCESS_DETACH代码运行,进入临界区,并且它成功了,因为“所有的门都打开了”。现在你的DLL_PROCESS_DETACH代码开始表现得不稳定,因为那个数据结构中的值是不一致的。

        哦,天哪,现在你手头有一个相当难看的烂摊子。

        如果你的线程在拥有自旋锁或其他自制同步对象时被终止,你的DLL_PROCESS_DETACH很可能会无限期地挂起,耐心等待那个被终止的线程释放自旋锁(它永远不会这样做)。

        但等等,事情变得更糟了。那个临界区可能是保护进程堆的关键!如果被终止的线程之一恰好在执行HeapAllocateLocalFree等堆函数的中间,那么进程堆很可能是不一致的。如果你的DLL_PROCESS_DETACH试图分配或释放内存,它可能会因为损坏的堆而崩溃。

        总结起来:如果你因为进程终止而得到一个DLL_PROCESS_DETACH,†不要尝试任何聪明的动作。只是返回而不做任何事情,让正常的进程清理发生。内核将关闭你所有打开的内核对象句柄。当你的进程地址空间被拆除时,你分配的任何内存将自动被释放。就让进程安静地死去吧。

        请注意,如果你是一个好孩子,在调用ExitThread之前清理了进程中的所有线程,那么你已经逃脱了所有这些疯狂,因为没有什么需要清理。

        还要注意,如果你因为动态卸载而得到一个DLL_PROCESS_DETACH,那么你确实需要清理你的内核对象和分配的内存,因为进程将继续运行。但另一方面,在动态卸载的情况下,其他线程不应该在你的DLL中执行代码(因为你即将被卸载),所以——假设你正确地编写了你的DLL——你的任何关键部分都不应该有持有,你的数据结构应该是一致的。

        等等,这场灾难还没结束。尽管内核在进程中终止了除一个线程之外的所有线程,但这并不意味着阻止了新线程的创建。如果有人在他们的DLL_PROCESS_DETACH中调用CreateThread(听起来很疯狂),线程确实会被创建并开始运行!但记住,“所有的门都打开了”,所以你的临界区只是装饰,让你感觉良好。

        (在进程终止开始后创建线程的能力不是错误;这是有意的和必要的。线程注入是调试器进入进程的方式。如果不允许线程注入,你就无法调试进程终止!)

        下次,我们将看到Windows XP上进程终止的方式如何导致不止一个问题。

脚注

        †阅读本文的每个人都应该已经知道如何判断是否是这种情况。我假设你很聪明。别让我失望。

  • 28
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x0007

可不可奖励我吃只毛嘴鸡 馋😋

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值