Como o Linux "mata" um processo?


91

Muitas vezes me confunde o fato de que, apesar de trabalhar profissionalmente com computadores há várias décadas e com o Linux há uma década, na verdade trato a maior parte da funcionalidade do sistema operacional como uma caixa preta, não muito diferente da mágica.

Hoje pensei no killcomando e, embora o use várias vezes por dia (tanto em seu "normal" quanto em seu -9sabor), devo admitir que não tenho absolutamente nenhuma idéia de como ele funciona nos bastidores.

Do meu ponto de vista, se um processo em execução estiver "travado", chamo killseu PID e, de repente, ele não está mais em execução. Magia!

O que realmente acontece lá? As páginas de manual falam sobre "sinais", mas certamente isso é apenas uma abstração. Enviar kill -9para um processo não requer a cooperação do processo (como manipular um sinal), apenas o mata.

  • Como o Linux impede o processo de continuar consumindo tempo de CPU?
  • Ele foi removido da programação?
  • Desconecta o processo de seus identificadores de arquivos abertos?
  • Como é liberada a memória virtual do processo?
  • Existe algo como uma tabela global na memória, em que o Linux mantém referências a todos os recursos ocupados por um processo, e quando eu "mato" um processo, o Linux simplesmente passa por essa tabela e libera os recursos um por um?

Eu realmente gostaria de saber tudo isso!



Respostas:


72

Enviar kill -9 para um processo não requer a cooperação do processo (como manipular um sinal), apenas o mata.

Você está presumindo que, porque alguns sinais podem ser capturados e ignorados, todos envolvem cooperação. Mas, de acordo com man 2 signal"os sinais SIGKILL e SIGSTOP não podem ser capturados ou ignorados". O SIGTERM pode ser capturado, e é por isso que a planície killnem sempre é eficaz - geralmente isso significa que algo no manipulador do processo deu errado. 1 1

Se um processo não define (ou não pode) definir um manipulador para um determinado sinal, o kernel executa uma ação padrão. No caso de SIGTERM e SIGKILL, é para finalizar o processo (a menos que seu PID seja 1; o kernel não será finalizado init) 2 significa que seus identificadores de arquivo estão fechados, sua memória retornada ao pool do sistema, seu pai recebe SIGCHILD, seu órfão os filhos são herdados pelo init, etc., como se tivesse chamado exit(veja man 2 exit). O processo não existe mais - a menos que acabe como um zumbi; nesse caso, ele ainda está listado na tabela de processos do kernel com algumas informações; isso acontece quando seu pai nãowaite lide com essas informações corretamente. No entanto, os processos zumbis não têm mais memória alocada para eles e, portanto, não podem continuar sendo executados.

Existe algo como uma tabela global na memória em que o Linux mantém referências a todos os recursos ocupados por um processo e quando eu "mato" um processo, o Linux simplesmente passa por essa tabela e libera os recursos um por um?

Eu acho que é preciso o suficiente. A memória física é rastreada por página (uma página geralmente igual a um pedaço de 4 KB) e essas páginas são obtidas e retornadas a um pool global. É um pouco mais complicado, pois algumas páginas liberadas são armazenadas em cache, caso os dados contidos sejam necessários novamente (ou seja, dados que foram lidos em um arquivo ainda existente).

As páginas de manual falam sobre "sinais", mas certamente isso é apenas uma abstração.

Claro, todos os sinais são uma abstração. Eles são conceituais, assim como "processos". Estou jogando semântica um pouco, mas se você quer dizer que o SIGKILL é qualitativamente diferente do SIGTERM, sim e não. Sim no sentido de que não pode ser capturado, mas não no sentido de que ambos são sinais. Por analogia, uma maçã não é laranja, mas maçãs e laranjas são, de acordo com uma definição preconcebida, ambas frutas. SIGKILL parece mais abstrato, pois você não pode pegá-lo, mas ainda é um sinal. Aqui está um exemplo de manipulação do SIGTERM, tenho certeza que você já viu isso antes:

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

void sighandler (int signum, siginfo_t *info, void *context) {
    fprintf (
        stderr,
        "Received %d from pid %u, uid %u.\n",
        info->si_signo,
        info->si_pid,
        info->si_uid
    );
}

int main (void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = sighandler;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGTERM, &sa, NULL);
    while (1) sleep(10);
    return 0;
}

Este processo irá dormir para sempre. Você pode executá-lo em um terminal e enviá-lo com o SIGTERM kill. Ele cospe coisas como:

Received 15 from pid 25331, uid 1066.

1066 é o meu UID. O PID será aquele do shell a partir do qual killé executado ou o PID de kill se você o bifurcar ( kill 25309 & echo $?).

