web-dev-qa-db-ja.com

SIGINTがスクリプトのバックグラウンドプロセスで機能しないのはなぜですか?

スクリプトには次のものがあります。

yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
ps aux | grep yes

実行すると、スクリプトの終わりまでにyesがまだ実行されていることが出力に示されます。ただし、コマンドをインタラクティブに実行すると、次のようにプロセスが正常に終了します。

> yes >/dev/null &
[1] 9967
> kill -INT 9967
> ps aux | grep yes
sean ... 0:00 grep yes

SIGINTがインタラクティブインスタンスでプロセスを終了するのに、スクリプトインスタンスでは終了しないのはなぜですか?

編集

問題の診断に役立つ可能性のある補足情報を次に示します。上記のスクリプトをシミュレートするために、次のGoプログラムを作成しました。

package main

import (
    "fmt"
    "os"
    "os/exec"
    "time"
)

func main() {
    yes := exec.Command("yes")
    if err := yes.Start(); err != nil {
        die("%v", err)
    }

    time.Sleep(time.Second*2)

    kill := exec.Command("kill", "-INT", fmt.Sprintf("%d", yes.Process.Pid))
    if err := kill.Run(); err != nil {
        die("%v", err)
    }

    time.Sleep(time.Second*2)

    out, err := exec.Command("bash", "-c", "ps aux | grep yes").CombinedOutput()
    if err != nil {
        die("%v", err)
    }
    fmt.Println(string(out))
}

func die(msg string, args ...interface{}) {
    fmt.Fprintf(os.Stderr, msg+"\n", args...)
    os.Exit(1)
}

スクリプトでmainとしてビルドし、./mainを実行し、./main./main &をインタラクティブに実行すると、次の同じ出力が得られます。

sean ... 0:01 [yes] <defunct>
sean ... 0:00 bash -c ps aux | grep yes
sean ... 0:00 grep yes

ただし、スクリプトで./main &を実行すると、次のようになります。

sean ... 0:03 yes
sean ... 0:00 bash -c ps aux | grep yes
sean ... 0:00 grep yes

これにより、私はこれらすべてをBashシェルで実行していますが、違いはBash自体のジョブ制御とはあまり関係がないと信じています。

4
eZanmoto

シェルが異なればジョブ制御の処理も異なるため、どのシェルが使用されるかが問題になります(ジョブ制御は複雑です; job.c in bashは現在、clocに従ってCの3,300行に相当します。たとえば、Mac OS X10.11でのpdksh5.2.14とbash3.2の比較は次のとおりです。

$ cat code
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
$ bash code
38643
38643
$ ksh code
38650
$ 

また、ここで関連するのは、yesがシグナル処理を実行しないため、親シェルプロセスから継承されるものをすべて継承することです。対照的に、シグナル処理を実行する場合

$ cat sighandlingcode 
Perl -e '$SIG{INT} = sub { die "ouch\n" }; sleep 5' &
pid=$!
sleep 2
kill -INT $pid
$ bash sighandlingcode 
ouch
$ ksh sighandlingcode 
ouch
$ 

—ここではPerlとは異なり、yesが信号処理を変更したため、SIGINTは親シェルに関係なくトリガーされます。信号処理に関連するシステムコールがあります。これは、DTraceやLinuxのstraceなどで確認できます。

-bash-4.2$ cat code
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
pkill yes
-bash-4.2$ rm foo*; strace -o foo -ff bash code
21899
21899
code: line 9: 21899 Terminated              yes > /dev/null
-bash-4.2$ 

yesプロセスがSIGINTを無視して終了することがわかりました。

-bash-4.2$ egrep 'exec.*yes' foo.21*
foo.21898:execve("/usr/bin/pkill", ["pkill", "yes"], [/* 24 vars */]) = 0
foo.21899:execve("/usr/bin/yes", ["yes"], [/* 24 vars */]) = 0
foo.21903:execve("/usr/bin/pgrep", ["pgrep", "yes"], [/* 24 vars */]) = 0
foo.21904:execve("/usr/bin/pkill", ["pkill", "yes"], [/* 24 vars */]) = 0
-bash-4.2$ grep INT foo.21899
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
--- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=21897, si_uid=1000} ---
-bash-4.2$ 

Perlコードを使用してこのテストを繰り返すと、SIGINTが無視されないこと、またはpdkshの下にbashbashのインタラクティブモードのように「モニターモード」をオンにすると、yesが強制終了されます。

-bash-4.2$ cat monitorcode 
#!/bin/bash
set -m
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
pkill yes
-bash-4.2$ ./monitorcode 
22117
[1]+  Interrupt               yes > /dev/null
-bash-4.2$ 
5
thrig