CentraleSupélecDépartement informatique
Plateau de Moulon
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
3IF1020 - Programmation système - TP n°1

Table des matières

  • Date de la séance de TP : vendredi 3 octobre 2025 - 13h45
  • Date limite de prise en compte de votre version sur votre dépôt GitLab : dimanche 12 octobre 2024 - 23h59

Utiliser les fichiers du dossier tp/ps_tp1 de votre dépôt GitL, seul le contenu de ce dossier sera pris en compte pour l'évaluation de votre travail sur ce TP.

Les noms des fichiers à utiliser sont indiqués dans ce sujet.

⚠️Les noms des fichiers ont été récemment modifiés, il peut être nécessaire de faire un Update fork pour mettre à jour votre dépôt git.⚠️

Vous devez respecter les add, commit & push mentionnés dans le sujet. Cela ne vous interdit bien sûr pas de corriger une réponse déjà fournie dans un commit ultérieur.


Ces exercices ont pour objectif de mettre en œuvre une partie des éléments présentés en cours en vous faisant utiliser des appels systèmes classiques d'un système d'exploitation de type Unix.

Avec un système Windows, l'environnement WSL devrait permettre de faire cet exercice (non testé). En revanche, MSYS2 ne peut pas être utilisé. Si vous n'êtes pas familier avec ces aspects techniques, utilisez MyDocker en demandant cet environnement.

Les programmes seront écrits en C.

1. Un programme, un processus (1 point)

1.1. Générateur de nombre aléatoires

  • Ouvrir deux fenêtres Terminal que nous appelleront Term1 et Term2.
  • Écrire dans le fichier server_11.c un programme C contenant un main() qui :
    • affiche un message avec printf()
    • puis, dans une boucle infinie :
      • affiche son id (numéro de processus, utilisez getpid()), celui de son père (getppid()), ainsi que celui de son groupe (getpgrp())
      • affiche un nombre aléatoire entre 0 et 99 obtenu avec rand()
      • se met en pause pendant une seconde grâce à sleep()
    • affiche de nouveau un message (après la boucle)
    • retourne le code EXIT_SUCCESS indiquant un fonctionnement nominal.

getpid() est souligné car c'est un lien vers la page de documentation de cette fonction : vous devez prendre l'habitude d'aller consulter cette documentation, ainsi que celle des autres fonctions mentionnées dans le sujet.

Les pages de documentation des appels systèmes peuvent parfois être très techniques et compliquées à comprendre ; ce n'est pas le cas de getpid(), mais c'est le cas de sigaction() que vous allez utiliser juste après. Il ne faut pas chercher à tout lire et tout comprendre pour utiliser une telle function une première fois : survolez la documentation, identifiez les points importants (au début la plus part du temps) pour votre besoin, le cas échéant cherchez des exemples sur Internet.


Toutes les fonctions systèmes utiles pour cet exercice sont mentionnées dans le sujet. Si vous en utilisez d'autres, vous devez le justifier.

  • Dans Term1, compiler et exécuter ce programme.
  • Dans Term2, taper la commande ps aj. Identifier votre processus dans la liste, quel est son processus père ? Que constatez-vous pour les numéros de groupe ? Quel est son état (colonne STAT) ?

Les réponses aux questions posées doivent être en commentaires dans le fichier source.


ps est une commande classique d'un système Unix® permettant l'affichage des processus existants.


  • Dans Term1, arrêter le programme en utilisant la combinaison de touches CTRL-C, puis vérifier dans Term2 qu'il n'apparait plus dans la liste affichée par la commande ps aj.
  • Dans Term1, relancer le programme et le suspendre en utilisant la combinaison de touches CTRL-Z. Quel est l'état du processus ?
  • Dans Term1, taper la commande fg. Quel est son effet ?
  • Dans Term1, suspendre de nouveau le processus puis taper la commande bg. Quel est son effet ? Quel est l'état du processus ?
  • Dans Term1, exécuter une seconde fois votre programme en suffixant la commande de lancement avec le caractère  & . Quel est l'effet de ce caractère ?
  • Dans Term1, taper la commande fg puis CTRL-C, le refaire. Est-ce que les deux processus sont bien arrêtés ?

add, commit & push


1.2. Non arrêt sur CTRL-C

