[譯] 如何殺死一個進程和它的全部子進程

如何殺死一個進程和它的全部子進程

在類 Unix 系統中殺死進程比預期中更棘手。上週我在調試一個在 Semaphore 中終止做業的問題。更具體地說,這是一個有關於在做業中終止正在運行的進程的問題。如下是我從中學到的要點:html

  • 類 Unix 操做系統有着複雜的進程間關係:父子進程、進程組、會話、會話的領導進程。可是,在 Linux 與 MacOS 等操做系統中,這其中的細節並不統一。符合 POSIX 的操做系統支持使用負 PID 向進程組發送信號。
  • 使用系統調用向會話中的全部進程發送信號並不是易事。
  • 用 exec 啓動的子進程將繼承其父進程的信號配置。例如,若是父進程忽略 SIGHUP 信號,它的子進程也會忽略 SIGHUP 信號。
  • 「孤兒進程組內發生了什麼」這一問題的答案並不簡單。

殺死父進程並不會同時殺死子進程

每一個進程都有一個父進程。咱們可使用 pstreeps 工具來觀察這一點。前端

# 啓動兩個虛擬進程
$ 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

  • 用 PGID 表示的進程組
  • 用 SID 表示的會話

咱們能夠在支持做業控制的 Shell 環境中觀察到進程組,例如 bashzsh,它們爲每一個管道命令都建立了一個進程組。進程組是一個或多個進程(一般與一個做業關聯)的集合,能夠從同一個終端接收信號。每一個進程組都有一個惟一的進程組 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
複製代碼

請注意,在前半段中,tailgrep 的 PGID 是相同的。bash

會話是進程組的集合,一般由一個控制終端和一個會話領導進程組成。若是會話中有一個控制終端,它就具備單個前臺進程組,除了該控制終端,會話中的全部其餘進程組都是後臺進程組。

會話

並不是全部的 bash 進程都是會話,可是當你使用 SSH 登陸一臺遠程服務器時,你一般會獲得一個會話。當 bash 做爲會話領導進程運行時,它將 SIGHUP 信號傳播給它的子進程。SIGHUP 信號的傳播方式就是我一直以來堅信子進程會與父進程一塊兒消亡的核心緣由。

在 Unix 中會話的實現並不是一致

在上述事例中,你能夠注意到 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 忽略的信號傳播到子進程

被忽略的信號,就像是被 nohup 忽略的信號那樣,都被傳播到進程的全部子進程中。這種信號傳播方式就是我上週在 bug 排查中遇到的最終瓶頸。

個人程序是用於運行 bash 命令的代理程序,而我在該程序中驗證到的是,我已經創建了一個具備控制終端的 bash 會話。該控制終端是 bash 會話中其餘啓動進程的會話領導進程。個人進程樹以下所示:

agent -+
       +- bash (session leader) -+
                                 | - process1
                                 | - process2
複製代碼

我假設,當我使用 SIGHUP 殺死 bash 會話時,它的子進程也會同時終止。對代理的集成測試也證實了這一點。

可是,我忽略了這個代理是以 nohup 啓動的。當你使用 exec 啓動子進程時,就像咱們在代理中啓動 bash 進程同樣,它會從它的父進程繼承信號狀態。

最後一個結論使我驚訝萬分。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索