CSC 4103 – Programmation système

Portail informatique

Créez le programme fork_chain.c. Ce programme créé une "chaîne" de 4 processus: le processus P1 créé P2, P2 crée P3, et P3 crée P4.

Chaque processus, en plus d'engendrer un processus fils, affiche son PID et son PPID. Le dernier processus de la chaîne exécute la commande ps f grâce à la fonction system.

$ ./fork_chain [12255] I'm P1. My PPID=27799 [12256] I'm P2. My PPID=12255 $ [12258] I'm P4. My PPID=12257 [12257] I'm P3. My PPID=12256 PID TTY STAT TIME COMMAND 27799 pts/5 Ss+ 0:00 bash 24866 pts/0 Ss+ 0:00 bash 12226 pts/2 Ss+ 0:00 bash 12258 pts/5 S 0:00 ./fork_chain 12259 pts/5 S 0:00 \_ sh -c ps f 12260 pts/5 R 0:00 \_ ps f

La commande ps f lancée par P4 affiche-t-elle tous les processus (P1, P2, P3, P4) ? Non ?

Modifiez le programme pour faire "travailler" les processus P2 et P3 avant leur appel à fork. On peut utiliser sleep(1) pour "simuler" un travail d'une seconde.

Observez l'affichage produit par le programme

$ ./fork_chain_v2 [12366] I'm P1. My PPID=27799 $ [12367] I'm P2. My PPID=1 [12368] I'm P3. My PPID=1 [12369] I'm P4. My PPID=12368 PID TTY STAT TIME COMMAND 24866 pts/0 Ss+ 0:00 bash 12226 pts/2 Ss+ 0:00 bash 12369 pts/5 S 0:00 ./fork_chain_v2 12370 pts/5 S 0:00 \_ sh -c ps f 12371 pts/5 R 0:00 \_ ps f On remarque que P2 et P3 ont pour PPID 1. Leur père est donc "mort".

Modifiez le programme afin que chaque processus attende la terminaison de ses fils avant de se terminer.

$ ./fork_chain_v3 [12599] I'm P1. My PPID=27799 [12600] I'm P2. My PPID=12599 [12601] I'm P3. My PPID=12600 [12602] I'm P4. My PPID=12601 PID TTY STAT TIME COMMAND 27799 pts/5 Ss 0:00 bash 12599 pts/5 S+ 0:00 \_ ./fork_chain_v3 12600 pts/5 S+ 0:00 \_ ./fork_chain_v3 12601 pts/5 S+ 0:00 \_ ./fork_chain_v3 12602 pts/5 S+ 0:00 \_ ./fork_chain_v3 12603 pts/5 S+ 0:00 \_ sh -c ps f 12604 pts/5 R+ 0:00 \_ ps f 24866 pts/0 Ss+ 0:00 bash 12226 pts/2 Ss+ 0:00 bash [P3] P4 est mort. Je peux m'arrêter [P2] P3 est mort. Je peux m'arrêter [P1] P2 est mort. Je peux m'arrêter $

La solution actuelle fonctionne, mais elle engendre des processus inutilement: la fonction system crée un processus shell qui crée un processus exécutant la commande ps f.

Utilisez la fonction execlp à la place de la fonction system.

Avant de commencer cet exercice, téléchargez l'archive TP7.tgz et extrayez la.

L'objectif de cet exercice est de continuer à vous familiariser avec la gestion de processus, tout en découvrant comment est implémenté le shell que vous utilisez tous les jours.

Le programme mysh.c est une base permettant d'implémenter un shell rudimentaire. Une boucle demande à l'utilisateur de saisir une commande, la chaîne de caractère est transformée en un tableau de mot (par la fonction extract_command), et le tableau est ensuite passé à la fonction execute_command chargée d'exécuter la commande.

Complétez la fonction execute_command pour que les commandes saisies par l'utilisateur soient exécutées. Pour cela, utilisez la fonction system.

Pour ceux qui travaillent sur leur machine personnelle, vous aurez sans doute besoin d'installer le paquet libreadline-dev. $ ./mysh mysh $ ls Makefile Makefile~ mysh mysh.c mysh $ ps f PID TTY STAT TIME COMMAND 27799 pts/5 Ss 0:01 bash 18942 pts/5 S+ 0:00 \_ ./mysh 18947 pts/5 S+ 0:00 \_ sh -c ps f 18950 pts/5 R+ 0:00 \_ ps f 24866 pts/0 Ss+ 0:00 bash 12226 pts/2 Ss+ 0:00 bash mysh $

En tapant la commande ps f, on voit que mysh crée un processus sh qui crée un processus ps. Quel gaspillage des ressources de l'ordinateur !

Nous allons plutôt créer un processus fils qui va utiliser la fonction execvp pour exécuter la commande. Il est également nécessaire que le processus père attente la terminaison du processus fils qu'il vient de créer avant de poursuivre son exécution.

