如何殺死程序並確保 PID 未被重用
例如,假設您有一個類似於以下內容的 shell 腳本:
longrunningthing & p=$! echo Killing longrunningthing on PID $p in 24 hours sleep 86400 echo Time up! kill $p
應該做的伎倆,不是嗎?除了程序可能已經提前終止並且它的 PID 可能已經被回收,這意味著一些無辜的工作會在它的信號隊列中得到一個炸彈。在實踐中,這可能確實很重要,但它仍然讓我擔心。破解 longrunningthing 自己死掉,或者在 FS 上保留/刪除它的 PID 就可以了,但我在想這裡的一般情況。
最好是使用該
timeout
命令,如果你有它的意思是:timeout 86400 cmd
目前的(8.23)GNU 實現至少
alarm()
在等待子程序時通過使用或等效的方式工作。它似乎並沒有防止在返回和退出SIGALRM
之間傳遞(有效地取消了那個警報)。在那個小視窗期間,甚至可能在 stderr 上寫入消息(例如,如果孩子轉儲了一個核心),這將進一步擴大該競爭視窗(例如,如果 stderr 是一個完整的管道,則無限期地擴大)。waitpid()``timeout``timeout
我個人可以忍受這個限制(可能會在未來的版本中修復)。
timeout
還將特別注意報告正確的退出狀態,處理其他極端情況(如 SIGALRM 在啟動時被阻止/忽略,處理其他信號……)比您可能手動完成的要好。作為一個近似值,你可以這樣寫
perl
:perl -MPOSIX -e ' $p = fork(); die "fork: $!\n" unless defined($p); if ($p) { $SIG{ALRM} = sub { kill "TERM", $p; exit 124; }; alarm(86400); wait; exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?)) } else {exec @ARGV}' cmd
http://devel.ringlet.net/sysutils/timelimit/上有一個
timelimit
命令(比 GNU 早了幾個月)。timeout
timelimit -t 86400 cmd
那個使用
alarm()
類似 - 的機制,但在SIGCHLD
(忽略停止的孩子)上安裝了一個處理程序來檢測孩子的死亡。它還在執行之前取消警報waitpid()
(如果它處於未決狀態,則不會取消傳遞SIGALRM
,但它的編寫方式,我看不出這是一個問題)並在呼叫之前waitpid()
終止(所以不能終止重用的 pid )。netpipes也有一個
timelimit
命令。那個比所有其他的早幾十年,採用另一種方法,但對於停止的命令不能正常工作,並1
在超時時返回退出狀態。作為對您問題的更直接回答,您可以執行以下操作:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then kill "$p" fi
也就是說,檢查該程序是否仍然是我們的子程序。同樣,有一個小的競爭視窗(在
ps
檢索該程序的狀態和kill
殺死它之間),在此期間程序可能會死亡並且它的 pid 被另一個程序重用。使用一些 shell (
zsh
,bash
,mksh
),您可以傳遞作業規範而不是 pid。cmd & sleep 86400 kill % wait "$!" # to retrieve the exit status
這僅在您僅生成一個後台作業時才有效(否則並不總是能夠可靠地獲得正確的作業規範)。
如果這是一個問題,只需啟動一個新的 shell 實例:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
這是有效的,因為外殼會在孩子死亡時從作業表中刪除作業。在這裡,不應該有任何競爭視窗,因為在 shell 呼叫
kill()
時,SIGCHLD 信號尚未處理並且 pid 無法重用(因為它沒有被等待),或者它已被處理並且作業已從程序表中刪除(並kill
會報告錯誤)。bash
’kill
至少在 SIGCHLD 訪問其作業表以擴展 之前阻塞 SIGCHLD%
並在 . 之後解除阻塞kill()
。
sleep
即使在cmd
死後也可以避免該程序掛起的另一種選擇是使用bash
或ksh93
使用管道read -t
而不是sleep
:{ { cmd 4>&1 >&3 3>&- & printf '%d\n.' "$!" } | { read p read -t 86400 || kill "$p" } } 3>&1
那個仍然有競爭條件,你失去了命令的退出狀態。它還假設
cmd
不關閉其 fd 4。您可以嘗試實施無種族解決方案,
perl
例如:perl -MPOSIX -e ' $p = fork(); die "fork: $!\n" unless defined($p); if ($p) { $SIG{CHLD} = sub { $ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new; sigprocmask(SIG_BLOCK, $ss, $oss); waitpid($p,WNOHANG); exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?)) unless $? == -1; sigprocmask(SIG_UNBLOCK, $oss); }; $SIG{ALRM} = sub { kill "TERM", $p; exit 124; }; alarm(86400); pause while 1; } else {exec @ARGV}' cmd args...
(儘管需要改進以處理其他類型的極端情況)。
另一種無競爭的方法可能是使用程序組:
set -m ((sleep 86400; kill 0) & exec cmd)
但是請注意,如果涉及到終端設備的 I/O,則使用程序組可能會產生副作用。它有一個額外的好處,雖然可以殺死所有其他由
cmd
.