Como usar os manipuladores de sinal na linguagem C?

Como usar os manipuladores de sinal na linguagem C?
Neste artigo, vamos mostrar como usar os manipuladores de sinal no Linux usando a linguagem C. Mas primeiro discutiremos o que é sinal, como ele gerará alguns sinais comuns que você pode usar em seu programa e depois veremos como vários sinais podem ser tratados por um programa enquanto o programa é executado. Então vamos começar.

Sinal

Um sinal é um evento que é gerado para notificar um processo ou tópico que alguma situação importante chegou. Quando um processo ou tópico recebe um sinal, o processo ou o tópico interrompe o que está fazendo e toma alguma ação. O sinal pode ser útil para a comunicação entre processos.

Sinais padrão

Os sinais são definidos no arquivo de cabeçalho sinal.h Como uma macro constante. O nome do sinal começou com um "sig" e seguido por uma breve descrição do sinal. Então, todo sinal tem um valor numérico único. Seu programa deve sempre usar o nome dos sinais, não o número dos sinais. O motivo é que o número do sinal pode diferir de acordo com o sistema, mas o significado dos nomes será padrão.

A macro Nsig é o número total de sinal definido. O valor de Nsig é maior que o número total de sinal definido (todos os números de sinal são alocados consecutivamente).

A seguir estão os sinais padrão:

Nome do sinal Descrição
Sighup Aperte o processo. O sinal de suspiro é usado para relatar a desconexão do terminal do usuário, possivelmente porque uma conexão remota é perdida ou desliga.
Sigint Interromper o processo. Quando o usuário digita o caractere INTR (normalmente Ctrl + C), o sinal SIGINT é enviado.
Sigquit Saia do processo. Quando o usuário digita o caractere de abandono (normalmente Ctrl + \), o sinal sigquit é enviado.
Sigill Instrução ilegal. Quando é feita uma tentativa de executar lixo ou instrução privilegiada, o sinal sigill é gerado. Além disso, Sigill pode ser gerado quando a pilha transbordar ou quando o sistema tiver problemas para executar um manipulador de sinal.
Sigtrap Armadilha de rastreamento. Uma instrução de ponto de interrupção e outras instruções de armadilha gerarão o sinal Sigtrap. O depurador usa este sinal.
Sigabrt Abortar. O sinal Sigabrt é gerado quando a função abort () é chamada. Este sinal indica um erro detectado pelo próprio programa e relatado pela chamada de função abort ().
Sigfpe Exceção de ponto flutuante. Quando ocorreu um erro aritmético fatal, o sinal SIGFPE é gerado.
SIGUSR1 e SIGUSR2 Os sinais SIGUSR1 e SIGUSR2 podem ser usados ​​como você deseja. É útil escrever um manipulador de sinal para eles no programa que recebe o sinal de comunicação simples entre processos.

Ação padrão dos sinais

Cada sinal tem uma ação padrão, um dos seguintes:

Prazo: O processo terá rescisão.
Essencial: O processo será encerrado e produzirá um arquivo de despejo principal.
IGN: O processo irá ignorar o sinal.
Parar: O processo vai parar.
Cont O processo continuará de ser parado.

A ação padrão pode ser alterada usando a função de manipulador. A ação padrão de algum sinal não pode ser alterada. Sigkill e Sigabrt A ação padrão do sinal não pode ser alterada ou ignorada.

Manuseio de sinal

Se um processo recebe um sinal, o processo tem uma escolha de ação para esse tipo de sinal. O processo pode ignorar o sinal, especificar uma função de manipulador ou aceitar a ação padrão para esse tipo de sinal.

  • Se a ação especificada para o sinal for ignorada, o sinal será descartado imediatamente.
  • O programa pode registrar uma função de manipulador usando função como sinal ou sigacção. Isso é chamado de manipulador pega o sinal.
  • Se o sinal não foi tratado nem ignorado, sua ação padrão ocorre.

Podemos lidar com o sinal usando sinal ou sigacção função. Aqui vemos como o mais simples sinal() A função é usada para lidar com sinais.

int signal () (int signum, void (*func) (int))

O sinal() vai ligar para o functão função se o processo receber um sinal Signum. O sinal() Retorna um ponteiro para funcionar functão se for bem -sucedido ou retornar um erro para errar e -1 caso contrário.

O functão O ponteiro pode ter três valores:

  1. Sig_dfl: É um ponteiro para a função padrão do sistema Sig_dfl (), declarado em h arquivo de cabeçalho. É usado para tomar medidas padrão do sinal.
  2. Sig_ign: É um ponteiro para o sistema ignorar a função Sig_ign (),declarado em h arquivo de cabeçalho.
  3. Ponteiro de função do manipulador definido pelo usuário: O tipo de função do manipulador definido pelo usuário é void (*) (int), significa que o tipo de retorno é nulo e um argumento do tipo int.

Exemplo de manipulador de sinal básico

#incluir
#incluir
#incluir
void sig_handler (int signum)
// O tipo de retorno da função do manipulador deve ser anulado
printf ("\ Ninside Handler function \ n");

int main ()
sinal (sigint, sig_handler); // Registre o manipulador de sinal
para (int i = 1 ;; i ++) // loop infinito
printf ("%d: função principal interna \ n", i);
sono (1); // atraso por 1 segundo

retornar 0;