Pour être certain d'attendre le bon processus, utilisez la fonction waitpid plutôt que wait. $ ./mysh mysh $ ls Makefile Makefile~ mysh mysh.c mysh.c~ mysh_final.c mysh_v1.c mysh_v1.c~ mysh_v2.c mysh_v2.c~ mysh $ ps f PID TTY STAT TIME COMMAND 18968 pts/3 Ss 0:00 bash 19819 pts/3 S+ 0:00 \_ ./mysh 19822 pts/3 R+ 0:00 \_ ps f 24866 pts/0 Ss+ 0:00 bash 12226 pts/2 Ss+ 0:00 bash mysh $

Au fait, êtes vous sûr que le programme travaille "proprement" ? Utilisez valgrind pour détecter les éventuels problèmes mémoire et corrigez les.

$ valgrind --leak-check=full ./mysh ==19891== Memcheck, a memory error detector [...] mysh $ ls Makefile Makefile~ mysh mysh.c mysh.c~ mysh_final.c mysh_v1.c mysh_v1.c~ mysh_v2.c mysh_v2.c~ mysh $ exit ==19893== ==19893== HEAP SUMMARY: ==19893== in use at exit: 109,613 bytes in 189 blocks ==19893== total heap usage: 305 allocs, 116 frees, 128,673 bytes allocated ==19893== ==19893== 3 bytes in 1 blocks are definitely lost in loss record 1 of 45 ==19893== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19893== by 0x40096E: extract_command (mysh.c:21) ==19893== by 0x400B5D: main (mysh.c:76) ==19893== ==19893== 5 bytes in 1 blocks are possibly lost in loss record 2 of 45 ==19893== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19893== by 0x4E6EC28: xmalloc (in /lib/x86_64-linux-gnu/libreadline.so.6.3) ==19893== by 0x4E4E105: readline_internal_teardown (in /lib/x86_64-linux-gnu/libreadline.so.6.3) ==19893== by 0x4E4F171: readline (in /lib/x86_64-linux-gnu/libreadline.so.6.3) ==19893== by 0x400B4D: main (mysh.c:73) ==19893== ==19893== 16 bytes in 1 blocks are definitely lost in loss record 13 of 45 ==19893== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19893== by 0x4009FE: extract_command (mysh.c:34) ==19893== by 0x400B5D: main (mysh.c:76) ==19893== ==19893== 45 (24 direct, 21 indirect) bytes in 1 blocks are definitely lost in loss record 16 of 45 ==19893== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19893== by 0x40093C: extract_command (mysh.c:18) ==19893== by 0x400B5D: main (mysh.c:76) ==19893== ==19893== LEAK SUMMARY: ==19893== definitely lost: 43 bytes in 3 blocks ==19893== indirectly lost: 21 bytes in 2 blocks ==19893== possibly lost: 5 bytes in 1 blocks ==19893== still reachable: 109,544 bytes in 183 blocks ==19893== suppressed: 0 bytes in 0 blocks ==19893== Reachable blocks (those to which a pointer was found) are not shown. ==19893== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==19893== ==19893== For counts of detected and suppressed errors, rerun with: -v ==19893== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 0 from 0) ==19891== ==19891== HEAP SUMMARY: ==19891== in use at exit: 109,613 bytes in 189 blocks ==19891== total heap usage: 305 allocs, 116 frees, 128,673 bytes allocated ==19891== ==19891== 3 bytes in 1 blocks are definitely lost in loss record 1 of 45 ==19891== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19891== by 0x40096E: extract_command (mysh.c:21) ==19891== by 0x400B5D: main (mysh.c:76) ==19891== ==19891== 5 bytes in 1 blocks are possibly lost in loss record 2 of 45 ==19891== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19891== by 0x4E6EC28: xmalloc (in /lib/x86_64-linux-gnu/libreadline.so.6.3) ==19891== by 0x4E4E105: readline_internal_teardown (in /lib/x86_64-linux-gnu/libreadline.so.6.3) ==19891== by 0x4E4F171: readline (in /lib/x86_64-linux-gnu/libreadline.so.6.3) ==19891== by 0x400B4D: main (mysh.c:73) ==19891== ==19891== 16 bytes in 1 blocks are definitely lost in loss record 13 of 45 ==19891== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19891== by 0x4009FE: extract_command (mysh.c:34) ==19891== by 0x400B5D: main (mysh.c:76) ==19891== ==19891== 45 (24 direct, 21 indirect) bytes in 1 blocks are definitely lost in loss record 16 of 45 ==19891== at 0x4C2BBCF: malloc (vg_replace_malloc.c:299) ==19891== by 0x40093C: extract_command (mysh.c:18) ==19891== by 0x400B5D: main (mysh.c:76) ==19891== ==19891== LEAK SUMMARY: ==19891== definitely lost: 43 bytes in 3 blocks ==19891== indirectly lost: 21 bytes in 2 blocks ==19891== possibly lost: 5 bytes in 1 blocks ==19891== still reachable: 109,544 bytes in 183 blocks ==19891== suppressed: 0 bytes in 0 blocks ==19891== Reachable blocks (those to which a pointer was found) are not shown. ==19891== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==19891== ==19891== For counts of detected and suppressed errors, rerun with: -v ==19891== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 0 from 0) [...] ==19961== ==19961== HEAP SUMMARY: ==19961== in use at exit: 109,544 bytes in 183 blocks ==19961== total heap usage: 305 allocs, 122 frees, 128,673 bytes allocated ==19961== ==19961== LEAK SUMMARY: ==19961== definitely lost: 0 bytes in 0 blocks ==19961== indirectly lost: 0 bytes in 0 blocks ==19961== possibly lost: 0 bytes in 0 blocks ==19961== still reachable: 109,544 bytes in 183 blocks ==19961== suppressed: 0 bytes in 0 blocks [...]

