Travaux pratique d'informatique Temps Réel,, TP 1 1 1.Introduction à la gestion des processus et aux tubes de communication 1.1.Objectifs La séance vise à familiariser l'étudiant avec les concepts de processus et de communication entre processus par l'intermédiaire de tubes. À la fin de la séance, l'étudiant devra être capable de manipuler les processus et d'utiliser les tubes de communication. Les manipulations sont notamment: la création d arborescences de processus; la gestion des identificateurs de processus; la mise en évidence des processus orphelins; l'attente de la mort des processus fils par le processus père; la création de tubes et l'envoi de données par leur intermédiaire. N.B. L'environnement de travail est le système Linux (dont quelques commandes sont décrites brièvement en annexe). La programmation est réalisée en langage C standard accompagné éventuellement de certaines fonctions C++. 1.2.Généralités Un ensemble d'outils est nécessaire à la réalisation des travaux pratiques: un éditeur de texte (gedit) : utilisé pour créer et modifier le code source; un compilateur (gcc ou g++) : permettant de transformer le code source en fichier exécutable; un terminal (xterm) : utilisé pour appeler le compilateur et exécuter les applications; des outils complémentaires : pourront être utilisés pour déboguer et formater un code source existant (gdb et indent) et sont décrits en annexe. 1.3.Généralités sur les processus Un processus est un programme en cours d exécution sur un système; il possède un code ainsi qu'un ensemble de données et est désigné de manière unique sur le système par un identificateur : le pid (pour Process IDentifier). Cet identifiant est de type pid_t mais peut être considéré et utilisé comme un entier. Les principales fonctions de manipulation de processus sont les suivantes : int main(int argc, char *argv[]) fonction principale exécutée lors de l'appel à un programme C. Les paramètres passés en arguments sont optionnels et leurs noms argc et argv sont purement conventionnels. Ces paramètres contiennent respectivement : le nombre d'arguments passés en ligne de commande lors de l'appel au programme; la liste des arguments d'appel. Par exemple, l'appel à un programme tel que./prog 10 150 donnera à argc et argv les valeurs suivantes: argc=3 argv[0]="prog" argv[1]="10" argv[2]="150" La fonction atoi peut être utilisée pour convertir une chaîne de caractères en entier.
Travaux pratique d'informatique Temps Réel,, TP 1 2 pid_t fork() fonction qui crée un processus fils par duplication du processus qui y fait appel. En cas de succès, la fonction fork retourne la valeur du pid du fils au processus père et la valeur 0 au processus fils créé. En cas d erreur lors de la création du processus fils, cette fonction renvoie 1 au processus père et le processus fils ne sera pas créé. int p=0 processus principal pid=512 processus fils pid=513 p=fork() p=513 instruction1 p=0 instruction1 instruction2 instruction2 Exemples d'utilisation du fork() : p = fork(); switch(p) case 1 : /* erreur */ instruction 1 ; break ; case 0 : /* on est dans le fils */ instruction 2 ; break ; default : /* on est dans le père */ instruction 3 ; break ; p = fork(); if(p == 1) /* erreur */ instruction 1 ; else if (p == 0) /* on est dans le fils */ instruction 2 ; else /* on est dans le père */ instruction 3 ; pid_t getpid() renvoie le pid du processus qui y fait appel. pid_t getppid() renvoie le pid du père du processus qui y fait appel. void exit(int status) provoque la terminaison du processus en cours. Il prend en argument un code de retour. Ce code peut être récupéré par le processus père (voir wait). Cette fonction ne renvoie pas de valeur dans la fonction qui l'invoque. pid_t wait(int *status) attend la fin d'un processus fils. wait retourne le pid du fils en cas de succès et 1 en cas d'erreur. status récupère la valeur retournée par le fils, par exemple par la commande exit(status).
Travaux pratique d'informatique Temps Réel,, TP 1 3 int exec() permet de lancer et d'exécuter un programme externe en remplaçant le processus en cours. Différentes variantes pour la spécification du fichier et/ou des arguments existent : execl, execle, execlp, execv, execv, execvp,... execl (char *path, char *arg0, char *arg1,, char *argn, NULL) cette variante exécute un programme ; path est le chemin du fichier programme ; arg0 : premier argument (correspondant au nom du programme exécutable); argn est le n ième argument. Remarques : Exemple : execl ("/bin/ls", "ls", " la", NULL) ; exécute la commande ls située dans le répertoire /bin/ en utilisant le paramètre la Écrire vos programmes C à l aide de l éditeur de texte gedit. Compiler en utilisant le terminal: gcc source o destination (e.g. : gcc test.c o test) Exécuter à partir du terminal :./nomdufichier (e.g. :./test) Utiliser la commande "man" de UNIX pour trouver les explications relatives aux diverses commandes (e.g. : man execv). 1.4.Généralités sur les tubes Un tube est une structure de communication entre processus. Il est utilisé au moyen de descripteurs : l'un pour la lecture et l'autre pour l'écriture. Ces deux descripteurs sont placés dans un tableau de deux entiers. a) Principales primitives de manipulation de tubes Ces fonctions nécessitent l'inclusion de la librairie <unistd.h>. int pipe(int tube[2]); crée une structure de communication appelée tube (pipe en anglais). L'appel système pipe() renvoie la valeur 0 en cas de succès et 1 en cas d'erreur. La fonction prend en argument un tableau de deux entiers (appelés descripteurs). Elle affecte des valeurs aux deux éléments du tableau de la manière suivante : l'élément d'indice 0 est le descripteur de lecture alors que l'élément d'indice 1 est le descripteur d'écriture. Exemple : int tube[2] ; if (pipe(tube)) /* erreur */... else... int close(int desc); ferme un descripteur et libère les ressources associées. L'utilisation du close() indiquera également que l'on ne souhaite plus écrire dans le tube; les informations introduites préalablement dans le buffer sont alors envoyées si une demande de lecture est réalisée.
Travaux pratique d'informatique Temps Réel,, TP 1 4 int read(int desc, void* pt, size_t taille); permet de lire les données se trouvant dans le tube dont le descripteur est desc et écrit ces données à l'emplacement mémoire pt. Par défaut, cette primitive est bloquante tant que les données à lire ne sont pas disponibles sur le descripteur. En revanche s'il n'y a plus rien à lire (fichier terminé ou connexion réseau coupée), la primitive n'est pas bloquée. int write(int desc, void* pt, size_t taille); permet d'écrire les données se trouvant à l'emplacement mémoire pt, dans le tube correspondant au descripteur d'écriture desc.
Travaux pratique d'informatique Temps Réel,, TP 1 5 1.5.Exercice 1 : Processus orphelins Écrire un programme qui crée un processus père et un processus fils. Le processus père affichera 5 fois le message : «Je suis le père» alors que le processus fils écrira 3 fois le message «Je suis le fils». Après l affichage de chaque message, mettre le processus actif en attente avec l instruction «sleep(1)». Examiner le comportement de ce programme lorsque le père affiche 3 fois le message et le fils 5 fois. Modifier ce programme pour que le père attende la mort du processus fils. Remarque : utiliser \n avec la fonction printf() 1.6.Exercice 2 : Arborescence de processus Écrire un programme qui crée les arborescences de processus suivantes : 1. Un processus principal P 0 crée deux processus fils : P 1 et P 2. Le fils P 1 crée à son tour un fils P 3. À la création de chaque processus, celui ci s'annonce en affichant le message suivant : Je suis le processus <pid>, mon père est le processus <pid_du_père> P 0 P 2 P 1 P 3 2. Un processus père crée n fils. Le nombre n sera passé en argument au programme. P 0 P 1 P 2 P 3... P n 1.7.Exercice 3 Écrire un programme qui crée deux processus fils, puis lit (au clavier) des entiers non nuls et envoie les nombres négatifs à son fils gauche et les nombres positifs à son fils droit. La fin est notifiée par la réception (au clavier) du chiffre 0. À la fin de la lecture, le père attend que ses fils lui envoient la somme des nombres reçus. Le père affiche ces résultats. 1.8.Exercice 4 Écrire un programme qui crée un fils, puis lit des chaînes de caractères d'une longueur de 80 caractères. Il transmet ces caractères à son fils qui les convertit en majuscules et en affiche le résultat. Indication : la fonction toupper() reçoit un caractère en argument et renvoie le même caractère en majuscule.
Travaux pratique d'informatique Temps Réel,, TP 1 6 1.9.Exercices supplémentaires a) Combien de processus sont lancés par le programme suivant : #include <stdio.h> #include <stdlib.h> int main() int p1, p2; p1 = fork(); p2 = fork(); exit(0); b)observer les résultats fournis par le programme suivant : #include <stdio.h> #include <stdlib.h> int main() int a = 5; if(fork()) wait(); printf("valeur de a = %d\n",a); printf("adresse de a = %p\n",&a); else a = 10; printf("valeur de a = %d\n",a); printf("adresse de a = %p\n",&a); exit(0); 1.10.Correction de codes sources a) Le fichier err1.c contient un code qui devrait gérer l'exécution de deux processus, l'un devant afficher «Je suis le fils» et l'autre «Je suis le père». A la place ils affichent deux fois «Je suis le père» et une fois «Je suis le fils». Corrigez l'erreur. b) Le fichier err2.c contient un code qui devrait gérer l'exécution de deux processus devant se partager un tube pour communiquer. Le père place dans le tube un nombre passé en argument et le fils doit réceptionner et afficher ce nombre. A la place, le fils affiche constamment la valeur «0». Corrigez l'erreur. c) Le fichier err3.c contient un code qui devrait gérer l'exécution de deux processus qui mettent au carré un nombre et affiche le résultat. Le nombre est passé en ligne de commande et lu par le père qui l'envoie au fils grâce à un tube. Le fils met le nombre au carré puis le renvoie au père qui affiche le résultat. Le père affiche cependant la valeur originale du nombre. Corrigez l'erreur.
Travaux pratique d'informatique Temps Réel,, TP 1 7
Travaux pratique d'informatique Temps Réel,, TP 1 8 1.11.Annexe 1 : Interaction avec l'utilisateur en C/C++ Pour la résolution des exercices de TP, la syntaxe du C ou du C++ peut indifféremment être utilisée. Le petit exemple ci dessous montre l'entrée d'une chaîne de caractères au clavier et l'affichage à la console (sortie standard). En C En C++ #include <stdio.h> int main() char nom[50]; int nbr; scanf("%s",nom); scanf("%d",&nbr); printf("bonjour %s\n",nom); printf("votre nombre est %d\n",nbr); #include <iostream> using namespace std; int main() char nom[50]; int nbr; cin>>nom; cin>>nbr; cout<<"bonjour "<<nom<<"\n"; cout<<"votre nombre est %d\n",nbr); 1.12.Annexe 2 : Utilisation de Linux L'utilisation du système d'exploitation Linux peut principalement se faire de deux manières: à l'aide du gestionnaire de fenêtres (interface graphique à la manière de Microsoft Windows) : l'interface propose un ensemble de fonctionnalités usuelles; à l'aide d'un terminal: c'est ce mode qui nous intéressera plus particulièrement car il offre des possibilités très puissantes en dépit d'un apprentissage nécessaire. Toute application peut aussi bien se lancer à partir d'un terminal qu'à partir du menu du gestionnaire de fenêtre. Le système Linux est avant tout orienté «multi utilisateurs» en ce sens qu'il a été conçu pour permettre l'existence d'un ensemble d'utilisateurs simultanément sur une même machine tout en laissant un maximum de liberté (préférences) à chaque utilisateur. Tout utilisateur Linux démarre une session sous un nom donné (e.g. itr). Un mot de passe est associé à chaque utilisateur de même qu'un répertoire utilisateur (e.g. /home/itr) permettant l'enregistrement des documents et répertoires de l'utilisateur. Principalement, seul ce répertoire est accessible en écriture par l'utilisateur, le reste du système étant ainsi «protégé» d'une erreur de manipulation qui rendrait l'ordinateur indisponible pour tous les autres utilisateurs.
Travaux pratique d'informatique Temps Réel,, TP 1 9 a)utilisation en mode terminal Commande Description Exemple d'utilisation Gestion des répertoires pwd Affichage du répertoire courant $ pwd /home/itr ls Affichage du contenu du répertoire courant (l'option l permet d'obtenir plus d'informations) $ ls fichier1.c fichier2.c cd Changement de répertoire $ pwd /home/itr $ cd exercices $ pwd /home/itr/exercices mkdir Création d'un répertoire $ mkdir rep1 rmdir Effacement d'un répertoire $ rmdir rep1 cp mv Gestion des fichiers (et répertoires) Copie d'un fichier ou d'un répertoire ( r permet de copier également les sous répertoires) Déplacement d'un fichier ou d'un répertoire (ainsi que de son arborescence) $ cp r sous_rep /home/itr/rep $ mv sous_rep /home/itr/rep rm Effacement d'un fichier $ rm fichier1.c cat Affichage du contenu d'un fichier $ cat fichier1.c more Affichage du contenu d'un fichier avec arrêt à chaque page $ more fichier1.c Gestion des processus ps Affichage des processus lié au terminal courant $ ps PID TTY TIME CMD 27483 pts/3 00:00:00 bash 28001 pts/3 00:00:00 ps pstree Affichage de tous les processus du système (l'utilisation de p permet d'obtenir le pid correspondant à chaque processus) kill Envoi un signal à un processus (l'option s permet de spécifier le type de signal à envoyer) ipcs ipcrm Affiche des informations sur l'utilisation des ressources IPC (cfr TP4) Supprime l'utilisation d'une ressource IPC (cfr TP4) (des options particulières permettent de spécifier quel type de ressource supprimer voir page de manuel) $ pstree p init(1) + artsd(27467) atd(4194) atieventsd(3696)... $ kill s 9 4194 $ ipcs Shared Memory Segments key shmid owner perms 0x00000000 221773830 itr 600 $ ipcrm m 221773830
Travaux pratique d'informatique Temps Réel,, TP 1 10 Toutes les commandes utilisées dans le terminal Linux dispose d'une page de manuel. Cette page peut être affichée à l'aide de la commande man. Les pages de manuel sont regroupées en catalogues afin de séparer les commandes de nom identique existants dans des contextes différents (e.g. la commande kill existe dans le terminal mais aussi en tant que fonction du langage C). Le contexte peut donc être spécifié lors de l'appel à la commande man: $ man 1 kill > page de manuel de la commande terminal kill $ man 2 kill > page de manuel de la fonction C kill() La liste des catalogues peut être obtenue dans la page de manuel de la commande man: $ man man 1.13.Annexe 3 : Langage C a) Section Déclarative. // bibliothèques #include <stdio.h> #include <stdlib.h> // constantes #define Nmax 20 #define MAX 10 // variables int a ; float a ; char a; char a[10]; char a[max]; // types typedef struct char[max] Nom; char[max] Prénom; Etudiant; b)condition if (condition) else c) Boucles for (i=0 ; i < n ; i++) instruction 1; instruction 2; do instruction 1; instruction 2; while (condition); while (condition) instruction1; instruction2;
Travaux pratique d'informatique Temps Réel,, TP 1 11 1.14.Annexe 4 : Utilisation des outils annexes a) Le débogueur gdb L'utilisation du débogueur nécessite que le code soit compilé à l'aide d'une option particulière : $ gcc g code.c o application Lorsqu'une application qui a été compilée à l'aide de cette option est exécutée et s'arrête anormalement, un fichier spécial est créé (core). Ce fichier représente une image de l'espace mémoire du processus au moment du plantage et est examiné lors de l'utilisation du débogueur gdb. list run L'appel au débogueur se fait de la manière suivante: $ gdb application Les commandes du débogueur sont regroupées dans le tableau ci dessous. Affiche le code du programme Lance l'exécution du programme jusqu'au prochain point d'arrêt break Insère un point d'arrêt à la ligne spécifiée break 22 cont quit info breakpoints set detach on fork Continue l'exécution du programme jusqu'au prochain point d'arrêt Permet de quitter le débogueur Affiche une liste des points d'arrêt Permet de spécifier le comportement lors de l'appel à la fonction fork. on le processus sera démarré automatiquement off le processus sera suspendu (utile pour le débogage) set detach on fork off b)le formateur de code indent Un code bien présenté au niveau de sa structure et de son indentation est nécessaire à une bonne compréhension de son fonctionnement. Une attention particulière doit donc être apportée à la mise en forme du code développé afin d'en faciliter la lisibilité. Un outil permet de mettre en forme de manière automatique un code existant. Il s'agit de l'utilitaire indent. Cet outil peut être utilisé avec un grand nombre d'options spécifiant le type de formatage utilisé, par exemple: $ indent kr code.c 1.15.Références bibliographiques 1. Informatique temps réel Pierre Manneback, 2006 2007 Faculté Polytechnique de Mons. 2. Informatique de base Notes de Laboratoire 2005 2006 Mohammed Benjelloun Faculté Polytechnique de Mons. 3. Programmation Linux Neil Matthew, Richard Stones Eyrolles 2000. 4. Programmation système en C sous Linux Christophe Blaess Éditions Eyrolles, 2005.