Tenho os dois programas simples a seguir.
Pai:
package main
import (
"fmt"
"syscall"
)
func main() {
attr := &syscall.ProcAttr{
Files: []uintptr{0, 1, 2},
Sys: &syscall.SysProcAttr{ // child in its own group
Setpgid: true,
Pgid: 0,
},
}
_, err := syscall.ForkExec("./child/child", []string{"child"}, attr)
if err != nil {
fmt.Println("Error:", err)
return
}
}
Criança:
package main
import (
"fmt"
"time"
)
func main() {
for {
fmt.Println("hi from child")
time.Sleep(time.Second * 5)
}
}
Saída do ps
:
yakog@yakog-computer:~/goprojects/parent$ ps -o pid,ppid,pgid,uid,wchan,stat,tt,command -t /dev/pts/19
PID PPID PGID UID WCHAN STAT TT COMMAND
1867701 1867320 1867701 1000 do_sel Ss+ pts/19 bash
1870508 2118 1870508 1000 ep_pol Sl pts/19 child
Quando pressiono CTRL-Z
ou CTRL-C
, não tem efeito algum. É exatamente o que eu esperava, já que o processo 1870508 não faz parte do trabalho de primeiro plano e CTRL-Z
/ CTRL-C
invoca kill -SIGTSTP -1867701
/ kill -SIGINT -1867701
. Portanto, 1870508 não recebe esses sinais.
Além disso, quando invoco kill -SIGINT 1870508
ou kill -SIGSTOP 1870508
, o processo é encerrado/suspenso. Ainda consigo entender. Embora 1870508 não faça parte do trabalho em primeiro plano, com kill
o comando enviamos "diretamente" o sinal para o processo .
No entanto, por que kill -SIGTSTP 1870508
não funciona? Após iniciar o ./parent
processo e chamar kill -SIGTSTP 1870508
o comando, literalmente nada acontece (o 1870508 ainda tem status R
/ S
e continua a imprimir no terminal). Não consigo entender por que ele não suspendeu o processo (o moveu para T
). Deve ser o mesmo que com -SIGINT
e -SIGSTOP
( nós enviamos "diretamente" -SIGTSTP para o processo ), no entanto, não tem efeito neste caso.
O estranho é que se alterarmos o código pai (código abaixo) e fizermos com que ele continue a execução após a criação do filho, ele kill -SIGTSTP 1870508
funcionará como deveria (o filho será suspenso).
package main
import (
"fmt"
"os/signal"
"syscall"
"time"
)
func main() {
attr := &syscall.ProcAttr{
Files: []uintptr{0, 1, 2},
Sys: &syscall.SysProcAttr{ // child in its own group
Setpgid: true,
Pgid: 0,
},
}
_, err := syscall.ForkExec("./child/child", []string{"child"}, attr)
signal.Ignore(syscall.SIGTSTP)
if err != nil {
fmt.Println("Error:", err)
return
}
for {
fmt.Println("hi from parent")
time.Sleep(time.Second * 7)
}
}
Além disso, quando eu invoco kill -SIGSTOP 1870508
(movo o processo para T
o estado) e então invoco, kill -SIGINT 1870508
o processo não é encerrado... Por quê?
Ver
info libc 'Job Control Signals'
:Ou a especificação POSIX :
Um grupo de processos órfãos é definido como:
E seu grupo de processos de ID 1870508 se qualifica, pois seu único processo (1870508) tem 2118 como pai, provavelmente o sub-ceifador filho ao qual ele foi reconectado após a morte de seu pai.
Todos os processos recebem sinais o tempo todo. Eles não se importam se estão em primeiro plano, segundo plano ou parados.
As chaves
ctrl-z
ectrl-c
enviam o sinal para o processo em primeiro plano. Isso não significa que o processo deve estar em primeiro plano para receber um sinal, mas significa que se o processo não estiver em primeiro plano,ctrl-c
envia o sinal para algum outro processo. Então o comportamento não é diferente, é um processo diferente que recebe o comportamento. Sempre há algum processo em primeiro plano. Então se seu processo alvo estiver em segundo plano e você pressionarctrl-c
seu shell está recebendo o ctrl-c, e ele lida com isso ou ignora, mas não sai.Se um processo for suspenso (
T
estado), ele recebe o sinal. No entanto, um processo pode capturar SIGINT e fazer processamento adicional, mas se for suspenso, o sinal será colocado em sua fila pendente e ele fará esse processamento (e então possivelmente sairá) quando não estiver mais suspenso.Experimentando enviar sinais para processos suspensos, notei que alguns sinais os fazem morrer imediatamente, e alguns exigem que sejam retomados antes que o sinal tenha efeito. Possivelmente há algo no processo (como uma biblioteca?) que define um manipulador de sinais para esses sinais para que ele possa fazer alguma limpeza (como liberar buffers) antes que o processo saia.