¿Cómo Linux "mata" un proceso?


91

A menudo me desconcierta que, aunque he trabajado profesionalmente con computadoras durante varias décadas y Linux durante una década, en realidad trato la mayor parte de la funcionalidad del sistema operativo como una caja negra, no muy diferente a la magia.

Hoy pensé en el killcomando, y aunque lo uso varias veces al día (tanto en su "normal" como en su -9sabor) debo admitir que no tengo ni idea de cómo funciona detrás de escena.

Desde mi punto de vista, si un proceso en ejecución está "colgado", llamo killa su PID, y de repente ya no se está ejecutando. ¡Magia!

¿Qué pasa realmente allí? Las páginas de manual hablan de "señales" pero seguramente eso es solo una abstracción. Enviar kill -9a un proceso no requiere la cooperación del proceso (como manejar una señal), simplemente lo elimina.

  • ¿Cómo impide Linux que el proceso continúe ocupando tiempo de CPU?
  • ¿Se elimina de la programación?
  • ¿Desconecta el proceso de sus identificadores de archivo abiertos?
  • ¿Cómo se libera el proceso 'memoria virtual?
  • ¿Hay algo así como una tabla global en la memoria, donde Linux mantiene referencias a todos los recursos ocupados por un proceso, y cuando "mato" un proceso, Linux simplemente pasa por esa tabla y libera los recursos uno por uno?

¡Realmente me gustaría saber todo eso!



Respuestas:


72

Enviar kill -9 a un proceso no requiere la cooperación del proceso (como manejar una señal), solo lo elimina.

Supone que, debido a que algunas señales pueden captarse e ignorarse, todas implican cooperación. Pero según man 2 signal"las señales SIGKILL y SIGSTOP no pueden ser captadas o ignoradas". SIGTERM puede ser atrapado, por lo que el simple killno siempre es efectivo, generalmente esto significa que algo en el controlador del proceso ha salido mal. 1

Si un proceso no (o no puede) definir un controlador para una señal dada, el kernel realiza una acción predeterminada. En el caso de SIGTERM y SIGKILL, esto es para terminar el proceso (a menos que su PID sea 1; el núcleo no terminará init) 2, lo que significa que sus identificadores de archivo están cerrados, su memoria regresó al grupo de sistemas, su padre recibe SIGCHILD, su huérfano los hijos son heredados por init, etc., como si hubiera llamado exit(ver man 2 exit). El proceso ya no existe, a menos que termine como un zombi, en cuyo caso todavía aparece en la tabla de proceso del núcleo con alguna información; eso sucede cuando su padre nowaity lidiar con esta información correctamente. Sin embargo, los procesos zombie ya no tienen memoria asignada y, por lo tanto, no pueden continuar ejecutándose.

¿Hay algo así como una tabla global en la memoria en la que Linux mantiene referencias a todos los recursos ocupados por un proceso y cuando "mato" un proceso, Linux simplemente pasa por esa tabla y libera los recursos uno por uno?

Creo que eso es lo suficientemente preciso. Se realiza un seguimiento de la memoria física por página (una página generalmente equivale a un fragmento de 4 KB) y esas páginas se toman y se devuelven a un grupo global. Es un poco más complicado porque algunas páginas liberadas se almacenan en caché en caso de que los datos que contienen se vuelvan a requerir (es decir, datos que se leyeron de un archivo aún existente).

Las páginas de manual hablan de "señales", pero seguramente eso es solo una abstracción.

Claro, todas las señales son una abstracción. Son conceptuales, al igual que los "procesos". Estoy jugando semántica un poco, pero si te refieres a SIGKILL es cualitativamente diferente de SIGTERM, entonces sí y no. Sí en el sentido de que no se puede atrapar, pero no en el sentido de que ambas son señales. Por analogía, una manzana no es una naranja, pero las manzanas y las naranjas son, según una definición preconcebida, ambas frutas. SIGKILL parece más abstracto ya que no puedes atraparlo, pero sigue siendo una señal. Aquí hay un ejemplo de manejo de SIGTERM, estoy seguro de que has visto esto 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 proceso dormirá para siempre. Puede ejecutarlo en una terminal y enviarlo con SIGTERM kill. Escupe cosas como:

Received 15 from pid 25331, uid 1066.

1066 es mi UID. El PID será el del shell desde el cual killse ejecuta, o el PID de kill si lo bifurcas ( kill 25309 & echo $?).

Nuevamente, no tiene sentido establecer un controlador para SIGKILL porque no funcionará. 3 Si yo kill -9 25309el proceso terminará. Pero eso sigue siendo una señal; el kernel tiene la información sobre quién envió la señal , qué tipo de señal es, etc.


1. Si no ha mirado la lista de posibles señales , vea kill -l.

