Como os sinais funcionam internamente?

31

Em geral, para matar processos que geram sinais como SIGKILL, SIGTSTPetc.

Mas como é sabido quem ordenou esse sinal específico, quem o enviou a um processo específico e, em geral, como os sinais executam suas operações? Como os sinais funcionam internamente?

Varun Chhangani
fonte
A pergunta é um pouco difícil de entender. Peço desculpas e não quero dizer desrespeito. Deseja saber quem pode ter executado um comando que interrompeu um processo ou deseja saber mais sobre o SIGKILL e o SIGSTP?
pullsumo
@mistermister Quero saber quem pode ter executado um comando que matou um processo e como?
Varun Chhangani

Respostas:

35

A visão de 50.000 pés é a seguinte:

  1. Um sinal é gerado pelo kernel internamente (por exemplo, SIGSEGVquando um endereço inválido é acessado ou SIGQUITquando você pressiona Ctrl+ \), ou por um programa que usa o killsyscall (ou vários relacionados).

  2. Se for por um dos syscalls, o kernel confirmará que o processo de chamada possui privilégios suficientes para enviar o sinal. Caso contrário, um erro é retornado (e o sinal não acontece).

  3. Se for um dos dois sinais especiais, o kernel age incondicionalmente, sem nenhuma entrada do processo de destino. Os dois sinais especiais são SIGKILL e SIGSTOP. Todas as informações abaixo sobre ações padrão, sinais de bloqueio etc. são irrelevantes para esses dois.

  4. Em seguida, o kernel descobre o que fazer com o sinal:

    1. Para cada processo, há uma ação associada a cada sinal. Existem vários padrões, e os programas podem definir diferentes usando sigaction, signaletc. Isso inclui coisas como "ignorar completamente", "interromper o processo", "interromper o processo com um dump principal", "interromper o processo", etc.

    2. Os programas também podem desativar a entrega de sinais ("bloqueados"), sinal por sinal. Então o sinal permanece pendente até ser desbloqueado.

    3. Os programas podem solicitar que, em vez de o kernel tomar alguma ação, ele entregue o sinal ao processo de forma síncrona (com sigwait, et al. Ou signalfd) ou de forma assíncrona (interrompendo o que o processo está fazendo e chamando uma função especificada).

Há um segundo conjunto de sinais chamado "sinais em tempo real", que não têm significado específico, e também permitem que vários sinais sejam colocados em fila (sinais normais enfileiram apenas um de cada quando o sinal está bloqueado). Eles são usados ​​em programas multithread para que os threads se comuniquem. Vários são usados ​​na implementação de threads POSIX da glibc, por exemplo. Eles também podem ser usados ​​para se comunicar entre diferentes processos (por exemplo, você pode usar vários sinais em tempo real para que um programa fooctl envie uma mensagem ao foo daemon).

Para uma visão que não seja de 50.000 pés, tente man 7 signale também a documentação interna (ou fonte) do kernel.

derobert
fonte
"Os dois sinais especiais são SIGKILL e SIGSTOP" Então, o que pode SIGCONT ser ...
Hauke Laging
@HaukeLaging SIGCONT é o sinal que desfaz o SIGSTOP. A documentação não a lista como especial ... Portanto, não tenho certeza se tecnicamente um processo pode configurá-lo para ignorá-lo, então você não poderá retomar o processo (apenas o SIGKILL).
derobert
22

A implementação do sinal é muito complexa e específica do kernel. Em outras palavras, diferentes kernels implementarão sinais de maneira diferente. Uma explicação simplificada é a seguinte:

A CPU, com base em um valor especial de registro, possui um endereço na memória onde espera encontrar uma "tabela de descritores de interrupção", que na verdade é uma tabela vetorial. Há um vetor para todas as exceções possíveis, como divisão por zero, ou interceptação, como INT 3 (depuração). Quando a CPU encontra a exceção, ela salva os sinalizadores e o ponteiro de instrução atual na pilha e depois salta para o endereço especificado pelo vetor relevante. No Linux, esse vetor sempre aponta para o kernel, onde há um manipulador de exceções. A CPU está pronta e o kernel Linux assume o controle.

Observe que você também pode acionar uma exceção do software. Por exemplo, o usuário pressiona CTRL- C, então essa chamada vai para o kernel que chama seu próprio manipulador de exceções. Em geral, existem diferentes maneiras de chegar ao manipulador, mas independentemente da mesma coisa básica acontece: o contexto é salvo na pilha e o manipulador de exceção do kernel é pulado para.