CTRL-C a pour conséquence l'envoi d'un signal au processus. Par défaut, ce signal provoque l'arrêt du processus. Nous souhaitons changer ce comportement.

  • Recopier votre programme dans le fichier server_12.c.
  • Définir une fonction qui reçoit un numéro de signal (un int) et affiche un message contenant le numéro du signal reçu. Installer cette fonction, en utilisant sigaction(), pour qu'elle soit automatiquement exécutée sur réception du signal envoyé par CTRL-C. Vous utiliserez bien sûr le nom symbolique du signal.

Une fonction installée avec sigaction() pour un signal donné le reste tant que vous n'installez pas une autre fonction pour ce même signal.


La documentation de sigaction() précise que cette fonction retourne -1 en cas d'erreur et positionne la variable globale errno avec le code d'erreur. Une bonne pratique dans ce cas est d'utiliser perror() pour afficher un message avant de terminer le processus avec la fonction exit() utilisant le code de retour EXIT_FAILURE.

De manière générale, il est important en programmation système de n'ignorer aucun signalement d'erreur.


  • Dans Term1, compiler et exécuter ce programme, essayer de l'arrêter avec CTRL-C, vérifier que le message de votre fonction est affiché mais que le processus continue son exécution.
  • Quelle est la valeur du signal envoyé par CTRL-C ?
  • Dans Term2, taper kill idid est le numéro de processus de votre programme (kill est la commande, id son argument). Quelle est la conséquence de cette commande ?

kill est une autre commande classique d'un système Unix® permettant d'envoyer un signal à un processus.


  • Dans Term1, exécuter de nouveau votre programme ; dans Term2, utiliser la commande kill avec l'option -s INT (l'option doit être indiquée avant l'argument). Quelle est la conséquence de cette variante ?
  • Modifier votre programme pour obtenir le même comportement (affichage d'un message et pas d'arrêt) dans les deux cas. Dans Term1, exécuter de nouveau votre programme ; comment l'arrêter ? Est-il possible d'intercepter tous les signaux de façon à ce qu'il ne soit plus possible d'arrêter un processus ?

add, commit & push


1.3. Choix du signal pour l'arrêt

Le choix suivant est effectué : les deux signaux provoquent l'affichage d'un message via la fonction installée avec sigaction(), mais seul kill sans option stoppe le processus.

  • Recopier votre programme dans le fichier server_13.c.
  • Modifier votre boucle pour qu'elle ne s'exécute que tant qu'une variable booléenne globale running soit vraie (cette variable doit être vraie au démarrage de votre programme). Modifier votre fonction qui est appelée sur réception d'un signal pour réaliser le comportement souhaité.

Votre variable running doit être déclarée volatile : elle va en effet être modifiée par votre fonction de manière asynchrone par rapport au déroulement du programme principal, ce mot clef impose au compilateur d'aller systématiquement chercher la valeur de cette variable dans la mémoire centrale, donc de ne pas la conserver dans un registre pour des raisons d'optimisation.


  • Dans Term1, compiler et exécuter ce programme, essayer de l'arrêter le en utilisant CTRL-C, puis arrêter le avec kill, vérifier que le message de votre fonction est affiché dans les deux cas.

add, commit & push


1.4. Message de fin

  • Recopier votre programme dans le fichier server_14.c.
  • Ajouter dans votre programme une fonction qui affiche un message. Installer cette fonction au début de main(), en utilisant atexit(), pour qu'elle soit automatiquement exécutée à la fin du programme. Tester si cette fonction est toujours activée quelle que soit la façon dont le programme est arrêté.

La remarque faite ci-dessus pour sigaction() est aussi valable pour atexit().


add, commit & push


2. Un programme, deux processus (1 point)

2.1. Même code

  • Recopier votre programme dans le fichier server_21.c.
  • Modifier le pour que le processus s'arrête maintenant aussi avec un CTRL-C.
  • Dupliquer votre processus en utilisant fork(). Les 2 processus exécutent le même programme, faites en sorte que les messages précisent s'ils sont émis par le père ou par le fils (mais éviter de dupliquer du code !)
  • Que constatez-vous pour les numéros de groupe ? Est-ce que les 2 processus s'arrêtent avec CTRL-C ? Pouvez-vous expliquer pourquoi ?
Besoin d'aide ?

Consulter par exemple la page de getpgid().


