我一直在研究使用 shell 脚本进行流程管理,并且开始意识到确保其正确完成是多么困难。
例如,您可以将程序的PID记录到一个文件中,wait
放在上面,并在程序退出后清理PID文件。
kill
例如,如果您要尝试从 init 脚本使用此守护进程,您可能会考虑执行以下操作:
do_stop() {
kill $(</var/run/program.pid)
}
这显然是行不通的。在获取 PID 和发送终止信号之间,另一个进程可能已经死亡并取代它的位置。
正确的方法似乎需要在程序的父程序中使用 IPC 向其子程序发送终止信号。这将确保进程的 PID 没有被其他进程重复使用。
我一直在尝试编写自己的尽可能正确的初始化脚本。在这种情况下,我一直在为 NRPE 写一篇文章。不幸的是,NRPE 进行了守护进程并与 init 断绝关系,这意味着我无法wait
使用它。相反,我想出了以下解决方案:
do_stop() {
echo "Stopping (sending SIGTERM to) nrpe"
pkill -u nrpe || { echo >&2 "nrpe isn't running"; exit 1; }
}
用户运行的唯一进程nrpe
是 NRPE 本身,考虑到系统在我的控制之下,我认为这是一个相对合理的解决方案。
我很好奇的是原子性pkill
(如果这个词用得正确的话)。我假设pkill
遵循以下步骤:
- 解析进程标准的参数后,在进程表中查找 PID。
- 发送
SIGTERM
(默认)到获得的PID
假设pkill -u nrpe
在步骤 1 中给出的 PID 为 42。是否有可能nrpe
在步骤 2 发生之前,该进程可能会死亡,而另一个进程可能会在其位置生成?
答案1
您怀疑存在(小!)原子性问题是正确的。
无论您使用什么方法,无论是系统标准实用程序(例如start-stop-daemon
,自己创建的 PID 文件、用于pkill
通过用户 ID、可执行二进制文件或其他方式查询和终止),在查找进程之间始终存在间隔您想要杀死进程并将该进程 ID 提供给系统kill
调用以向其发送信号。
基本上,你不应该担心它。为了遇到麻烦,两个都必须发生以下情况:
- 目标进程在您识别其进程 ID 的时间和实际终止它的时间之间死亡。
- 新创建的进程的进程 ID 必须在同一时间间隔内进行循环,以重用刚刚腾出的进程 ID。
这真的不太可能。
请注意,在您正在研究的特定情况下,您实际上确实有一种方法可以保护自己免受这种情况的影响。用户运行的唯一进程nrpe
是 NRPE 本身,因此如果您在发出命令之前切换到用户nrpe
(root
可能是从 )kill
,则可能会出现极不可能的情况尝试杀死属于其他东西的可怜的无辜进程,但您没有权限这样做,并且不会产生任何效果。