2. Otra excepción, como menciona Tim Post a continuación, se aplica a procesos en suspensión ininterrumpida . No se pueden despertar hasta que se resuelva el problema subyacente, por lo que TODAS las señales (incluida SIGKILL) se difieren por la duración. Sin embargo, un proceso no puede crear esa situación a propósito.

3. Esto no significa que usar kill -9es algo mejor que hacer en la práctica. Mi controlador de ejemplo es malo en el sentido de que no conduce a exit(). El verdadero propósito de un controlador SIGTERM es darle al proceso la oportunidad de hacer cosas como limpiar archivos temporales y luego salir voluntariamente. Si lo usa kill -9, no tiene esta oportunidad, así que solo haga eso si la parte de "salir voluntariamente" parece haber fallado.


Ok, pero con qué mata el proceso -9porque ese es el verdadero problema de cómo quién desea que este muera. ;)
Kiwy

@ Kiwy: El núcleo. IPC incluyendo señales pasan a través de él; El núcleo implementa las acciones predeterminadas.
Ricitos

12
Vale la pena mencionar que la suspensión del disco (D) se adelanta a todas las señales, mientras el proceso está en ese estado. Por lo tanto, intentar kill -9ciertos procesos vinculados de E / S no funcionará, al menos no de inmediato.
Tim Post

77
Añadiría que, dado que kill -9no se puede detectar un proceso, un proceso que lo recibe no puede realizar ninguna limpieza (por ejemplo, eliminar archivos temporales, liberar memoria compartida, etc.) antes de que salga. Por lo tanto, use kill -9(aka kill -kill) solo como último recurso. Comience con ay kill -hup/ o kill -termprimero y luego úselo kill -killcomo el golpe final.
JRFerguson

"El proceso ya no existe, a menos que termine como un zombi, en cuyo caso todavía aparece en la tabla de procesos del núcleo con alguna información" en realidad, todos los procesos entran en estado zombi cuando mueren, y el zombie desaparecerá cuando el padre espera con impaciencia al niño, normalmente eso sucede demasiado rápido para que lo veas
listo

3

Cada proceso se ejecuta durante el tiempo programado y luego es interrumpido por el temporizador de hardware, para proporcionar su núcleo de CPU para otras tareas. Es por eso que es posible tener muchos más procesos que núcleos de CPU, o incluso ejecutar todo el sistema operativo con muchos procesos en una CPU de un solo núcleo.

Después de que se interrumpe el proceso, el control vuelve al código del núcleo. Ese código puede tomar la decisión de no reanudar la ejecución del proceso interrumpido, sin ninguna cooperación del lado del proceso. kill -9 puede terminar la ejecución en cualquier línea de su programa.


0

Aquí hay una descripción idealizada de cómo funciona matar un proceso. En la práctica, cualquier variante de Unix tendrá muchas complicaciones y optimizaciones adicionales.

El núcleo tiene una estructura de datos para cada proceso que almacena información sobre qué memoria está mapeando, qué hilos tiene y cuándo están programados, qué archivos tiene abiertos, etc. Si el núcleo decide matar un proceso, hace una nota en La estructura de datos del proceso (y tal vez en la estructura de datos de cada uno de los hilos) que el proceso debe ser eliminado.

Si uno de los subprocesos del proceso está actualmente programado en otra CPU, el núcleo podría desencadenar una interrupción en esa otra CPU para que ese subproceso deje de ejecutarse más rápidamente.

Cuando el planificador advierte que un hilo está en un proceso que debe ser eliminado, ya no lo programará.

Cuando ya no se programan ninguno de los subprocesos del proceso, el núcleo comienza a liberar los recursos del proceso (memoria, descriptores de archivo, ...). Cada vez que el núcleo libera un recurso, verifica si su propietario todavía tiene recursos activos. Una vez que el proceso no tiene más recursos en vivo (mapeo de memoria, descriptor de archivo abierto, ...), la estructura de datos para el proceso en sí se puede liberar y la entrada correspondiente se puede eliminar de la tabla de procesos.

Algunos recursos pueden liberarse inmediatamente (por ejemplo, desasignar memoria que no está siendo utilizada por una operación de E / S). Otros recursos deben esperar, por ejemplo, los datos que describen una operación de E / S no se pueden liberar mientras la operación de E / S está en progreso (mientras un DMA en progreso, la memoria a la que está accediendo está en uso y la cancelación del DMA requiere en contacto con el periférico). El conductor de dicho recurso recibe una notificación y puede intentar acelerar la cancelación; una vez que la operación ya no esté en progreso, el controlador completará la liberación de ese recurso.

(La entrada en la tabla de proceso es en realidad un recurso que pertenece al proceso padre, que se libera cuando el proceso muere y el padre reconoce el evento ).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.