trap 和 SIGINT 的奇怪問題

trap 和 SIGINT 的奇怪問題

請解釋一下:

#!/bin/bash
# This is scripta.sh
./scriptb.sh &
pid=$!
echo $pid started
sleep 3
while true
do
    kill -SIGINT $pid
    echo scripta.sh $$
    sleep 3
done 

-

#!/bin/bash
# This is scriptb.sh
trap "echo Ouch;" SIGINT

while true
do
 echo scriptb.sh $$
 sleep 1
done

當我運行./scripta.sh陷阱時不打印。如果我從 SIGINT 切換到任何其他訊號(我嘗試了 SIGTERM、SIGUSR1),陷阱將按預期列印「Ouch」。怎麼會發生這種事呢?

答案1

如果我從 SIGINT 切換到任何其他訊號(我嘗試過 SIGTERM、SIGUSR1),陷阱會按預期列印「Ouch」。

顯然你沒有嘗試SIGQUIT;您可能會發現它的行為與 SIGINT 相同。

問題在於工作控制。

在 Unix 的早期,每當 shell 將一個進程或管道放入後台時,它都會將這些進程設置為忽略 SIGINT 和 SIGQUIT,這樣,如果用戶隨後鍵入Ctrl+ C(中斷)或Ctrl+ \(退出),它們就不會被終止。當作業控制出現時,它也帶來了進程組,因此現在 shell 需要做的就是將後台作業放入一個新的進程組中;只要不是目前的終端進程組,進程就不會看到來自鍵盤的訊號(Ctrl+ CCtrl+\Ctrl+ Z(SIGTSTP))。 shell 可以讓後台程序保持預設的訊號處理。事實上,當進程被帶入前台時,它可能必須被Ctrl+殺死。C

但非互動式 shell 不使用作業控制。出於歷史原因,非互動式 shell 會退回到忽略後台進程的 SIGINT 和 SIGQUIT 的舊行為,這是有道理的 - 允許後台進程繼續運行,即使鍵盤類型的信號發送給它們。 shell 腳本在非互動式 shell 中執行。

而且,如果您查看trap命令下的最後一段重擊(1), 你會看到的

進入 shell 時被忽略的訊號無法被捕捉或重設。

所以,如果你逃離./scriptb.sh & 你的互動的shell 的命令提示符,其訊號配置保持不變(即使它被放入背景),並且命令trap按預期工作。但是,如果您執行./scripta.sh(使用或不使用&),它將在非互動式 shell 中執行該腳本。當非互動式 shell 運行時./scriptb.sh &,它會將scriptb進程設定為忽略中斷並退出。因此,trap命令scriptb.sh默默地失敗了。

答案2

透過一些追蹤:

strace -o aaa ./scripta

我們可以觀察到預設情況下

read(255, "#!/bin/bash\n# this is scripta.sh"..., 153) = 153
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
lseek(255, -108, SEEK_CUR)              = 45
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1ee9b7ca10) = 2483

scripta阻塞INTCHLD信號也是如此;透過(此處稱為)scriptb繼承這些設定。以及正在做什麼?如果我們從 via 運行它:forkclonescriptbscripta

strace -o bbb ./scriptb &

然後尋找訊號相關的東西,我們發現:

rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_IGN, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, 8) = 0

這表明沒有任何東西被阻止,然後該INT信號首先被給予預設處理,然後被忽略。scriptb直接從shell下運行strace對比顯示:

rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
rt_sigaction(SIGINT, {0x45fbf0, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [INT CHLD], 8) = 0
...

或者在進入然後重複的睡眠呼叫處理之前從未被忽略。好的。呃。讓我們在scripta和之間放置一個墊片scriptb,重置SIGINT為預設狀態...

#include <getopt.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int ch;
    while ((ch = getopt(argc, argv, "h?")) != -1) {
        switch (ch) {
        case 'h':
        case '?':
        default:
            abort();
        }
    }
    argc -= optind;
    argv += optind;
    if (argc < 1) abort();

    signal(SIGINT, SIG_DFL);

    execvp(*argv, argv);
    abort();
    return 1;
}

使用方法如下:

$ make defaultsig
cc     defaultsig.c   -o defaultsig
$ grep defaultsig scripta.sh
./defaultsig ./scriptb.sh &
$ ./scripta.sh 
12510 started
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
...

是的,現在效果很好。但是,我不知道為什麼bash會這樣,也許向他們提交錯誤?中的程式碼sig.c看起來非常複雜,並且其他地方有更多的訊號處理...

答案3

trap 和 SIGINT 的奇怪問題

感謝大家的回答以及您花時間研究這個問題。

請允許我重述並整合(對以下內容中可能顯而易見的內容表示歉意):

1)我忘了在我的問題中加入我也嘗試過SIGQUIT,它的行為與SIGINT相同;

2)由此,我已經懷疑問題與這兩個訊號的互動式 bash 的預設配置有關;

3)與 bash 互動時,它們的預設操作不會發生,因為當您唯一擁有的就是提示符號時,退出或中斷任何事情都是沒有意義的。如果你想離開 shell,只要輸入 exit;

4)我不認為SIGQUIT和SIGINT在作業控制中發揮特殊作用(與SIGTSTP、SIGTTOU、SIGTTIN相反);

5) 對我來說,這兩個訊號的互動式 bash 的預設配置由後台(非互動式)shell(在我們的例子中執行 scriptb.sh 的 shell)繼承是沒有意義的;

6) 事實上,正如前台進程組不繼承(從啟動它的 shell)SIGQUIT 和 SIGINT 的配置一樣,恕我直言,後台進程組發生同樣的情況應該是有意義的。

7)此外,無論繼承的性格是什麼,陷阱都應該改變它。

8)總而言之,我傾向於同意 thrig 的觀點,並認為我們在這裡看到的是一個錯誤。

相關內容