重新启动服务通常是通过PID文件实现的--即进程ID被写入某个文件,并且基于这个数字,停止命令将杀死进程(或者在重新启动之前)。
当您考虑它时(或者如果您不喜欢这个,那么搜索),您会发现这是有问题的,因为每个PID都可以重用。设想一个完整的服务器重新启动,您在启动时调用‘./you-script.shstart’(例如,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
}发布于 2014-09-19 11:53:15
PID文件的问题是多方面的,不仅仅限于回收和重新启动。
更大的问题是,PID文件中的信息与进程的状态之间存在不可避免的断开/竞争。
这是使用PID文件的流程:
每件事都很好,很好。我们有一个PID,内核保证这个数字是为我们预定的进程保留的。
在(2)中,您将PID的控制让给其他没有此保证的进程。这本身并不是一个问题,但这种行为很少是没有过错的。
在(3)中,父进程死亡。它本身就有PID排他性的核心保证。它可能对PID做了等待(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/等人。
发布于 2016-03-23 20:44:02
作为runit的另一种选择,来自libslack库的daemon命令可以在客户端程序终止时自动重新生成-而无需使用PID文件。
使用带有daemon命令的命名守护进程,您可以手动重新启动客户机程序;但是,这将创建一个PID文件,该文件可能导致种族条件,正如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.)https://stackoverflow.com/questions/25906020
复制相似问题