“正确”做PID文件是否仍有缺陷?

时间:2014-09-18 06:48:12

标签: linux bash

重新启动服务通常通过PID文件实现 - 即。进程ID写入某个文件,根据该数字,stop命令将终止进程(或重启之前)。

当你考虑它时(或者如果你不喜欢它,那么search)你会发现这是有问题的,因为每个PID都可以重复使用。想象一下完整的服务器重启,你在启动时调用'./your-script.sh start'(例如crontab中的@reboot)。现在你的-script.sh会杀死一个任意 PID,因为它已经在重新启动之前存储了中的PID。

我可以想象的一个解决方法是存储其他信息,以便您可以执行'ps -pid | grep'并且只有当它返回你杀死它的东西时。或者在可靠性和/或简单性方面有更好的选择吗?

#!/bin/bash

function start() {
  nohub java -jar somejar.jar >> file.log 2>&1 &
  PID=$!
  # one could even store the "ps -$PID" information but this makes the
  # killing too specific e.g. if some arguments will be added or similar
  echo "$PID somejar.jar" > $PID_FILE
}

function stop() {
  if [[ -f "$PID_FILE" ]]; then
    PID=$(cut -f1 -d' ' $PID_FILE)
    # now get the second information and grep the process list with this
    PID_INFO=$(cut -f2 -d' ' $PID_FILE)
    RES=$(ps -$PID | grep $PID_INFO)
    if [[ "x$RES" != "x" ]]; then
       kill $PID
    fi
  fi
}

2 个答案:

答案 0 :(得分:4)

PID文件的问题是多方面的,不仅限于回收和重启。

更大的问题是PID文件中的信息与过程状态之间存在不可避免的断开/竞争。

这是使用PID文件的流程:

  1. 你叉&执行一个过程。 "父母" process知道fork的PID,并保证这个PID专门为他的fork保留。
  2. 您的父母将fork的PID写入文件。
  3. 你的父母死了,还有关于PID排他性的保证。
  4. 不同的进程读取PID文件中的数字。
  5. 不同的进程检查系统上是否存在与读取的PID相同的进程。
  6. 不同的进程使用他读取的PID向进程发送信号。
  7. 在(1)中,一切都很好,花花公子。我们有一个PID,内核保证我们的数据是为我们的预期过程保留的。

    在(2)中,您正在控制PID到其他没有此保证的进程。本身不是一个问题,但这种行为很少,如果没有错误。

    在(3)中,您的父进程终止。它本身就具有PID独占性的核心保证。它可能会也可能没有对PID进行等待(2)。预期进程的真实状态将丢失,我们剩下的只是PID文件中的标识符,该标识符可能会或可能不会引用预期的进程。

    在(4)中,没有任何保证的过程会读取PID文件,任何使用此号码都只能获得任意成功。

    在(5)中,没有任何保证的过程实际上使用了标识符,这是我们实际做坏事的第一点:我们使用可能的过程标识符来查询内核。可能不会涉及预期的过程。我们得到的答案将取决于具有该PID的过程的状态,而不一定是我们预期的过程。

    在(6)中,我们犯了最大的错误:我们实际上正在执行变异操作,旨在影响我们最初启动的过程,但绝不保证这种意图。我们可能会发出任何随机系统过程的信号。

    这是为什么?什么样的东西可能会发生混乱PID?

    在(1)之后的任何地方,真正的过程可能会死亡。只要父母保留对PID的排他性的保证,内核就不会回收PID。它仍然存在并且指的是过去的过程(我们称之为" zombie"过程,你的真实过程已经死亡,但PID仍然仅为它保留)。没有其他过程可以使用此PID并发出信号,它根本无法到达任何进程。

    一旦父母发布他的保证或在(3)之后,内核就会回收死进程的PID。僵尸已经消失,PID现在可以被任何其他分叉的新进程使用。假设您正在编译某些内容,会产生数千个小进程。内核为每个内核选择随机或顺序(取决于其配置)的新PID。你已经完成了,现在你重启了apache。内核重用了死过程中释放的PID来表示重要的事情。

    但PID文件仍然包含PID。任何读取PID文件(4)的进程都假设这个数字指的是你的长期过程。

    您使用所读数字执行的任何操作(5)(6)都将以新流程为目标,而不是旧流程。

    不仅如此,您还不能在行动之前执行任何检查,因为您可以执行的任何检查与您可以执行的任何操作之间存在不可避免的竞争。如果您首先查看ps,请查看" name"你的过程是(不是这是一个非常棒的保证任何事情,请不要这样做),然后发出信号,你的ps检查和你的信号之间的时间仍然可以看到过程死亡,和/或通过新的过程回收。所有这些问题的根源是内核没有给你任何PID的独占使用保证,因为你不是它的父。

    故事的道德:不要将你孩子的PID给别人。父级和父级应该使用它,因为他是系统中唯一一个(保存内核)对其存在和身份的任何保证。

    这通常意味着让父母保持活着,而不是发出信号来终止过程,而是与父母交谈;借助于插座等。见http://smarden.org/runit/等人

答案 1 :(得分:1)

作为runit的替代方法,daemon库中的libslack命令可以在终止时自动重新生成客户端程序,而不使用PID文件。

使用带有daemon命令的named守护程序可以手动重启客户端程序;但是,这将创建一个PID文件,这可能导致race conditions已经由lhunath指出。

# daemon example without PID file
daemon --respawn --acceptable=10 --delay=10 bash -- -c 'sleep 30'

# from: man daemon
# "If started with the --respawn option, the client process 
# will be restarted after it is killed by the SIGTERM signal."
#
# (Problem would be to reliably get e.g. the bash pid in the daemon example above.)