是否可以強制忽略訊號的程式在 ctrl-C 上退出?

是否可以強制忽略訊號的程式在 ctrl-C 上退出?

我有一個忽略 SIGINT 的程序,但我想在前台運行。我想找到一種方法來強制它在 Ctrl-C 上關閉。有沒有什麼方法可以編寫一個包裝器(您可以呼叫./wrapper.sh my_program)來強制行為不當的程式退出,可能是透過偵測被忽略的 SIGINT 並產生 SIGKILL ?

這個答案與我正在尋找的完全相反——我想強制一個忽略信號的程式在 SIGINT 上退出。

答案1

讓我們建造一個將 SIGINT「轉換」為 SIGKILL 的包裝器。

我們需要一個運行並在+my_program上取得 SIGINT的進程。當它收到 SIGINT 時,它應該發送 SIGKILL 到。請注意,我們不需要實際接收由+引起的 SIGINT ;由於額外的進程而導致 SIGKILL 就足夠了。Ctrlcmy_programmy_programCtrlc

相關事實:

  • 沿著另一個進程運行一個進程的標準方法是在後台非同步運行其中一個進程(即終止&)。
  • 當您點擊Ctrl+時c,終端模擬器會將 SIGINT 傳送到前台進程組中的進程。
  • 一些(簡單的)shell 在同一進程組中運行所有內容。如果他們被告知在後台運行命令,那麼他們會將其標準輸入重定向到/dev/null或等效文件,以防止該命令竊取輸入。
  • 其他 shell 可以在單獨的進程組中執行每個命令。如果他們被告知在後台運行命令,那麼他們將保持其標準輸入不變;後台的命令仍然無法竊取控制終端的輸入,因為SIGTTIN。這允許 shell 透過通知終端新的前台進程組來將作業從背景移動到前台。此機制稱為作業控制,可以停用。在腳本中它預設是禁用的。
  • 無論哪種方式,後台進程都無法從終端機讀取資料。這意味著我們不應該my_program在後台運行,以防 stdin 是終端並且my_program需要從中讀取。
  • 不幸的是,在某些 shell 中,我們也不應該在背景執行其他進程。即使停用作業控制,某些 shell 也會使用單獨的進程組。不在前台進程組中的其他進程將不會收到Ctrl+上的 SIGINT c,因此它無法將其「轉換」為 SIGKILL。
  • 在我的 Debian 10 中,posh有一個 shell 在同一進程組中執行所有內容。

這導致以下包裝器:

#!/usr/bin/env posh

( trap 'kill -s KILL 0' INT
  while kill -s 0 "$$" 2>/dev/null; do sleep 1; done
) &
exec "$@"

exec "$@"運行my_program(或您指定的任何內容),可能帶有參數。感謝exec my_program將更換包裝紙。它不僅能夠從標準輸入(可能是終端)讀取資料;它的 PID 將是被替換的包裝器之一。從這個意義上說,包裝紙是透明的。此外,它還有一個很好的功能:PID在啟動my_program之前就已知my_program,我們可以輕鬆地在其他進程(在本例中是在後台的子 shell 中)中使用它來檢測my_program實際終止的時間。

trapSIGINT“轉換”為 SIGKILL。注意kill -s KILL 0將 SIGKILL 傳送到整個進程組,包括子進程(my_program如果它們在該組中)(如果您只想殺死my_program,則kill -s KILL "$$"改為使用)。儘管如此,僅kill -s 0 "$$"測試其存在性my_program

有一個替代方案不需要 shell 來運行同一進程組中的所有內容。訣竅是:管道中的進程應該在一個進程組中運行;透過巧妙的重定向,您可以建立一條管道,其中各部分之間不相互連接。

#!/bin/sh -

exec 9>&1
( "$@"; kill -s TERM 0 ) >&9 9>&- | (
  trap 'kill -s KILL 0' INT
  while :; do sleep 1; done
)

在此變體中my_program不會替換包裝器。第二個kill是將SIGINT「轉換」為SIGKILL。第一個kill是終止循環,以防my_program在循環本來可以生存的情況下退出。

有一些場景可能無法按您的預期工作(使用任何包裝器)。他們之中:

  • 如果my_program分叉並退出,則將真正的工作留給子進程。所討論的麻煩程序很可能不會像這樣運行,因為您嘗試Ctrl+c它。但一般來說可能是這樣。
  • 如果my_program在另一個進程組中產生子進程,並且您希望將它們與其父進程一起殺死。
  • 如果my_program配置終端不在Ctrl+上發送 SIGINT c。如果您懷疑發生這種情況,請透過在和stty -F /dev/tty intr ^C之間放置來改進包裝器。在這種情況下,程式甚至可能不會忽略 SIGINT,它可能只是確保它不會從終端獲取它。所以也許 SIGKILL 有點矯枉過正;也許恢復終端的此功能就足夠了,+將開始工作。sleep 1doneCtrlc

來自評論:

忽略 SIGINT 的程序通常有一個很好的理由。

真的。嘗試使用 SIGTERM 或 SIGHUP 而不是 SIGKILL。也許有問題的程序不會忽略至少其中之一,並通過適當的清理優雅地退出。

相關內容