web-dev-qa-db-fra.com

Comment Linux "tue" un processus?

Cela me déconcerte souvent que, même si je travaille professionnellement avec des ordinateurs depuis plusieurs décennies et Linux depuis une décennie, je traite en fait la plupart des fonctionnalités du système d'exploitation comme une boîte noire, un peu comme la magie.

Aujourd'hui, j'ai pensé à la commande kill, et pendant que je l'utilise plusieurs fois par jour (à la fois dans son "normal" et -9 saveur) Je dois admettre que je n'ai absolument aucune idée de comment cela fonctionne dans les coulisses.

De mon point de vue, si un processus en cours est "bloqué", j'appelle kill sur son PID, puis il ne fonctionne plus soudainement. La magie!

Que se passe-t-il vraiment là-bas? Les pages de manuel parlent de "signaux" mais ce n'est sûrement qu'une abstraction. Envoi en cours kill -9 à un processus ne nécessite pas la coopération du processus (comme la gestion d'un signal), il le tue.

  • Comment Linux empêche-t-il le processus de continuer à prendre du temps CPU?
  • Est-il supprimé de la planification?
  • Déconnecte-t-il le processus de ses descripteurs de fichiers ouverts?
  • Comment la mémoire virtuelle du processus est-elle libérée?
  • Existe-t-il quelque chose comme une table globale en mémoire, où Linux conserve des références à toutes les ressources occupées par un processus, et quand je "tue" un processus, Linux passe simplement par cette table et libère les ressources une par une?

J'aimerais vraiment savoir tout ça!

91
seratoninant

L'envoi de kill -9 à un processus ne nécessite pas la coopération du processus (comme la gestion d'un signal), il le tue simplement.

Vous présumez que parce que certains signaux peuvent être captés et ignorés, ils impliquent tous une coopération. Mais selon man 2 signal, "Les signaux SIGKILL et SIGSTOP ne peuvent pas être capturés ou ignorés". SIGTERM peut être intercepté, c'est pourquoi plain kill n'est pas toujours efficace - cela signifie généralement que quelque chose dans le gestionnaire du processus a mal tourné.1

Si un processus ne définit pas (ou ne peut pas) définir un gestionnaire pour un signal donné, le noyau effectue une action par défaut. Dans le cas de SIGTERM et SIGKILL, ceci met fin au processus (sauf si son PID est 1; le noyau ne se terminera pas init)2 ce qui signifie que ses descripteurs de fichiers sont fermés, sa mémoire retournée au pool système, son parent reçoit SIGCHILD, ses enfants orphelins sont hérités par init, etc., tout comme s'il avait appelé exit (voir man 2 exit). Le processus n'existe plus - à moins qu'il ne devienne un zombie, auquel cas il est toujours répertorié dans la table des processus du noyau avec quelques informations; cela se produit lorsque son parent ne fait pas wait et traite correctement ces informations. Cependant, les processus zombies n'ont plus de mémoire allouée et ne peuvent donc pas continuer à s'exécuter.

Existe-t-il quelque chose comme une table globale en mémoire où Linux conserve des références à toutes les ressources occupées par un processus et quand je "tue" un processus Linux passe simplement par cette table et libère les ressources une par une?

Je pense que c'est assez précis. La mémoire physique est suivie par page (une page équivaut généralement à un bloc de 4 Ko) et ces pages sont extraites et retournées à un pool global. C'est un peu plus compliqué dans la mesure où certaines pages libérées sont mises en cache au cas où les données qu'elles contiennent seraient à nouveau nécessaires (c'est-à-dire des données qui ont été lues à partir d'un fichier encore existant).

Les pages de manuel parlent de "signaux" mais ce n'est sûrement qu'une abstraction.

Bien sûr, tous les signaux sont une abstraction. Ils sont conceptuels, tout comme les "processus". Je joue un peu la sémantique, mais si vous voulez dire que SIGKILL est qualitativement différent de SIGTERM, alors oui et non. Oui dans le sens où il ne peut pas être capturé, mais non dans le sens où ce sont tous les deux des signaux. Par analogie, un Apple n'est pas une orange mais les pommes et les oranges sont, selon une définition préconçue, les deux fruits. SIGKILL semble plus abstrait puisque vous ne pouvez pas l'attraper, mais c'est quand même un signal. Voici un exemple de gestion de SIGTERM, je suis sûr que vous les avez déjà vu:

#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;
}

