- 原文地址:Killing a process and all of its descendants
- 原文做者:igor_sarcevic
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:江五渣
- 校對者:TokenJan,portandbridge
在類 Unix 系統中殺死進程比預期中更棘手。上週我在調試一個在 Semaphore 中終止做業的問題。更具體地說,這是一個有關於在做業中終止正在運行的進程的問題。如下是我從中學到的要點:html
每一個進程都有一個父進程。咱們可使用 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)。android
我對父子進程間的關係有着錯誤的假設。我認爲若是我殺死了父進程,那麼也會殺死它的全部子進程。然而這是錯誤的。相反,子進程將會成爲孤兒進程,而 init 進程將從新成爲它們的父進程。ios
讓咱們看看經過終止 bash 進程(sleep 命令的當前父進程)來重建進程間的父子關係後發生了哪些變化。git
$ kill 29051 # 殺死 bash 進程
$ pstree -A
init(1)-+
|-sleep(28919)
`-sleep(28965)
複製代碼
於我而言,從新分配父進程的行爲很奇怪。例如,當我使用 SSH 登陸一臺服務器,啓動一個進程,而後退出時,我啓動的進程將會被終止。我錯誤地認爲這是 Linux 上的默認行爲。當我離開一個 SSH 會話時,進程的終止與進程組、會話的領導進程和控制終端都有關。github
讓咱們再次觀察上述事例中 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 表示的父子進程關係外,進程間還有其餘兩種關係:shell
咱們能夠在支持做業控制的 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
29051 19702 19701 29051 pts/2 19784 SN 1000 0:00 grep CRON
29051 19784 19784 29051 pts/2 19784 R+ 1000 0:00 ps j
29050 29051 29051 29051 pts/2 19784 Ss 1000 0:00 -bash
複製代碼
請注意,在前半段中,tail
和 grep
的 PGID 是相同的。bash
會話是進程組的集合,一般由一個控制終端和一個會話領導進程組成。若是會話中有一個控制終端,它就具備單個前臺進程組,除了該控制終端,會話中的全部其餘進程組都是後臺進程組。
並不是全部的 bash 進程都是會話,可是當你使用 SSH 登陸一臺遠程服務器時,你一般會獲得一個會話。當 bash 做爲會話領導進程運行時,它將 SIGHUP 信號傳播給它的子進程。SIGHUP 信號的傳播方式就是我一直以來堅信子進程會與父進程一塊兒消亡的核心緣由。
在上述事例中,你能夠注意到 SID (進程的會話 ID)出現的位置。它是會話中全部進程共享的 ID。
可是,你須要記住,並不是全部的 Unix 系統都遵循這一實現。單一 UNIX 規範只討論「會話領導進程」,沒有相似於進程 ID 或進程組 ID 的「會話 ID」。會話領導進程是一個具備惟一進程 ID 的單進程,所以咱們能夠討論的會話 ID 是會話領導者的進程 ID。
System V Release 4 引入了會話 ID。
實際上,這意味着你能在 Linux 上經過 ps
命令獲取會話 ID,可是在 BSD 及其變體(如 MacOS)上,會話 ID 並不存在,或始終爲零。
咱們可使用該 PGID,經過 kill 命令向整個進程組發送信號:
$ kill -SIGTERM -- -19701
複製代碼
咱們用一個負數 -19701
向進程組發送信號。若是咱們傳遞的是一個正數,這個數將被視爲進程 ID 用於終止進程。若是咱們傳遞的是一個負數,它被視爲 PGID,用於終止整個進程組。
負數來自系統調用的直接定義。
殺死會話中的全部進程與之徹底不一樣。如咱們在前一節說到的,有些系統沒有會話 ID 的概念。即便是具備會話 ID 的系統,例如 Linux,也沒有提供系統調用來終止會話中的全部進程。你須要遍歷 /proc
輸出的進程樹,收集全部的 SID,而後一一終止進程。
Pgrep 實現了遍歷、收集並經過會話 ID 殺死進程的算法。使用如下命令:
pkill -s <SID>
複製代碼
被忽略的信號,就像是被 nohup
忽略的信號那樣,都被傳播到進程的全部子進程中。這種信號傳播方式就是我上週在 bug 排查中遇到的最終瓶頸。
個人程序是用於運行 bash 命令的代理程序,而我在該程序中驗證到的是,我已經創建了一個具備控制終端的 bash 會話。該控制終端是 bash 會話中其餘啓動進程的會話領導進程。個人進程樹以下所示:
agent -+
+- bash (session leader) -+
| - process1
| - process2
複製代碼
我假設,當我使用 SIGHUP 殺死 bash 會話時,它的子進程也會同時終止。對代理的集成測試也證實了這一點。
可是,我忽略了這個代理是以 nohup
啓動的。當你使用 exec
啓動子進程時,就像咱們在代理中啓動 bash 進程同樣,它會從它的父進程繼承信號狀態。
最後一個結論使我驚訝萬分。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。