Por que o SIGINT não funciona em um processo de segundo plano em um script?

3

Eu tenho o seguinte em um script:

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

Quando eu executo, a saída mostra que yes ainda está em execução no final do script. No entanto, se eu executar os comandos de forma interativa, o processo será finalizado com êxito, conforme descrito a seguir:

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

Por que o SIGINT encerra o processo na instância interativa, mas não na instância do script?

EDITAR

Aqui estão algumas informações complementares que podem ajudar a diagnosticar o problema. Eu escrevi o seguinte programa Go para simular o script acima.

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)
}

Eu criei como main e executou ./main em um script, e a execução ./main e ./main & interativamente deu o mesmo resultado a seguir:

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

No entanto, a execução de ./main & em um script fornece o seguinte:

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

Isso me faz acreditar que a diferença tem menos a ver com o controle de trabalho do próprio Bash, embora eu esteja executando tudo isso em um shell Bash.

    
por eZanmoto 21.06.2017 / 19:45

1 resposta

5

O shell usado é uma preocupação, pois shells diferentes lidam com o controle de tarefas de maneira diferente (e o controle de tarefas é complicado; job.c in bash atualmente pesa 3.300 linhas de C de acordo com cloc ). pdksh 5.2.14 versus bash 3.2 no Mac OS X 10.11 por exemplo show:

$ 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
$ 

Também relevante aqui é que yes não executa manipulação de sinais, portanto, herda o que houver para ser herdado do processo de shell pai; se, ao contrário, fizermos o manuseio do sinal -

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

- o SIGINT é acionado independentemente do shell pai, já que perl aqui, ao contrário de yes , alterou o tratamento do sinal. Existem chamadas de sistema relevantes para o tratamento de sinais que podem ser observadas com coisas como o DTrace ou aqui strace no Linux:

-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$ 

Descobrimos que o processo yes termina com SIGINT ignorado:

-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$ 

Repita este teste com o código perl e deve-se ver que SIGINT não é ignorado, ou também que sob pdksh não há nenhum ignorar sendo definido como em bash . Com o "modo monitor" ativado como está no modo interativo em bash , yes é eliminado.

-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$ 
    
por 21.06.2017 / 23:15