Como propagar um sinal para os processos infantis de um script bash
- 3227
- 160
- Enrique Gutkowski PhD
Suponha que escrevamos um script que gera um ou mais processos de execução longa; Se o referido script recebe um sinal como Sigint
ou Sigterm
, Provavelmente queremos que seus filhos também sejam demitidos (normalmente quando os pais morrem, os filhos sobrevivem). Também podemos querer executar algumas tarefas de limpeza antes que o próprio script saia. Para poder alcançar nosso objetivo, devemos primeiro aprender sobre grupos de processos e como executar um processo em segundo plano.
Neste tutorial, você aprenderá:
- O que é um grupo de processo
- A diferença entre os processos de primeiro plano e plano
- Como executar um programa em segundo plano
- Como usar a concha
espere
incorporado para aguardar um processo executado em segundo plano - Como encerrar os processos filhos quando o pai recebe um sinal
Requisitos de software e convenções usadas
Categoria | Requisitos, convenções ou versão de software usada |
---|---|
Sistema | Distribuição Independente |
Programas | Nenhum software específico é necessário |
Outro | Nenhum |
Convenções | # - requer que os comandos Linux sejam executados com privilégios root diretamente como usuário root ou por uso de sudo comando$ - Requer que os comandos do Linux sejam executados como um usuário não privilegiado regular |
Um exemplo simples
Vamos criar um script muito simples e simular o lançamento de um processo de longa execução:
#!/BIN/BASH TRAP "ECHO SINAL RECEBIDO!"SIGINT ECHO" O script PID é $ "Sleep 30
cópia de A primeira coisa que fizemos no roteiro foi criar uma armadilha para pegar Sigint
e imprima uma mensagem quando o sinal for recebido. Nós do que fazemos nosso script imprimir seu PID: podemos obter expandindo o $$
variável. Em seguida, executamos o dormir
comando para simular um processo de longa execução (30
segundos).
Salvamos o código dentro de um arquivo (digamos que ele é chamado teste.sh
), torná -lo executável e inicie -o de um emulador de terminal. Obtemos o seguinte resultado:
O script pid é 101248
Se estivermos focados no emulador de terminal e pressionamos Ctrl+C enquanto o script está em execução, um Sigint
o sinal é enviado e tratado por nosso armadilha:
O script pid é 101248 ^CSignal recebido!
Embora a armadilha tenha lidado com o sinal como esperado, o script foi interrompido de qualquer maneira. Por que isso aconteceu? Além disso, se enviarmos um Sigint
sinalize para o script usando o matar
Comando, o resultado que obtemos é bem diferente: a armadilha não é executada imediatamente e o script continua até que o processo infantil não saia (depois 30
segundos de "Sleeping"). Por que essa diferença? Vamos ver…
Grupos de processos, em primeiro plano e trabalhos de fundo
Antes de respondermos às perguntas acima, devemos entender melhor o conceito de grupo de processos.
Um grupo de processos é um grupo de processos que compartilham o mesmo PGID (ID do grupo de processo). Quando um membro de um grupo de processos cria um processo infantil, esse processo se torna um membro do mesmo grupo de processo. Cada grupo de processos tem um líder; Podemos reconhecê -lo facilmente porque é PID e a PGID são os mesmos.
Podemos visualizar PID e PGID de executar processos usando o ps
comando. A saída do comando pode ser personalizada para que apenas os campos em que estamos interessados sejam exibidos: neste caso Cmd, PID e PGID. Fazemos isso usando o -o
Opção, fornecendo uma lista de campos separados por vírgula como argumento:
$ ps -a -o pid, pgid, cmd
Se executarmos o comando enquanto nosso script estiver executando a parte relevante da saída que obtemos é a seguinte:
PID PGID CMD 298349 298349 /BIN /BASH ./teste.SH 298350 298349 Sono 30
Podemos ver claramente dois processos: o PID do primeiro é 298349
, O mesmo que o seu PGID: este é o líder do grupo de processos. Foi criado quando lançamos o script como você pode ver no Cmd coluna.
Este processo principal lançou um processo infantil com o comando sono 30
: Como esperado, os dois processos estão no mesmo grupo de processo.
Quando pressionamos o Ctrl-C enquanto focamos no terminal do qual o script foi lançado, o sinal não foi enviado apenas ao processo pai, mas para todo o grupo de processos. Qual grupo de processos? O grupo de processos em primeiro plano do terminal. Todos os processos membros deste grupo são chamados processos de primeiro plano, Todos os outros são chamados processos de fundo. Aqui está o que o manual do Bash tem a dizer sobre o assunto:
VOCÊ SABIA?Para facilitar a implementação da interface do usuário para o controle de trabalho, o sistema operacional mantém a noção de um ID atual do grupo de processos terminais. Membros desse grupo de processos (processos cujo ID do grupo de processo é igual ao ID do grupo de processos do terminal atual) recebem sinais gerados pelo teclado, como o SIGINT. Dizem que esses processos estão em primeiro plano. Os processos de fundo são aqueles cujo ID do grupo de processos difere do do terminal; Tais processos são imunes a sinais gerados pelo teclado.Quando enviamos o Sigint
sinal com o matar
Comando, em vez disso, direcionamos apenas o PID do processo pai; Bash exibe um comportamento específico quando um sinal é recebido enquanto aguarda a conclusão de um programa: o "código de armadilha" para esse sinal não é executado até que esse processo termine. É por isso que a mensagem "recebida do sinal" foi exibida somente após o dormir
comando saiu.
Para replicar o que acontece quando pressionamos Ctrl-C no terminal usando o matar
Comando para enviar o sinal, devemos atingir o grupo de processos. Podemos enviar um sinal para um grupo de processo usando a negação do PID do líder do processo, Então, supondo o PID do líder do processo é 298349
(Como no exemplo anterior), nós funcionaríamos:
$ kill -2 -298349
Gerenciar a propagação de sinal de dentro de um script
Agora, suponha que lançamos um script de longa execução de um shell não interativo, e queremos que o script gerencie a propagação do sinal automaticamente, para que quando ele recebe um sinal como Sigint
ou Sigterm
Ele termina sua criança potencialmente longa, eventualmente executando algumas tarefas de limpeza antes de sair. Como podemos fazer isso?
Como fizemos anteriormente, podemos lidar com a situação em que um sinal é recebido em uma armadilha; No entanto, como vimos, se um sinal é recebido enquanto o shell está esperando a conclusão de um programa, o "Código de Trap" é executado somente após a saída do processo infantil.
Não é isso que queremos: queremos que o código de armadilha seja processado assim que o processo pai receber o sinal. Para atingir nosso objetivo, devemos executar o processo infantil no fundo: podemos fazer isso colocando o &
símbolo após o comando. No nosso caso, escrevíamos:
#!/Bin/Bash Trap 'Echo Signal recebido!'SIGINT ECHO "O script pid é $" Sleep 30 &
cópia de Se deixarmos o script dessa maneira, o processo pai sairia logo após a execução do sono 30
comando, deixando -nos sem a chance de executar tarefas de limpeza depois de terminar ou ser interrompido. Podemos resolver esse problema usando o shell espere
construídas em. A página de ajuda de espere
define desta maneira:
Aguarda cada processo identificado por um ID, que pode ser um ID de processo ou uma especificação de trabalho e relata seu status de terminação. Se o ID não for dado, espera por todos os processos infantis atualmente ativos e o status de retorno é zero.
Depois de definirmos um processo a ser executado em segundo plano, podemos recuperar seu PID no $!
variável. Podemos passar como um argumento para espere
Para fazer o processo dos pais aguardar seu filho:
#!/Bin/Bash Trap 'Echo Signal recebido!'SIGINT ECHO "O script PID é $" Sleep 30 e espere $!
cópia de Terminamos? Não, ainda há um problema: a recepção de um sinal tratado em uma armadilha dentro do script, causa o espere
Construído para retornar imediatamente, sem realmente esperar pelo término do comando em segundo plano. Este comportamento está documentado no manual do Bash:
Para resolver este problema, temos que usar espere
Novamente, talvez como parte da própria armadilha. Aqui está a aparência do nosso script no final:
#!/Bin/Bash Cleanup () echo "Limpeza ..." # Nosso código de limpeza vai aqui TRAP 'ECHO SINAL RECEBIDO!; matar "$ Child_pid"; aguarde "$ child_pid"; limpeza 'sigint sigterm eco "O script pid é $" sono 30 & child_pid = "$!"Espere" $ Child_pid "
cópia de No script, criamos um limpar
função onde poderíamos inserir nosso código de limpeza e fizer nosso armadilha
Pegue também o Sigterm
sinal. Aqui está o que acontece quando executamos este script e enviamos um desses dois sinais para ele:
- O script é lançado e o
sono 30
O comando é executado em segundo plano; - O PID do processo infantil é "armazenado" no
Child_pid
variável; - O script aguarda o término do processo infantil;
- O script recebe um
Sigint
ouSigterm
sinal - O
espere
O comando retorna imediatamente, sem esperar o término da criança;
Neste ponto, a armadilha é executada. Iniciar:
- A
Sigterm
sinal (omatar
padrão) é enviado para oChild_pid
; - Nós
espere
para garantir que a criança seja encerrada após receber este sinal. - Depois
espere
retorna, executamos olimpar
função.
Propagar o sinal para várias crianças
No exemplo acima, trabalhamos com um script que tinha apenas um processo infantil. E se um script tiver muitos filhos, e se alguns deles tiverem filhos próprios?
No primeiro caso, uma maneira rápida de obter o pids de todas as crianças é usar o Jobs -P
Comando: este comando exibe os PIDs de todos os trabalhos ativos no shell atual. Podemos do que usar matar
para encerrá -los. Aqui está um exemplo:
#!/Bin/Bash Cleanup () echo "Limpeza ..." # Nosso código de limpeza vai aqui TRAP 'ECHO SINAL RECEBIDO!; matar $ (empregos -p); espere; limpeza 'sigint sigterm eco "o script pid é $" sono 30 e sono 40 e espere
cópia de O script lança dois processos em segundo plano: usando o espere
incorporado sem argumentos, esperamos por todos eles e mantemos o processo pai vivo. Quando o Sigint
ou Sigterm
Os sinais são recebidos pelo script, enviamos um Sigterm
para os dois, tendo seus PIDs devolvidos pelo Jobs -P
comando (trabalho
é um shell embutido; portanto, quando o usamos, um novo processo não é criado).
Se os filhos têm um processo de filhos, e queremos encerrar todos eles quando o ancestral receber um sinal, podemos enviar um sinal para todo o grupo de processos, como vimos antes.
Isso, no entanto, apresenta um problema, já que, enviando um sinal de terminação para o grupo de processos, entraríamos em um loop de "sinal de sinal/sinal". Pense nisso: no armadilha
para Sigterm
nós enviamos um Sigterm
sinal para todos os membros do grupo de processos; Isso inclui o próprio script pai!
Para resolver esse problema e ainda ser capaz de executar uma função de limpeza após o término dos processos infantis, devemos alterar o armadilha
para Sigterm
Pouco antes de enviarmos o sinal para o grupo de processos, por exemplo:
#!/bin/bash limpeut () echo "limpando ..." # Nosso código de limpeza vai aqui trap 'trap' "sigterm; matar 0; espere; limpeza 'sigint sigterm eco "o script pid é $" sono 30 e sono 40 e espere
cópia de Na armadilha, antes de enviar Sigterm
Para o grupo de processos, mudamos o Sigterm
armadilha, para que o processo pai ignore o sinal e somente seus descendentes são afetados por ele. Observe também que na armadilha, para sinalizar o grupo de processos, usamos matar
com 0
como pid. Este é um tipo de atalho: quando o PID passado para matar
é 0
, todos os processos no atual Grupo de processo é sinalizado.
Conclusões
Neste tutorial, aprendemos sobre grupos de processos e qual é a diferença entre os processos em primeiro plano e em segundo plano. Aprendemos que o Ctrl-C envia um Sigint
sinalize para todo o grupo de processos em primeiro plano do terminal de controle e aprendemos a enviar um sinal para um grupo de processo usando matar
. Também aprendemos a executar um programa em segundo plano e como usar o espere
Shell incorporado para esperar que ela saia sem perder a concha pai. Finalmente, vimos como configurar um script para que, quando ele receber um sinal, ele termina seus filhos antes de sair. Perdi algo? Você tem suas receitas pessoais para realizar a tarefa? Não hesite em me avisar!
Tutoriais do Linux relacionados:
- Gerenciamento de processos de fundo bash
- Multi-thread Bash Script e Gerenciamento de Processos no…
- Uma introdução à automação, ferramentas e técnicas do Linux
- Como executar comando em segundo plano no Linux
- Mastering Bash Script Loops
- Como instalar o sinal no Linux
- Como rastrear chamadas de sistema feitas por um processo com Strace em…
- Mint 20: Melhor que o Ubuntu e o Microsoft Windows?
- Comparando Linux Apache Prefork vs Worker MPMS
- Como trabalhar com grupos de pacotes DNF
- « MySQL Alterar senha do usuário
- Como reconstruir um pacote usando o sistema de construção do arco Linux »