Ce processus dormira pour toujours. Vous pouvez l'exécuter dans un terminal et l'envoyer SIGTERM avec kill. Il crache des trucs comme:

Received 15 from pid 25331, uid 1066.

1066 est mon UID. Le PID sera celui du Shell à partir duquel kill sera exécuté, ou le PID de kill si vous le forkez (kill 25309 & echo $?).

Encore une fois, il est inutile de définir un gestionnaire pour SIGKILL car cela ne fonctionnera pas.3 Si je kill -9 25309 Le processus se terminera. Mais c'est toujours un signal; le noyau a les informations sur qui a envoyé le signal , quel type de signal c'est le cas, etc.


1. Si vous n'avez pas regardé la liste des signaux possibles , voir kill -l.

2. Une autre exception, comme Tim Post le mentionne ci-dessous, s'applique aux processus en sommeil sans interruption . Ceux-ci ne peuvent pas être réveillés jusqu'à ce que le problème sous-jacent soit résolu, de sorte que TOUS les signaux (y compris SIGKILL) soient différés pour la durée. Cependant, un processus ne peut pas créer cette situation exprès.

3. Cela ne signifie pas que l'utilisation de kill -9 Est une meilleure chose à faire dans la pratique. Mon exemple de gestionnaire est mauvais dans le sens où il ne conduit pas à exit(). Le véritable objectif d'un gestionnaire SIGTERM est de donner au processus une chance de faire des choses comme nettoyer des fichiers temporaires, puis de quitter volontairement. Si vous utilisez kill -9, Cela n'a pas cette chance, alors ne faites cela que si la partie "quitter volontairement" semble avoir échoué.

72
goldilocks

Chaque processus s'exécute pendant une durée planifiée, puis est interrompu par un minuteur matériel, pour donner son cœur de processeur pour d'autres tâches. C'est pourquoi il est possible d'avoir beaucoup plus de processus qu'il n'y a de cœurs de processeur, ou même d'exécuter tous les systèmes d'exploitation avec beaucoup de processus sur un seul processeur.

Une fois le processus interrompu, le contrôle retourne au code du noyau. Ce code peut alors prendre la décision de ne pas reprendre l'exécution du processus interrompu, sans aucune coopération du côté du processus. kill -9 peut finir par être exécuté sur n'importe quelle ligne de votre programme.

3
h22

Voici une description idéale du fonctionnement d'un processus de destruction. En pratique, toute variante Unix aura de nombreuses complications et optimisations supplémentaires.

Le noyau a une structure de données pour chaque processus qui stocke des informations sur la mémoire qu'il mappe, quels threads il a et quand ils sont planifiés, quels fichiers il a ouverts, etc. Si le noyau décide de tuer un processus, il fait une note dans la structure de données du processus (et peut-être dans la structure de données de chacun des threads) que le processus doit être tué.

Si l'un des threads du processus est actuellement planifié sur un autre CPU, le noyau peut déclencher une interruption sur cet autre CPU pour que ce thread cesse de s'exécuter plus rapidement.

Lorsque le planificateur remarque qu'un thread est dans un processus qui doit être supprimé, il ne le planifie plus.

Lorsqu'aucun des threads du processus n'est plus planifié, le noyau commence à libérer les ressources du processus (mémoire, descripteurs de fichiers,…). Chaque fois que le noyau libère une ressource, il vérifie si son propriétaire possède toujours des ressources actives. Une fois que le processus n'a plus de ressource active (mappage de mémoire, descripteur de fichier ouvert,…), la structure de données du processus lui-même peut être libérée et l'entrée correspondante peut être supprimée de la table de processus.

Certaines ressources peuvent être libérées immédiatement (par exemple, désallouer de la mémoire qui n'est pas utilisée par une opération d'E/S). Les autres ressources doivent attendre, par exemple les données qui décrivent une opération d'E/S ne peuvent pas être libérées pendant que l'opération d'E/S est en cours (tandis qu'un DMA en cours, la mémoire à laquelle il accède est en cours d'utilisation et l'annulation de DMA nécessite de contacter le périphérique). Le pilote d'une telle ressource est averti et peut tenter de hâter l'annulation; une fois l'opération n'est plus en cours, le pilote achèvera la libération de cette ressource.

(L'entrée dans la table de processus est en fait une ressource qui appartient au processus parent, qui est libérée lorsque le processus meurt et le parent reconnaît l'événement .)