Na captura de tela da saída do exemplo1.C, podemos ver que na função principal o loop infinito está executando. Quando o usuário digitou Ctrl+C, a execução da função principal parou e a função manipuladora do sinal é invocada. Após a conclusão da função do manipulador, a execução da função principal retomada. Quando o tipo de usuário digitou Ctrl+\, o processo é interrompido.

Ignorar sinais de exemplo

#incluir
#incluir
#incluir
int main ()
sinal (sigint, sig_ign); // Registre o manipulador de sinal para ignorar o sinal
para (int i = 1 ;; i ++) // loop infinito
printf ("%d: função principal interna \ n", i);
sono (1); // atraso por 1 segundo

retornar 0;

Aqui a função manipuladora é registrada para Sig_ign () função para ignorar a ação do sinal. Então, quando o usuário digitou Ctrl+C, Sigint o sinal está gerando, mas a ação é ignorada.

Exemplo de manipulador de sinal de Reregister

#incluir
#incluir
#incluir
void sig_handler (int signum)
printf ("\ Ninside Handler function \ n");
sinal (sigint, sig_dfl); // RE Registrar manipulador de sinal para ação padrão

int main ()
sinal (sigint, sig_handler); // Registre o manipulador de sinal
para (int i = 1 ;; i ++) // loop infinito
printf ("%d: função principal interna \ n", i);
sono (1); // atraso por 1 segundo

retornar 0;

Na captura de tela da saída do exemplo3.C, podemos ver que, quando o usuário digitou pela primeira vez Ctrl+C, a função Handler é invocada. Na função do manipulador, o manipulador de sinal registra para Sig_dfl Para ação padrão do sinal. Quando o usuário digitou Ctrl+C pela segunda vez, o processo é encerrado, que é a ação padrão de Sigint sinal.

Sinais de envio:

Um processo também pode enviar sinais explicitamente para si ou para outro processo. função Raise () e Kill () pode ser usada para enviar sinais. Ambas as funções são declaradas em sinal.H Arquivo de cabeçalho.

int raise (int signum)

A função Raise () usada para enviar sinal Signum para o processo de chamada (por si só). Ele retorna zero se for bem -sucedido e um valor diferente de zero se falhar.

int matar (pid_t pid, int signum)

A função de morte usada para enviar um sinal Signum para um processo ou grupo de processo especificado por PID.

Exemplo de manipulador de sinal SIGUSR1

#incluir
#incluir
void sig_handler (int signum)
printf ("Função do manipulador interno \ n");

int main ()
sinal (SIGUSR1, SIG_HANDLER); // Registre o manipulador de sinal
printf ("Inside principal função \ n");
aumento (SIGUSR1);
printf ("Inside principal função \ n");
retornar 0;

Aqui, o processo envia o sinal SIGUSR1 para si mesmo usando a função Raise ().

Aumente o programa de exemplo de Kill

#incluir
#incluir
#incluir
void sig_handler (int signum)
printf ("Função do manipulador interno \ n");

int main ()
pid_t pid;
sinal (SIGUSR1, SIG_HANDLER); // Registre o manipulador de sinal
printf ("Inside principal função \ n");
pid = getpid (); // Processo ID de si mesmo
matar (PID, SIGUSR1); // Envie o SIGUSR1 para si mesmo
printf ("Inside principal função \ n");
retornar 0;

Aqui, o processo envia SIGUSR1 sinalizando para si mesmo usando matar() função. getpid () é usado para obter o ID do processo de si mesmo.

No próximo exemplo, veremos como os processos de pais e filhos se comunicam (comunicação entre processos) usando matar() e função de sinal.

Comunicação dos filhos de pais com sinais

#incluir
#incluir
#incluir
#incluir
void sig_handler_parent (int signum)
printf ("Pai: recebeu um sinal de resposta da criança \ n");

void sig_handler_child (int signum)
printf ("Child: recebeu um sinal do pai \ n");
sono (1);
matar (getppid (), SIGUSR1);

int main ()
pid_t pid;
if ((pid = fork ())<0)
printf ("Fork falhou \ n");
saída (1);

/ * Processo infantil */
else if (pid == 0)
sinal (SIGUSR1, SIG_HANDLER_CHILD); // Registre o manipulador de sinal
printf ("Child: Waiting for Signal \ n");
pausa();

/ * Processo pai */
outro
sinal (SIGUSR1, SIG_HANDLER_PARENT); // Registre o manipulador de sinal
sono (1);
printf ("Pai: enviando sinal para a criança \ n");
matar (PID, SIGUSR1);
printf ("Pai: aguardando a resposta \ n");
pausa();

retornar 0;

Aqui, garfo() A função cria processos infantis e retorna zero ao processo da criança e ID do processo para a criança ao processo dos pais. Então, o PID foi verificado para decidir o processo pai e filho. No processo dos pais, ele está dormindo por 1 segundo para que o processo infantil possa registrar a função do manipulador de sinal e aguardar o sinal do pai. Após 1 segundo processo pai, envie SIGUSR1 sinalize para o processo infantil e aguarde o sinal de resposta da criança. No processo infantil, primeiro está esperando o sinal do pai e quando o sinal é recebido, a função do manipulador é invocada. Da função do manipulador, o processo filho envia outro SIGUSR1 sinal para o pai. Aqui getppid () A função é usada para obter o ID do processo pai.

Conclusão

O sinal no Linux é um grande tópico. Neste artigo, vimos como lidar com o sinal do muito básico e também obter um conhecimento de como o sinal gera, como um processo pode enviar sinal para si e outros processos, como o sinal pode ser usado para comunicação entre processos.