Novamente, não faz sentido definir um manipulador para o SIGKILL porque ele não funcionará. 3 Se eu, kill -9 25309o processo será encerrado. Mas isso ainda é um sinal; o kernel possui informações sobre quem enviou o sinal , que tipo de sinal é etc.


1. Se você não examinou a lista de possíveis sinais , consulte kill -l.

2. Outra exceção, como Tim Post menciona abaixo, aplica-se a processos em suspensão ininterrupta . Eles não podem ser acordados até que o problema subjacente seja resolvido e, portanto, TODOS os sinais (incluindo o SIGKILL) foram adiados pela duração. Um processo não pode criar essa situação de propósito, no entanto.

3. Isso não significa que usar kill -9é a melhor coisa a se fazer na prática. Meu manipulador de exemplo é ruim no sentido em que não leva exit(). O verdadeiro objetivo de um manipulador SIGTERM é dar ao processo a chance de fazer coisas como limpar arquivos temporários e sair voluntariamente. Se você usar kill -9, ela não terá essa chance, então faça isso apenas se a parte "sair voluntariamente" parecer ter falhado.


Ok, mas com o que acabar com o processo, -9porque esse é o verdadeiro problema, como quem deseja que este morra! ;)
Kiwy

@ Kiwy: O kernel. IPC incluindo sinais passam por ele; o kernel implementa as ações padrão.
Goldilocks

12
Pode valer a pena mencionar que o sono em disco (D) antecipa todos os sinais, enquanto o processo estiver nesse estado. Portanto, tentar kill -9certos processos vinculados de E / S não funcionará, pelo menos não imediatamente.
Tim Post

7
Eu acrescentaria que, como um kill -9não pode ser capturado, um processo que o recebe não pode executar nenhuma limpeza (por exemplo, remover arquivos temporários, liberar memória compartilhada etc.) antes de sair. Portanto, use kill -9(aka kill -kill) apenas como último recurso. Comece com um kill -hupe / ou kill -termprimeiro e depois use kill -killcomo golpe final.
precisa saber é o seguinte

"O processo não existe mais - a menos que acabe como um zumbi; nesse caso, ele ainda está listado na tabela de processos do kernel com algumas informações" na verdade, todos os processos entram no estado de zumbi quando morrem, e o zumbi desaparece quando o pai não waitpid sobre a criança, normalmente isso acontece muito rápido para que você possa ver isso acontecer
inteligente

3

Cada processo é executado no horário agendado e, em seguida, é interrompido pelo timer do hardware, para fornecer o núcleo da CPU para outras tarefas. É por isso que é possível ter muito mais processos do que os núcleos da CPU ou até executar todo o sistema operacional com muitos processos em uma única CPU.

Depois que o processo é interrompido, o controle retorna ao código do kernel. Esse código pode então tomar uma decisão de não retomar a execução do processo interrompido, sem qualquer cooperação do lado do processo. O kill -9 pode acabar sendo executado em qualquer linha do seu programa.


0

Aqui está uma descrição idealizada de como a morte de um processo funciona. Na prática, qualquer variante Unix terá muitas complicações e otimizações adicionais.

O kernel possui uma estrutura de dados para cada processo que armazena informações sobre qual memória está mapeando, quais threads possui e quando são agendadas, quais arquivos ele abre etc. Se o kernel decide interromper um processo, faz uma anotação em a estrutura de dados do processo (e talvez na estrutura de dados de cada um dos encadeamentos) em que o processo deve ser morto.

Se um dos threads do processo estiver atualmente agendado em outra CPU, o kernel poderá acionar uma interrupção nessa outra CPU para fazer com que o thread pare de executar mais rapidamente.

Quando o planejador percebe que um encadeamento está em um processo que deve ser eliminado, não o agendará mais.

Quando nenhum dos threads do processo é agendado, o kernel começa a liberar os recursos do processo (memória, descritores de arquivo, ...). Cada vez que o kernel libera um recurso, ele verifica se o proprietário ainda possui recursos ativos. Quando o processo não tiver mais recursos ativos (mapeamento de memória, descritor de arquivo aberto,…), a estrutura de dados do próprio processo poderá ser liberada e a entrada correspondente poderá ser removida da tabela de processos.

Alguns recursos podem ser liberados imediatamente (por exemplo, desalocando a memória que não está sendo usada por uma operação de E / S). Outros recursos devem aguardar, por exemplo, os dados que descrevem uma operação de E / S não podem ser liberados enquanto a operação de E / S está em andamento (enquanto um DMA está em andamento, a memória que está acessando está em uso e o cancelamento do DMA exige contato com o periférico). O driver para esse recurso é notificado e pode tentar apressar o cancelamento; quando a operação não estiver mais em andamento, o driver concluirá a liberação desse recurso.

(A entrada na tabela de processos é na verdade um recurso que pertence ao processo pai, que é liberado quando o processo morre e o pai reconhece o evento .)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.