[译] 如何杀死一个进程和它的所有子进程
- 原文地址: Killing a process and all of its descendants
- 原文作者: igor_sarcevic
- 译文出自: 掘金翻译计划
- 本文永久链接: https:// github.com/xitu/gold-mi ner/blob/master/TODO1/killing-a-process-and-all-of-its-descendants.md
- 译者: 江五渣
- 校对者: TokenJan , portandbridge
如何杀死一个进程和它的所有子进程
在类 Unix 系统中杀死进程比预期中更棘手。上周我在调试一个在 Semaphore 中终止作业的问题。更具体地说,这是一个有关于在作业中终止正在运行的进程的问题。以下是我从中学到的要点:
- 类 Unix 操作系统有着复杂的进程间关系:父子进程、进程组、会话、会话的领导进程。但是,在 Linux 与 MacOS 等操作系统中,这其中的细节并不统一。符合 POSIX 的操作系统支持使用负 PID 向进程组发送信号。
- 使用系统调用向会话中的所有进程发送信号并非易事。
- 用 exec 启动的子进程将继承其父进程的信号配置。例如,如果父进程忽略 SIGHUP 信号,它的子进程也会忽略 SIGHUP 信号。
- “孤儿进程组内发生了什么”这一问题的答案并不简单。
杀死父进程并不会同时杀死子进程
每个进程都有一个父进程。我们可以使用
pstree
或
ps
工具来观察这一点。
# 启动两个虚拟进程
$ sleep 100 &
$ sleep 101 &
$ pstree -p
init(1)-+
|-bash(29051)-+-pstree(29251)
|-sleep(28919)
`-sleep(28964)
$ ps j -A
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:03 /sbin/init
29051 1470 1470 29051 pts/2 2386 SN 1000 0:00 sleep 100
29051 1538 1538 29051 pts/2 2386 SN 1000 0:00 sleep 101
29051 2386 2386 29051 pts/2 2386 R+ 1000 0:00 ps j -A
1 29051 29051 29051 pts/2 2386 Ss 1000 0:00 -bash
调用
ps
命令可以显示 PID(进程 ID) 和 PPID(父进程 ID)。
我对父子进程间的关系有着错误的假设。我认为如果我杀死了父进程,那么也会杀死它的所有子进程。然而这是错误的。相反,子进程将会成为孤儿进程,而 init 进程将重新成为它们的父进程。
让我们看看通过终止 bash 进程(sleep 命令的当前父进程)来重建进程间的父子关系后发生了哪些变化。
$ kill 29051 # 杀死 bash 进程
$ pstree -A
init(1)-+
|-sleep(28919)
`-sleep(28965)
于我而言,重新分配父进程的行为很奇怪。例如,当我使用 SSH 登录一台服务器,启动一个进程,然后退出时,我启动的进程将会被终止。我错误地认为这是 Linux 上的默认行为。当我离开一个 SSH 会话时,进程的终止与进程组、会话的领导进程和控制终端都有关。
什么是进程组和会话领导进程?
让我们再次观察上述事例中
ps j
命令的输出。
$ ps j -A
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:03 /sbin/init
29051 1470 1470 29051 pts/2 2386 SN 1000 0:00 sleep 100
29051 1538 1538 29051 pts/2 2386 SN 1000 0:00 sleep 101
29051 2386 2386 29051 pts/2 2386 R+ 1000 0:00 ps j -A
1 29051 29051 29051 pts/2 2386 Ss 1000 0:00 -bash
除了使用 PPID 和 PID 表示的父子进程关系外,进程间还有其他两种关系:
- 用 PGID 表示的进程组
- 用 SID 表示的会话
我们可以在支持作业控制的 Shell 环境中观察到进程组,例如
bash
和
zsh
,它们为每个管道命令都创建了一个进程组。进程组是一个或多个进程(通常与一个作业关联)的集合,可以从同一个终端接收信号。每个进程组都有一个唯一的进程组 ID。
# 启动一个由 tail 和 grep 命令组成的进程组
$ tail -f /var/log/syslog | grep "CRON" &
$ ps j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29051 19701 19701 29051 pts/2 19784 SN 1000 0:00 tail -f /var/log/syslog