PID 文件的问题是多方面的,不仅限于回收和重新启动。
更大的问题是 PID 文件中的信息和进程状态之间不可避免地存在脱节/竞争。
这是使用PID文件的流程:
- 您分叉并执行一个进程。 “父”进程知道 fork 的 PID,并保证该 PID 专门为其 fork 保留。
- 你的父进程将 fork 的 PID 写入一个文件。
- 你的父母去世了,随之而来的是 PID 独占性的保证。
- A 不同的进程读取PID文件中的数字。
- 不同的进程检查系统上是否存在与他读取的PID相同的进程。
- 不同的进程向带有他读取的PID的进程发送信号。
在(1)中,一切都很好。我们有一个 PID,并且内核保证该数字是为我们预期的进程保留的。
在 (2) 中,您将对 PID 的控制权交给没有此保证的其他进程。其本身并不是问题,但这种行为很少是没有过错的。
在(3)中,你的父进程死亡。它独自拥有 PID 独占性的内核保证。它可能会也可能不会对 PID 执行 wait(2)。预期进程的真实状态丢失了,我们剩下的只是 PID 文件中的一个标识符,它可能引用也可能不引用预期进程。
在(4)中,进程没有任何保证读取PID文件,任何使用这个数字都只能任意成功。
在(5)中,没有任何保证的进程实际上使用了某些东西的标识符,这是我们实际上做坏事的第一点:我们使用可能引用也可能不引用预期进程的进程标识符来查询内核。我们将得到的答案将是具有该 PID 的进程的状态,而不一定是我们想要的进程的状态。
在(6)中,我们犯了最严重的错误:我们实际上正在执行一个变异操作,旨在影响我们最初启动的流程,但绝不保证该意图。我们可以向任何随机系统进程发出信号。
为什么是这样?什么样的事情会扰乱 PID?
(1) 之后的任何地方,真正的进程都可能终止。只要父进程保留对 PID 独占性的保证,内核就不会回收 PID。它仍然会存在,并引用您以前的进程(我们称之为“僵尸”进程,您的真实进程死亡,但 PID 仍为其单独保留)。没有其他进程可以使用此 PID,并且发出信号它根本不会到达任何进程。
一旦父进程释放其保证或在(3)之后,内核就会回收死亡进程的PID。僵尸进程消失了,PID 现在可供任何其他分叉的新进程使用。假设你正在编译一些东西,就会产生数千个小进程。内核为每个进程选择随机或顺序(取决于其配置)新的 PID。完成后,现在重新启动 apache。内核将死进程释放的 PID 重新用于重要的事情。
但 PID 文件仍然包含 PID。任何读取 PID 文件 (4) 的进程都假设该数字指的是早已死亡的进程。
您对所读取的数字执行的任何操作 (5) (6) 都将针对新流程,而不是旧流程。
不仅如此,您还不能在执行操作之前执行任何检查,因为您可以执行的任何检查和您可以执行的任何操作之间存在不可避免的竞争。如果你首先看ps
看看你的进程的“名称”是什么(并不是说这是一个非常棒的保证,请不要这样做),然后向它发出信号,你的进程之间的时间ps
检查并且您的信号仍然可能看到进程死亡,和/或被新进程回收。所有这些问题的根源在于内核没有为您提供对 PID 的任何独占使用保证,因为您不是其父级。
这个故事的寓意是:不要将您孩子的 PID 告诉其他任何人。父级并且只有父级应该使用它,因为他是系统上唯一对其存在和身份有任何保证的人(保存内核)。
这通常意味着让父进程保持活动状态,而不是发出信号来终止进程,而是与父进程交谈;通过插座等方式。看http://smarden.org/runit/ et al.