CSC 4103 – Programmation système

Portail informatique

Signaux

Shell et signaux

Lors d'un TP précédent, vous avez implémenté un shell permettant d'exécuter des commandes. Le but de cet exercice est d'étendre ce shell en y ajoutant la gestion de certains signaux.

Pour commencer, téléchargez l'archive shell.tgz, extrayez la et vérifiez que le code fourni fonctionne. Ce code est inspiré du corrigé du TP shell, avec quelques modifications pour simplifier le fonctionnement du présent TP.

Terminaison du shell avec exit

Pour commencer, on souhaite "nettoyer" les processus lorsque le shell se termine. Pour cela, ajoutez une fonction void kill_pending_processes() qui parcourt la liste des processus en cours d'exécution (stockée dans la liste struct process* processes définie au début du fichier) en envoie le signal SIGINT à chacun d'entre eux. Après avoir envoyé SIGINT à un processus, il est nécessaire d'attendre sa terminaison avec waitpid et d'appeler complete_process pour supprimer le processus de la liste des processus en cours d'exécution.

Modifiez la fonction main pour qu'elle appelle kill_pending_processes lorsque l'utilisateur lance la commande exit.

Terminaison du shell avec Ctrl-C

On souhaite que la fonction kill_pending_processes soit appelée lorsque l'utilisateur arrête le shell avec Ctrl-C.

Modifiez le programme pour qu'il exécute void handle_signal(int signo) lorsqu'il reçoit le signal SIGINT. Si cette fonction est appellée avec signo == SIGINT, elle doit appeler kill_pending_processes, puis terminer le processus courant en appelant exit().

Suppression des tâches trop longues (1/2)

Certaines commandes peuvent s'exécuter pendant très longtemps, et on souhaite maintenant tuer les processus qui s'exécutent pendant trop longtemps. Pour cela, on fixe une durée limite pour chaque commande (ici la limite est de 2.5 s) et on demande au système d'exploitation de générer un signal une fois passé ce délai. Lorsque le shell reçoit ce signal, il parcourt la liste des processus en cours d'exécution et envoie SIGINT à tous ceux ayant atteint la date de péremption.

Modifiez la structure struct process pour y ajouter un champs struct timespec start_time. Dans la fonction execute_command, ajoutez un appel à clock_gettime pour initialiser ce champs avec la date actuelle. Modifiez également la fonction complete_process pour qu'il affiche la durée qui s'est écoulée entre le début et la fin du processus:

$ ./mysh mysh $ ls Makefile mysh mysh_1 mysh_1.c mysh_2 mysh_2.c mysh_3 mysh_3.c mysh.c Completed in 3.071906 ms mysh $ sleep 1 Completed in 1003.060581 ms mysh $ sleep 1 & (in the background: process 19740) mysh $ ls Makefile mysh mysh_1 mysh_1.c mysh_2 mysh_2.c mysh_3 mysh_3.c mysh.c Completed in 4.101278 ms [19740] Completed - sleep 1 & (duration: 3452.300978 ms)
Lorsque vous appelez clock_gettime, utilisez le clockid_t CLOCK_MONOTONIC.
Pour calculer la différence entre deux struct timespec, vous pouvez utiliser la macro TIME_DIFF définie au début du programme.

Suppression des tâches trop longues (2/2)

Pour faire en sorte d'arrêter les processus trop longs, programmez une alarme dans 2.5 secondes (en utilisant setitimer) lors de l'exécution d'une commande. La fonction setitimergénèrera le signal SIGALARM lorsque le temps imparti sera écoulé.

Interceptez ce signal avec la foncion handle_signal. Lorsque ce signal est reçu, la fonction doit parcourir la liste des processus (stockée dans processes) et envoyer le signal SIGINT à tous ceux qui ont été démarré il y a plus de 2.5 secondes.