O manipulador de exceção decide qual thread deve receber o sinal. Se algo como divisão por zero ocorreu, é fácil: o encadeamento que causou a exceção recebe o sinal, mas para outros tipos de sinais, a decisão pode ser muito complexa e, em alguns casos incomuns, um encadeamento mais ou menos aleatório pode pegue o sinal.

Para enviar o sinal, o que o kernel faz é primeiro definir um valor indicando o tipo de sinal, SIGHUPou o que seja. Este é apenas um número inteiro. Todo processo possui uma área de memória de "sinal pendente" onde esse valor é armazenado. Em seguida, o kernel cria uma estrutura de dados com as informações do sinal. Essa estrutura inclui um sinal de "disposição" que pode ser o padrão, ignorar ou manipular. O kernel então chama sua própria função do_signal(). A próxima fase começa.

do_signal()primeiro decide se ele irá lidar com o sinal. Por exemplo, se é uma morte , do_signal()apenas mata o processo, fim da história. Caso contrário, ele olha para a disposição. Se a disposição for padrão, do_signal()lida com o sinal de acordo com uma política padrão que depende do sinal. Se a disposição for manipulada, significa que existe uma função no programa do usuário projetada para manipular o sinal em questão e o ponteiro para esta função estará na estrutura de dados acima mencionada. Nesse caso, do_signal () chama outra função do kernel,handle_signal(), que passa pelo processo de retornar ao modo de usuário e chamar essa função. Os detalhes dessa transferência são extremamente complexos. Esse código no seu programa geralmente é vinculado automaticamente ao seu programa quando você usa as funções no signal.h.

Examinando o valor do sinal pendente adequadamente, o kernel pode determinar se o processo está manipulando todos os sinais e tomará as ações apropriadas, caso contrário, o que pode estar colocando o processo em suspensão ou matando-o ou outra ação, dependendo do sinal.

Tyler Durden
fonte
15

Embora essa pergunta tenha sido respondida, deixe-me postar um fluxo detalhado de eventos no kernel do Linux.
Isso é copiado inteiramente das postagens do Linux: Linux Signals - Internals no blog “Linux posts” em sklinuxblog.blogspot.in.

Programa C do Espaço do Usuário de Sinal

Vamos começar escrevendo um programa C de espaço de usuário de sinal simples:

#include<signal.h>
#include<stdio.h>

/* Handler function */
void handler(int sig) {
    printf("Receive signal: %u\n", sig);
};

int main(void) {
    struct sigaction sig_a;

    /* Initialize the signal handler structure */
    sig_a.sa_handler = handler;
    sigemptyset(&sig_a.sa_mask);
    sig_a.sa_flags = 0;

    /* Assign a new handler function to the SIGINT signal */
    sigaction(SIGINT, &sig_a, NULL);

    /* Block and wait until a signal arrives */
    while (1) {
            sigsuspend(&sig_a.sa_mask);
            printf("loop\n");
    }
    return 0;
};

Este código atribui um novo manipulador para o sinal SIGINT. O SIGINT pode ser enviado para o processo em execução usando a combinação de teclas Ctrl+ C. Quando Ctrl+ Cé pressionado, o sinal assíncrono SIGINT é enviado para a tarefa. Também é equivalente a enviar o kill -INT <pid>comando em outro terminal.

Se você fizer uma kill -l(que é minúscula L, que significa “lista”), conhecerá os vários sinais que podem ser enviados para um processo em execução.

[root@linux ~]# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

Também pode ser usada a seguinte combinação de teclas para enviar sinais específicos:

  • Ctrl+ C- envia ao SIGINT qual ação padrão é finalizar o aplicativo.
  • Ctrl+ \  - envia a SIGQUIT qual ação padrão é finalizar o núcleo de dumping do aplicativo.
  • Ctrl+ Z- envia SIGSTOP que suspende o programa.

Se você compilar e executar o programa C acima, obterá a seguinte saída:

