如何停止服务产生的所有子进程

如何停止服务产生的所有子进程

我在 Ubuntu 上运行一项服务,其配置如下:

#/etc/init/my_service.conf

start on (local-filesystems and net-device-up IFACE=eth1)

respawn

exec python -u /opt/XYZ/my_prog.py 2>&1 \
                 | logger -t my_prog.py

使用 停止服务时sudo service my_service stop,python 进程不会被终止。使用 终止父进程kill也不会终止 python 进程。

如何彻底停止服务(即终止所有子进程)?理想情况下,我不想修改上述配置文件。

答案1

理想情况下,我不希望修改上面的配置文件。

太厉害了!这是正确的做法。

您需要将您的更改execscript,并停止在分叉的子进程中作为管道的一部分运行该python程序。 这个 ServerFault 答案解释了如何在嵌入式 shell 脚本中执行此操作。我只对那里给出的脚本的最后一行做了一处更改:

exec python -u /opt/XYZ/my_prog.py 2>&1

毕竟,没有充分的理由不记录标准错误。

为了应对分叉,从到expect daemon切换到systemd,采取了越来越复杂的措施,却忽略了一点,那就是正确的做法是停止守护进程分叉如果说当前的混乱中有什么好事发生,那就是 IBM 在 1995 年所写和推荐的内容多年来一直是正确的。

习惯于链式加载守护进程。有很多工具集可以使这些事情变得简单。习惯不使用 shell 脚本的想法。有很多专门为这项工作设计的工具集,可以消除 shell 的开销(这是 Ubuntu 世界中众所周知的好主意)。

例如:ServerFault 答案中的 shell 命令可以用使用Laurent Bercot 的execline工具它们被设计成能够在没有子 shell 和未链接的 FIFO 的情况下完成此操作:

#!/command/execlineb -PW
pipeline -w {
    logger -t my_prog.py
} 
fdmove -c 2 1 
python -u /opt/XYZ/my_prog.py

然后你就可以简单地

exec /foo/this_execlineb_script

我的nosh工具集,它同样是一个包含以下内容的脚本:

#!/usr/local/bin/nosh
pipe 
fdmove -c 2 1 
python -u /opt/XYZ/my_prog.py | logger -t my_prog.py

或者,也可以将这一节直接放在 Upstart 作业定义中(使用技巧来避免 shell 元字符,以便 Upstart 不会生成 shell):

exec /usr/local/bin/exec pipe --separator SPLIT fdmove -c 2 1 python -u /opt/XYZ/my_prog.py SPLIT logger -t my_prog.py

进一步阅读

答案2

在 GNU/Linux 上,通常没有办法停止服务及其产生的所有子进程,因为子进程可以更改其 PPID(父进程 ID)。唯一知道的方法是痕迹系统调用在进程创建时生成进程,并保存这些进程的列表。

Ubuntu 的 init 系统upstart不会这样做。所以你的问题的答案是不可能-- 在 Ubuntu 上 -- 无需:

  1. 修改该脚本;
  2. 准确了解该进程产生的进程 ID;
  3. 手动跟踪这些进程 ID;
  4. 将他们一个个地杀死。

这就是为什么你应该运行一个运行systemd. 如你所见,systemd跟踪所有子进程并可以用一个命令杀死它们中的每一个。这就是 GNU/Linux 系统管理应该但是因为 systemd 太新,并且它“不是我发明的”(意思是 Canonical 没有发明它),所以 Ubuntu 不想使用它。

答案3

您已跳过预计节。新贵食谱指定:

警告

此节极其重要:请仔细阅读此部分!

Upstart 将跟踪它认为属于某个作业的进程 ID。如果某个作业指定了实例节,Upstart 将跟踪该作业的每个唯一实例的 PID。

如果您未指定 expect 节,Upstart 将跟踪它在 exec 或 script 节中执行的第一个 PID 的生命周期。但是,大多数 Unix 服务将“守护进程化”,这意味着它们将创建一个新进程(使用 fork(2)),该进程是初始进程的子进程。服务通常会“双重分叉”以确保它们与初始进程没有任何关联。(请注意,没有服务最初会分叉超过两次,因为这样做没有额外的好处)。

在这种情况下,Upstart 必须有一种方法来跟踪它,因此您可以使用 expect fork,或者 expect daemon,它允许 Upstart 使用 ptrace(2) 来“计数 fork”。

为了允许 Upstart 确定作业的最终进程 ID,它需要知道该进程将调用 fork(2) 多少次。Upstart 本身无法知道这个问题的答案,因为一旦守护进程运行,它就可以分叉许多“工作”进程,而这些进程本身可以分叉任意次数。在这种情况下,不能指望 Upstart 知道哪个 PID 是“主进程”,因为它根本不知道是否会创建工作进程,更不用说创建了多少次,或者进程最初会分叉多少次。因此,有必要告诉 Upstart 哪个 PID 是“主进程”或父进程。这可以使用 expect 节来实现。

您需要这个,因为您使用的管道|正在创建子进程。您可以在书中找到高级 Linux 编程对此进行简要介绍,其中指出:

例如,这个 shell 命令会导致 shell 产生两个子进程,一个用于ls一个较少的

 $ ls | less

我不知道这是否意味着一个或两个分叉,所以我会尝试修改这条线重生在您的代码中使用

 expect fork
 respawn

或者

 expect daemon
 respawn

我不相信这能实现仅有的systemd,尽管我的标志清楚地表明我是systemd

相关内容