On souhaite maintenant pouvoir lancer des processus en arrière-plan (en ajoutant & à la fin de la commande). Lorsque l'utilisateur lance une commande en arrière-plan, la fonction extract_command supprime le & final et position le champs mode à mode_background.

Modifiez votre code pour n'attendre la fin du processus fils que si la commande est lancée en avant-plan. Dans le cas contraire, affichez un message indiquant le PID du processus fils lancé en arrière plan. Par exemple:

mysh $ ls Makefile Makefile~ mysh mysh.c mysh.c~ mysh_final.c mysh_v1.c mysh_v1.c~ mysh_v2.c mysh_v2.c~ mysh_v3.c mysh_v3.c~ mysh_v4.c mysh_v4.c~ mysh $ ls & (in the background: process 20112) mysh $ Makefile Makefile~ mysh mysh.c mysh.c~ mysh_final.c mysh_v1.c mysh_v1.c~ mysh_v2.c mysh_v2.c~ mysh_v3.c mysh_v3.c~ mysh_v4.c mysh_v4.c~

Lorsqu'un processus qui s'exécutait en arrière-plan se termine, il le signale à son processus père. Dans un shell classique (comme bash), cela provoque l'affichage d'un message indiquant quel processus s'est terminé. Par exemple:

$ ls & [1] 19994 $ Makefile Makefile~ mysh mysh.c mysh.c~ mysh_final.c mysh_v1.c mysh_v1.c~ mysh_v2.c mysh_v2.c~ mysh_v3.c mysh_v3.c~ ls Makefile Makefile~ mysh mysh.c mysh.c~ mysh_final.c mysh_v1.c mysh_v1.c~ mysh_v2.c mysh_v2.c~ mysh_v3.c mysh_v3.c~ [1]+ Fini ls --color=auto $

Implémentez un mécanisme qui, après l'exécution d'une commande, vérifie si un processus fils s'est terminé. Si c'est le cas, le shell affiche le PID du processus qui s'est terminé:

mysh $ ls & (in the background: process 20154) mysh $ Makefile mysh mysh.c~ mysh_v1.c mysh_v2.c mysh_v3.c mysh_v4.c mysh_v5.c Makefile~ mysh.c mysh_final.c mysh_v1.c~ mysh_v2.c~ mysh_v3.c~ mysh_v4.c~ mysh_v5.c~ ls Makefile mysh mysh.c~ mysh_v1.c mysh_v2.c mysh_v3.c mysh_v4.c mysh_v5.c Makefile~ mysh.c mysh_final.c mysh_v1.c~ mysh_v2.c~ mysh_v3.c~ mysh_v4.c~ mysh_v5.c~ [20154] Completed mysh $ Pour cela, utilisez l'option WNOHANG de la fonction waitpid qui permet de tester (sans attendre) si un processus fils s'est terminé.

Avec le shell bash, lorsqu'un processus en arrière-plan se termine, son PID est affiché accompagné de la commande ayant engendré le processus.

Complétez votre implémentation pour qu'elle affiche également la commande ayant permis de créer le processus qui vient de se terminer:

$ ./mysh mysh $ ls & (in the background: process 20404) mysh $ Makefile mysh mysh.c~ mysh_v1.c mysh_v2.c mysh_v3.c mysh_v4.c mysh_v5.c mysh_v6.c Makefile~ mysh.c mysh_final.c mysh_v1.c~ mysh_v2.c~ mysh_v3.c~ mysh_v4.c~ mysh_v5.c~ mysh_v6.c~ ls Makefile mysh mysh.c~ mysh_v1.c mysh_v2.c mysh_v3.c mysh_v4.c mysh_v5.c mysh_v6.c Makefile~ mysh.c mysh_final.c mysh_v1.c~ mysh_v2.c~ mysh_v3.c~ mysh_v4.c~ mysh_v5.c~ mysh_v6.c~ [20404] Completed - ls & Il est nécessaire pour cela de créer une liste des processus lancés en arrière-plan qui ne sont pas encore terminé.