Skip to content

MOOC NSI - fondamentaux.

Transcription de la vidéo

4.2.2.7 : Signaux I

[00:00:01]

Nous allons voir maintenant un premier mécanisme de communication entre processus qui s'exécutent sur la même machine que sont les signaux système.

Un signal est un événement asynchrone destiné à un ou plusieurs processus. Vous vous rappelez peut-être, nous avons parlé d'interruption que nous avons définie comme un événement asynchrone destiné aux processeurs. Les deux notions sont donc analogues, mais les interruptions sont gérées au niveau matériel, alors que les signaux sont gérés au niveau logiciel. Le signal s'adresse donc aux processus, alors que l'interruption s'adresse aux processeurs. Certains signaux traduisent l'arrivée d'interruptions. Quand un signal est émis, il est émis à destination d'un processus qui lui réagit en exécutant un traitement particulier qui s'appelle le traitant. De manière schématique, si nous prenons un processus en train de s'exécuter, à l'arrivée d'un signal, par la flèche rose, il y a deux possibilités. Soit le processus ne réagit pas et continue avec son exécution, et ceci se passe si le signal est bloqué ou masqué, ou alors le processus est dérouté et exécute le traitant qui correspond à ce signal. Si le traitant est vide, alors, le processus ne fait pas grand chose et donc ignore le signal. Une fois que le traitant est fini, le processus retourne et reprend son exécution.

Tous les signaux sont associés à des traitants par défaut. Pour la plupart des signaux, ces traitants par défaut peuvent être redéfinis. Ce qui est important aussi de noter et on va voir ça un peu plus loin, c'est que les signaux sont des événements éphémères.

[00:02:13]

Ils ne sont pas mémorisés, donc on peut perdre la notion et l'information que des signaux ont été envoyés à certains processus.

Regardons maintenant quelques exemples de signaux très utilisés. De manière générale, les signaux sont identifiés par des numéros, mais vu que ces numéros dépendent du système d'exploitation, on utilise des noms symboliques pour les désigner. Un premier signal, c'est le signal SIGINT. Comme vous voyez, le nom est composé de SIG pour Signal et INT qui ici nous donne une indication sur la fonctionnalité par défaut qui est interrompre le processus et donc le traitant par défaut termine le processus destinataire. SIGINT est envoyé quand on utilise la combinaison de touches Ctrl-C.

Nous avons bien sûr la combinaison Ctrl-Z, qui envoie un signal de suspension au processus, c'est le signal SIGTSTP. SIGKILL, comme son nom l'indique, c'est un signal qui tue le processus, c'est un signal de terminaison. SIGSTOP c'est aussi un signal de suspension. SIGSEGV est un signal qui est déclenché quand il y a des accès mémoire illégaux. Pour ceux qui ont fait de la programmation C, ils vont certainement reconnaître des cas fréquents quand le programme s'arrête et dit qu'il y a une erreur de segmentation, c'est effectivement les cas où le programme fait des accès mémoire illégaux où il y a une interruption qui se traduit par ce signal là, qui est envoyé au processus et qui le termine.

[00:04:16]

Il y a également un signal SIGALRM qui permet de faire des alarmes temporelles. Il y a le signal SIGCHLD qui est envoyé par un fils à son père au moment où le fils se termine, le traitement par défaut est d'ignorer ce signal. Il y a deux signaux, SIGUSR1, SIGUSR2, qui peuvent être redéfinis pour les besoins de développement, mais qui, par défaut, terminent également les processus. Et enfin, un signal SIGCONT qui permet de reprendre l'exécution, donc de demander au processus de reprendre son exécution si, au préalable, il a été suspendu. Donc, pour tous les signaux listés ici, on peut redéfinir le traitant par défaut, sauf pour les signaux, SIGKILL et SIGSTOP qui sont utilisés par le système pour terminer les processus qui ont des comportements dangereux.

Qu'est ce qui se passe avec un signal quand il est envoyé , donc un processus où le système qui envoie un signal à destination d'un processus et ce processus doit le recevoir. Il se peut que le processus ne puisse pas recevoir le signal parce qu'il est occupé et il se peut également que le signal soit bloqué ou masqué. Une fois qu'il est démasqué ou débloqué, il va arriver au processus, il va être reçu et va être traité. Dans ce cas là, on va dire qu'il est pris en compte.

[00:06:01]

S'il n'est pas pris en compte, il attend à être pris en compte, on dit que le signal est pendant. Ce qui est important, c'est quand on a un signal d'un certain type. il se peut que le processus traite déjà un signal de ce type là. Si un deuxième signal arrive, il va attendre son tour et va être pendant. Mais s'il y a un deuxième, troisième ou encore plus de signaux qui arrivent, le système d'exploitation ne sait pas mémoriser l'information qui a plus de signaux que deux. Et donc, nous perdons les signaux qui arrivent ultérieurement.

Pour envoyer un signal, le système d'exploitation fournit la fonctionnalité kill. Dans Python elle est incluse dans le module os. kill prend deux paramètres. Le premier, c'est l'identifiant du processus destinataire et le deuxième, c'est l'identifiant du signal qu'on veut envoyer. La fonction kill est bien utilisée pour envoyer n'importe quel signal et pas uniquement pour tuer les processus. En jouant sur la valeur du premier argument, on peut faire des choses intéressantes. Le cas classique est quand on donne un identifiant d'un processus particulier. Dans ce cas là, le signal est envoyé à ce processus là. Si en premier argument, on utilise 0, le signal est envoyé à tous les processus qui sont du même groupe que le processus émetteur. Si on utilise moins 1 en premier argument, le signal est envoyé à tous les processus et enfin, si on utilise une valeur négative...