[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop

Mesmo com Ctrl+ Cou kill -2 <pid>o processo não será finalizado. Em vez disso, ele executará o manipulador de sinal e retornará.

Como o sinal é enviado ao processo

Se virmos as partes internas do sinal enviando para um processo e colocarmos o Jprobe com dump_stack na __send_signalfunção, veremos o seguinte rastreamento de chamada:

May  5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May  5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May  5 16:18:37 linux kernel: complete_signal+0x205/0x250
May  5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May  5 16:18:37 linux kernel: send_signal+0x3e/0x80
May  5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May  5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May  5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May  5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May  5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May  5 16:18:37 linux kernel:  ? ftrace_ops_list_func+0x106/0x120
May  5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May  5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May  5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May  5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May  5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May  5 16:18:37 linux kernel:  kthread+0xcf/0xe0
May  5 16:18:37 linux kernel:  kthread_create_on_node+0x140/0x140
May  5 16:18:37 linux kernel:  ret_from_fork+0x7c/0xb0
May  5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140

Portanto, a principal função solicita o envio do sinal:

First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state()  -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.

Agora tudo está configurado e as alterações necessárias são feitas task_structno processo.

Manipulação de sinal

O sinal é verificado / tratado por um processo quando retorna da chamada do sistema ou se o retorno da interrupção é feito. O retorno da chamada do sistema está presente no arquivo entry_64.S.

A função int_signal é chamada a partir da entry_64.Squal chama a função do_notify_resume().

Vamos verificar a função do_notify_resume(). Esta função verifica se temos o TIF_SIGPENDINGsinalizador definido no task_struct:

 /* deal with pending signal delivery */
 if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

Chamadas e sinais do sistema

Chamadas "lentas", por exemplo, bloqueio de leitura / gravação, processos em estado de espera: TASK_INTERRUPTIBLEou TASK_UNINTERRUPTIBLE.

Uma tarefa no estado TASK_INTERRUPTIBLEserá alterada para o TASK_RUNNINGestado por um sinal. TASK_RUNNINGsignifica que um processo pode ser agendado.

Se executado, seu manipulador de sinal será executado antes da conclusão do syscall "lento". O syscallnão é concluído por padrão.

Se o SA_RESTARTsinalizador estiver definido, syscallserá reiniciado depois que o manipulador de sinal terminar.

Referências

K_K
fonte
Obrigado por fazer um esforço para contribuir com o site, mas (1) se você deseja copiar material de outro site (palavra por palavra, letra por letra, incluindo erros de gramática e pontuação), você deve dizer que está fazendo então, muito mais claramente. Listar a fonte como uma "Referência", enquanto necessário, não é suficiente. A menos que você seja o autor do blog (K_K = sk?), Nesse caso, você não precisará vincular a ele - mas, se o fizer, deverá divulgar (por exemplo, dizer) que é seu. ... (continua)
G-Man diz 'Reinstate Monica'
(Continua)… (2) Sua fonte (o blog do qual você copiou) não é muito boa. Faz quatro anos desde que a pergunta foi feita; você não conseguiu encontrar uma referência melhor para copiar? (Se você é o autor original, desculpe.) Além dos erros gramaticais e de pontuação mencionados acima (e geralmente palavras desleixadas e má formatação), está errado. (2a) Ctrl + Z envia SIGTSTP, não SIGSTOP. (SIGTSTP, como SIGTERM, pode ser capturado; SIGSTOP, como SIGKILL, não pode.) ... (continua)
G-Man diz 'Reinstate Monica'
(Continua)… (2b) O shell não envia o sinal Ctrl + C. O shell não tem nenhum papel no envio de sinais (exceto quando o usuário usa o killcomando, que é um shell embutido). (2c) Embora ponto-e-vírgula após o fechamento }de uma função não sejam, estritamente falando, erros, eles são desnecessários e altamente heterodoxos. (3) Mesmo que tudo estivesse correto, não seria uma resposta muito boa para a pergunta. (3a) A questão, embora pouco clara, parece estar focada em como os atores (usuários e processos) iniciam (ou seja, enviam ) sinais. ... (continua)
G-Man diz 'Reinstate Monica'
(Continua) ... A resposta parece se concentrar nos sinais gerados pelo kernel (especificamente, sinais gerados pelo teclado) e como o processo do destinatário reage aos sinais. (3b) A pergunta parece estar no nível de “Alguém matou meu processo - quem o fez e como?” A resposta discute a API de tratamento de sinais, rotinas de kernel, depuração de kernel (Jprobe?), Rastreamentos de pilha de kernel e estruturas de dados do kernel. Na IMO, esse nível é inapropriadamente baixo - especialmente porque ele não fornece nenhuma referência em que um leitor possa aprender mais sobre esses trabalhos internos.
G-Man diz 'Reinstate Monica'
1
É o meu próprio blog .. meus próprios traços .. é isso que eu quero .. todo mundo deve conhecer um fluxo tão detalhado .. falar no ar não faz sentido .. mesmo se depois de violar as diretrizes desta comunidade, obtenha minha resposta removida através de canal .. esta é a resposta interna do kernel, não a gramática interna.
K_K