add, commit & push


  • Relancer votre programme. Utiliser ps aj dans Term2 pour les visualiser. Utiliser kill pour arrêter le processus fils puis ps aj de nouveau : que remarquez-vous ?
  • Maintenant, tuer le père, est-ce que le fils est toujours visible ?
  • Modifier votre code pour que le père s'arrète quand il détecte (via un signal) que le fils s'est arrêté.
  • Relancer votre programme et commencer cette fois par tuer le père : qu'observez-vous ?
  • Tuer le fils.
  • Modifier votre programme pour que le père, quand il est arrêté par un signal, arrête le fils en lui envoyant un signal avec kill().

add, commit & push


2.2. Un serveur, un client, un tube

Le père et le fils vont maintenant exécuter un code différent : au lieu d'afficher les nombres, le père va les communiquer (en tant que nombres - int -, pas sous forme de chaînes de caractères, et sans utiliser une valeur particulière pour indiquer la fin de la communication) au fils qui, lui, les affichera. La communication se fera via un « tube ».

  • Recopier votre programme dans le fichier server_22.c.
  • Supprimer l'arrêt du fils par le père et celui du père par le fils : les 2 processus vont maintenant communiquer par un tube, ils seront ainsi en mesure de détecter l'arrêt de l'autre.
  • Créer un tube en utilisant pipe() et modifier le code pour réaliser le comportement demandé. Vous utiliserez read() pour lire et write() pour écrire dans le tube.

Vous connaissez le principe DRY ? Ici, le code commun au père et au fils ne doit pas être dupliqué !


  • Vérifier que les processus s'arrêtent avec CTRL-C, ainsi qu'avec un kill sur le fils ou sur le père.

N'oubliez pas que les extrémités d'un tube doivent être fermées (avec close()) quelle que soit la façon dont le processus se termine (sauf bien sûr sur un kill -9) et que read() et write() retournent une valeur.


  • Comparer les messages affichés quand on arrête le père en premier, et ceux quand on arrête le fils en premier. Faire en sorte que le père affiche aussi son message de fin quand on arrête le fils en premier.
Besoin d'aide ?

Le parent reçoit un signal quand le tube est fermé par le fils.


add, commit & push


3. Deux programmes, deux processus (0,5 point)

3.1. Un serveur, un client, une fifo

Les deux processus seront maintenant des programmes différents (un serveur server_31.c qui envoie des nombres, pas des caractères, à un client client_31.c). Pour communiquer, ils utiliseront un « tube nommé » ou fifo.

  • Créer une fifo nommée à l'aide de la commande shell mkfifo.
  • En reprenant votre code précédent, écrire deux programmes distincts qui communiquent via la fifo. Celle-ci sera ouverte en utilisant open() (en écriture seule par le serveur, en lecture seule par le client). Dès qu'un des deux processus sera arrêté, l'autre devra s'arrêter aussi (en affichant ses messages d'arrêt).

add, commit & push


4. Partie avancée (1,5 point)

4.1. Sécurité de la fonction installée par sigaction()

La page async-signal-safe functions nous indique que l'utilisation de printf() dans la fonction installée par sigaction() n'est pas une bonne idée.

  • Recopier le programme du fichier server_13.c dans un fichier server_41.c.
  • Proposer une solution pour assurer la sécurité sans changer le comportement visible.

add, commit & push


4.2. Un serveur, un client, le réseau

Les deux processus sont toujours des programmes différents (un serveur server_42.c qui envoie des nombres à un client client_42.c), mais, pour communiquer, ils utiliseront une connexion réseau.

  • En reprenant votre code de la question 3.1, écrire deux programmes distincts qui communiquent via TCP.
    • Le serveur commencera par créer une extrémité de communication avec socket() en utilisant le domaine IPv4, une communication de type flux et le protocole associé par défaut. Il ne faudra pas oublier de faire les conversions entre la représentation machine et la représentation réseau à l'aide des fonctions htonl() et htons(). En utilisant bind(), le serveur associera ce socket à une adresse IPv4 de socket sur toutes les interfaces locales et en utilisant un numéro de port non privilégié qu'il faudra choisir. Il pourra alors attendre, en utilisant listen(), la demande d'ouverture de connexion par le client, l'accepter avec accept() et enfin lui envoyer des nombres aléatoires avec write().
    • Le client créera aussi une extrémité de communication du même type. Il utilisera connect() pour se connecter au serveur sur l'adresse de loopback, et pourra ainsi recevoir les nombres générés par le serveur avec read().

La description de ce qu'il faut faire est volontairement succincte afin de vous inciter le cas échéant à utiliser Internet pour vous aider.


add, commit & push