[00:07:53]

…alors, le signal va être envoyé à tous les processus qui sont dans le groupe qui a un numéro qui est égal à la valeur absolue du premier argument.

Bien sûr, on ne peut pas envoyer n'importe quel signal à n'importe quel processus. Il y a des restrictions qui sont liées aux droits et donc un utilisateur standard ne peut pas tuer typiquement, les processus prioritaires du système.

Illustrons l'utilisation du kill avec un exemple. Pour commencer, nous allons considérer un programme que vous connaissez déjà, où nous n'utilisons pas kill. Dans ce programme, il y a un premier processus qui est lancé, qui commence par exécuter main et il va créer un processus fils. Le fils va exécuter la partie if dans le bloc If-else, alors que le père va exécuter les instructions dans la partie else. Le fils, après avoir affiché son identifiant, rentre dans une boucle exécutée dix fois où il dort pendant une seconde et ensuite affiche le numéro d'itération. Le père, lui, fait un affichage et ensuite dort pendant trois secondes. À la fin, les deux processus affichent qu'ils ont terminé et se terminent. Si on regarde une exécution qui est montrée dans la capture à droite, nous voyons bien l'affichage du père, donc, qui commence par dire que son identifiant, c'est le 1387 et qui dit qu'effectivement, il ne va pas tuer son fils. Nous voyons qu'il se termine un peu plus loin.

[00:09:48]

où c’est écrit ‘j'ai terminé 1387”. Le fils s'exécute de manière indépendante. Donc, il y a ses affichages “mon pid est 1388” et ensuite il commence à afficher les différentes itérations. Il commence avant la terminaison du père et continue après ett nous voyons bien qu'il se termine avec l'affichage. “J'ai terminé 1388”. Utilisons maintenant kill. Donc, le fils fait dans ce qu'elle a exactement la même chose. Mais du côté du père après une période où il dort pendant trois secondes, il utilise la fonction kill a pour envoyer le signal SIGKILL à son fils. Donc, le numéro du fils, il l’a bien obtenu en retour de la fonction fork et donc qu'il l'a stocké dans newpid. Il envoie un signal qui doit résulter dans la terminaison du fils,ensuite, il attend que le fils soit effectivement terminé et il récupère les informations sur sa terminaison. Et nous avons le test qui va nous dire si oui ou non, le fils s'est terminé normalement. Si nous regardons la trace d'exécution. Nous commençons pareil avec l'affichage du père des deux premières lignes. Ensuite, il y a le fils qui commence son exécution. Il dit qu'il est le 1538. Ensuite, il commence à exécuter la boucle et afficher les numéros d'itération. Mais il est arrêté puisqu'il reçoit le signal envoyé par le père et donc là, c'est le père qui récupère l'information que “mon fils 1538 s'est terminée anormalement” et derrière lui se termine.

[00:11:57]

La fonction qu'il n'est pas uniquement disponible en interface programmatique, elle est également utilisée en ligne de commande. Alors ici, je vous montre le lancement d'un petit programme Python qui ne fait que boucler. Et à chaque itération, affiche points de suspension, numéro d'itération. Ici, je le lance en arrière plan. En arrière plan. ça veut dire que le processus qui va utiliser le programme est créé par le shel…mais le shell va être libre pour interagir avec l'utilisateur et ne va pas attendre la terminaison du processus pour pouvoir interagir. Donc, la première chose que le shell indique, c'est qu'il a bien lancé un processus qui est le 1889 et derrière nous voyons le premier affichage points de suspension zéro qui provient du programme boucle.py. Si j'utilise la fonctionnalité ps, je vois la liste de mes processus. Le premier, c'est le shell et le deuxième, c'est bien le 1889 qui est le programme Python qui tourne. Le programme continue donc je vois le deuxième affichage points de suspension 1 et donc là, je peux faire kill en donnant le numéro de processus 1889 si derrière, j'utilise de nouveau la commande ps, je vois bien que j'ai toujours mon processus shell...

[00:13:32]

Par contre, le processus qui correspond à l'exécution du programme boucle.py a été terminé.

Maintenant, regardons comment on envoie des signaux avec les raccourcis clavier que nous avons vu tout à l'heure dans la table. Cette fois ci, et je vais lancer mon petit programme boucle.py en premier plan, c'est à dire que le terminal est bloqué puisque le shell attend la terminaison de ce programme pour recommencer à interagir avec l'utilisateur. Alors, j'ai le premier affichage point point point zéro et là, je fais Control C. Cela se traduit donc par l'envoi d'un signal SIGINT qui termine le processus. Si je relance boucle.py , pareil, en premier plan, je peux utiliser l'autre raccourci, le Z, et là, le shell m'indique qu'effectivement, le processus qui exécute boucle.py a été arrêté, pas tué, mais arrêté. Donc, je peux utiliser des commandes du shell pour le relancer. Et typiquement, une telle commande c’est fg pour foreground, ça veut dire “relance en premier plan”. Et donc si je tape cette commande. boucle.py reprend et je vois la suite des affichages.

Pour résumer, dans cette vidéo, nous avons fait connaissance avec les signaux. Les signaux sont des événements asynchrones à destination des processus. Les processus peuvent donc envoyer des signaux à d'autres processus qui les reçoivent et qui les traitent. Les signaux sont donc un moyen basique de communication entre processus qui s'exécutent sur la même machine.