Programmation en temps partag� - Principes et notion de processus

ArticleCategory:

SoftwareDevelopment

AuthorImage:

[Leonardo]

TranslationInfo:

original in it: Leonardo Giordani

it to en: Leonardo Giordani

en to fr: Paul Delannoy

AboutTheAuthor:

Etudiant � la Facult� d'Ing�nieurs Polytechniciens en T�l�communications de Milan, il travaille comme administrateur r�seau et s'int�resse � la programmation (surtout en langage assembleur et en C/C++). Depuis 1999 il ne travaille que sous Linux/Unix.

Abstract:

Cette s�rie d'articles se propose d'initier le lecteur au concept de multit�che et � sa mise en pratique dans le syst�me d'exploitation Linux. Nous partirons des concepts th�oriques de base concernant le multit�che pour aboutir � l'�criture compl�te d'une application illustrant la communication entre processus, avec un protocole simple mais efficace. Pour la compr�hension de l'article il faut : Toute r�f�rence � une page de manuel sera plac�e entre parenth�ses apr�s le nom de la commande. Toutes les fonctions de glibc sont expliqu�es dans les pages info gnu (info Libc, ou tapez info:/libc/Top dans konqueror).

ArticleIllustration:

[ex�cutions en parall�le]

ArticleBody:

Introduction

Un des moments les plus importants dans l'histoire des syst�mes d'exploitation est l'apparition du concept de programmation multiple, technique d'entrelacement de l'ex�cution de plusieurs programmes afin d'utiliser au mieux et de mani�re constante les ressources du syst�me. Pensez � une station de travail, sur laquelle il est possible d'ex�cuter simultan�ment un traitement de texte, un lecteur audio, une file d'attente d'impression, un navigateur, et plus encore. C'est un concept important dans les OS modernes. Comme nous allons le d�couvrir cette petite liste n'est qu'une infime partie de l'ensemble des programmes qui fonctionnent couramment sur votre machine, m�me si c'est la plus �videmment visible.

Le concept de processus

Pour r�aliser l'entrelacement des programmes, une complication significative du syst�me d'exploitation est n�cessaire; pour �viter les conflits potentiels entre programmes, un choix incontournable consiste � encapsuler chacun avec l'ensemble des informations n�cessaires � son ex�cution.

Avant d'explorer ce qui se passe dans votre machine Linux, un peu de vocabulaire technique : Soit un PROGRAMME donn�, � un instant donn�, le CODE est l'ensemble des instructions qui le constituent, l'ESPACE MEMOIRE est la quantit� de m�moire de la machine occup�e par ses donn�es, et l'ETAT du PROCESSEUR est la valeur des param�tres du microprocesseur, comme les "flags" (drapeaux) ou le "Program Counter" (l'adresse m�moire de la prochaine instruction � ex�cuter).

Nous d�finissons le terme PROGRAMME S'EXECUTANT comme un ensemble d'objets constitu� de CODE, d'ESPACE MEMOIRE et d'ETAT du PROCESSEUR. Si, � un moment donn� du fonctionnement de la machine, nous pouvons sauver cet ensemble et le remplacer par l'ensemble des informations d'un autre programme en cours d'ex�cution, ce dernier va continuer au point o� il avait �t� arr�t� : faire cela une fois avec l'un, puis une fois avec l'autre procure l'entrelacement que nous d�crivions ci-dessus. Le terme PROCESSUS (ou TACHE) d�signe un tel programme en cours d'ex�cution.

Regardons � nouveau la station de travail dont nous parlions dans l'introduction : � chaque instant il n'y a qu'une TACHE en ex�cution (elle n'a qu'un seul microprocesseur qui NE peut PAS faire deux choses diff�rentes en m�me temps), et la machine ex�cute une part de son code; apr�s une certaine dur�e nomm�e QUANTUM, le processus en cours est suspendu, ses informations sont sauv�es et remplac�es par celles d'un autre processus en attente, dont le code sera ex�cut� pendant un quantum de temps, et ainsi de suite. C'est ce que nous appelons le "fonctionnement multit�che".

Comme nous l'indiquions pr�c�demment, l'introduction du multit�che pose une s�rie de probl�mes, dont beaucoup ne sont pas triviaux, comme celui de la gestion de la file d'attente des processus suspendus (SCHEDULING); pourtant l'architecture de chaque syst�me d'exploitation doit �tre prise en compte : ce sera peut-�tre le sujet principal d'un futur article de la s�rie, qui pr�sentera probablement quelques "morceaux" de code du noyau Linux.

Processus sous Linux et Unix

D�couvrons un peu les processus actifs sur notre machine. La commande qui nous donne ces informations est ps(1), acronyme de "process status". Dans une fen�tre shell, tapez la commande ps et vous obtenez l'�quivalent de

  PID TTY          TIME CMD
   2241 ttyp4    00:00:00 bash
   2346 ttyp4    00:00:00 ps

Comme je l'ai d�j� not�, cette liste n'est pas compl�te, mais examinons-la un instant : ps nous a donn� la liste de tous les processus actifs sur notre terminal. Nous reconnaissons dans la derni�re colonne le nom sous lequel le processus a �t� lanc� (comme "mozilla" pour le navigateur Mozilla et "gcc" pour le Compilateur C GNU). Evidemment "ps" appara�t dans cette liste puisqu'il �tait en cours d'ex�cution lors de l'affichage du r�sultat de la commande. L'autre processus pr�sent est celui du Bourne Again Shell, le shell "bash" qui fonctionne sur mes terminaux.

Ignorons (pour le moment) les informations TIME et TTY et attardons-nous sur PID, le "Process IDentifier". Ce pid est un nombre positif entier (non nul) qui est associ� � chaque processus lors de sa cr�ation; lorsqu'il sera termin�, son nombre pid pourra resservir, mais nous sommes assur�s que pendant toute la dur�e de son ex�cution ce nombre restera le m�me. Tout cela implique que chacun de vous aura obtenu une r�ponse de la commande ps diff�rente de l'exemple ci-dessus. Pour v�rifier la v�racit� de mes propos, ouvrez un autre shell sans fermer le premier et tapez-y la commande ps : la liste affiche les m�mes processus mais avec des nombres pid diff�rents, prouvant ainsi qu'il s'agit bien de deux processus distincts m�me si le programme est le m�me.

Nous pouvons aussi afficher la liste de tous les processus actifs sur notre ordinateur Linux : la page de manuel de la commande ps indique que l'option -e signifie "s�lection de tous les processus". Donc tapons "ps -e" sur un terminal et ps va afficher une longue liste au format illustr� ci-dessus. Pour pouvoir l'analyser tranquillement, redirigeons la sortie de la commande ps vers le fichier ps.log :

ps -e > ps.log

Nous pouvons alors lire ce fichier dans notre �diteur favori (ou simplemant avec la commande less); comme indiqu� au d�but de l'article le nombre de processus est sup�rieur � ce que nous imaginions. Nous voyons tout de suite que cette liste comporte non seulement l'ensemble des processus que nous avons cr�� (par la ligne de commande ou depuis un environnement graphique) mais aussi un autre ensemble de processus, certains avec des noms �tranges : le nombre et l'identit� des processus de la liste d�pendent de la configuration de votre syst�me, avec certains points communs. Tout d'abord, quelle que soit votre configuration syst�me, le processus dont le pid vaut 1 est toujours "init", le p�re de tous les processus; il a ce pid num�ro 1 parce qu'il est toujours le premier processus lanc� par le syst�me d'exploitation. Une autre remarque �vidente concerne la pr�sence de plusieurs processus dont le nom se termine par la lettre "d" : on les appelle "daemons" et ils font partie des processus les plus importants du syst�me. Nous �tudierons en d�tail init et les daemons dans un prochain article.

Le multit�che dans la libc

Nous comprenons maintenant le concept de processus et sa grande importance pour notre syst�me d'exploitation : nous allons poursuivre et commencer � �crire du code multit�che; apr�s les questions soulev�es par la simultan�it�, nous allons rencontrer un nouveau probl�me : celui de la communication entre des processus concurrents et leur synchronisation; nous d�couvrirons deux solutions �l�gantes � ce probl�me, les messages et les s�maphores. Ces derniers seront abord�s plus en profondeur dans un article � venir concernant les "threads". Apr�s avoir �tudi� les messages il sera temps de commencer l'�criture de notre application bas�e sur tous ces concepts.

La biblioth�que C standard (libc, mise en oeuvre sous Linux par glibc) utilise les caract�ristiques multit�ches d'Unix System V; l'Unix System V (maintenant appel� SysV) est une variante commerciale d'Unix, constituant l'une des deux grandes familles d'Unix, l'autre �tant l'Unix BSD.

Dans la libc, le type pid_t est d�fini comme un entier susceptible de contenir un pid. Nous l'utiliserons pour caract�riser un pid, uniquement pour la clart� : utiliser un entier revient au m�me.

D�couvrons la fonction qui va nous donner la valeur de pid du processus contenant notre programme.

pid_t getpid (void)

(qui est d�finie par pid_t dans unistd.h et dans sys/types.h) puis �crivons un programme pour afficher le pid obtenu sur la sortie standard. Tapez le code suivant dans l'�diteur de votre choix :

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main()
{
  pid_t pid;
  
  pid = getpid();
  printf("The pid assigned to the process is %d\n", pid);

  return 0;
}
Enregistrez ce programme sous print_pid.c et compilez-le :
gcc -Wall -o print_pid print_pid.c
Ceci va construire un ex�cutable nomm� print_pid. Rappelez-vous que si le r�pertoire courant n'est pas dans le chemin, vous devrez l'ex�cuter en tapant "./print_pid". L'ex�cution ne r�serve pas de grosses surprises : il affiche un nombre positif et, s'il est ex�cut� plusieurs fois, ce nombre augmente de un � chaque fois; ce n'est pas syst�matique, puisqu'un autre processus peut �tre cr�� par un autre programme entre une ex�cution de print_pid et la suivante. Essayez, par exemple, de lancer ps entre deux ex�cutions de print_pid...

Il est temps d'apprendre comment cr�er un processus, mais je dois encore dire quelques mots sur ce qui se passe lors d'une telle action. Quand un programme (contenu dans le processus A) cr�e un autre processus (B), les deux sont identiques, c'est-�-dire ont le m�me code, occupent la m�moire avec les m�mes donn�es (mais pas la m�me plage de m�moire) et ont le m�me �tat de processeur. Toutefois les deux peuvent s'ex�cuter de mani�re diff�rente, par exemple selon les saisies de l'utilisateur ou des donn�es al�atoires. A est le "processus p�re" alors que B est le "processus fils"; cela donne tout son sens � l'expression "p�re de tous les processus" d�signant init. La fonction qui cr�e un nouveau processus est :

pid_t fork(void)
et son nom lui vient de sa capacit� � s�parer en deux l'ex�cution du processus. Le nombre renvoy� par cette fonction est un pid, qui m�rite un peu d'attention. Nous avons dit que le processus en cours se partage en un p�re et un fils, qui vont s'ex�cuter en s'entrela�ant avec tous les processus en cours, pour effectuer diff�rentes choses; mais juste apr�s la duplication lequel va �tre ex�cut�, le p�re ou le fils ? Eh bien, la r�ponse est simplement : un des deux. Cette d�cision est prise par une partie du syst�me appel�e scheduler, selon un algorithme bas� sur d'autres param�tres et qui ne tient pas compte du fait qu'un processus soit p�re ou fils.

Cela dit, il est important de savoir quel processus est en cours, puisque le code est le m�me. Les deux processus contiendront chacun le code du p�re et celui du fils, mais chacun n'ex�cutera que l'un des deux. Un regard sur l'algorithme suivant pourra �clairer cette situation :

- FORK
- IF YOU ARE THE SON EXECUTE (...)
- IF YOU ARE THE FATHER EXECUTE (...)
ce qui repr�sente dans une esp�ce de meta-langage le code de notre programme. Voici la cl� du myst�re : la fonction fork renvoie '0' au processus fils et renvoie au p�re le nombre pid du fils. Il suffit donc de v�rifier si le pid renvoy� est z�ro pour savoir quel est le processus qui ex�cute ce code. Traduit en C nous obtenons :
int main()
{
  pid_t pid;

  pid = fork();
  if (pid == 0)
  {
    CODE OF THE SON PROCESS
  }
  CODE OF THE FATHER PROCESS
}
Ecrivons maintenant notre premier code multit�che : vous pouvez le sauver comme fichier fork_demo.c puis le compiler comme pr�c�demment. Je mets des num�ros de ligne pour la clart�. Le programme va se partager et le p�re et le fils vont tous les deux �crire dans l'�cran; le r�sultat final sera l'entrelacement de ces sorties (si tout se passe bien).
(01) #include <unistd.h>
(02) #include <sys/types.h>
(03) #include <stdio.h>

(04) int main()
(05) {
(05)   pid_t pid;
(06)   int i;
  
(07)   pid = fork();
  
(08)   if (pid == 0){
(09)     for (i = 0; i  < 8; i++){
(10)       printf("-SON-\n");
(11)     }
(12)     return(0);
(13)   }
  
(14)   for (i = 0; i < 8; i++){
(15)     printf("+FATHER+\n");
(16)   }

(17)   return(0);
(18) }

Les lignes de (01) � (03) contiennent les fichiers � inclure pour les biblioth�ques requises (Entr�es/Sorties standard, multit�che).
La proc�dure main (comme toujours en GNU), renvoie un entier, qui vaudra z�ro si le programme se termine sans erreur, ou un code d'erreur si quelque chose ne va pas; supposons pour l'instant que tout ira bien (nous ajouterons la gestion d'erreurs lorsque les concepts seront bien clairs). Nous d�finissons le type de donn�es contenant un pid (05) et un entier compteur de boucle (06). Ces deux types, nous l'avons dit, sont identiques, mais ne sont utilis�s que pour apporter une clart� suppl�mentaire.
A la ligne (07) nous appelons la fonction fork qui renverra 0 au programme ex�cut� dans le processus fils et le pid du processus fils au p�re; le test est � la ligne (08). Ainsi le code des lignes (09) � (13) sera ex�cut� dans le processus fils, alors que le reste dans les lignes (14) � (16) sera ex�cut� dans le processus p�re.
Chacune de ces parties �crit simplement 8 fois sur la sortie standard le mot "-SON-" ou le mot "+FATHER+", suivant le processus qui l'ex�cute, puis s'arr�te en retournant z�ro. Ceci est tr�s important, car sans ce dernier "return" le processus fils, apr�s la fin de la boucle, ex�cuterait le code �crit pour le p�re (essayez, �a ne fera aucun mal � votre machine, mais �a ne donnera pas le r�sultat esp�r�). Une telle erreur est tr�s difficile � d�tecter, puique l'ex�cution d'un programme multit�che (d'autant plus qu'il est complexe) donne des r�sultats diff�rents � chaque ex�cution, ce qui rend le d�boguage � partir des r�sultats tout simplement impossible.

En ex�cutant ce programme vous ne serez peut-�tre pas satisfait : je ne peux pas assurer que vous obtiendrez un m�lange des deux cha�nes, ceci en raison de la vitesse d'ex�cution d'une aussi courte boucle. Votre sortie sera probablement une succession de "+FATHER+" suivie par une suite de "-SON-" ou le contraire. Essayez d'ex�cuter le programme plusieurs fois : le r�sultat peut varier.

En ins�rant un d�lai al�atoire avant chaque instruction printf, nous pouvons obtenir une image plus visuelle du multit�che : nous obtenons cela avec les deux fonctions sleep et rand :

sleep(rand()%4)
avec ceci le programme dort (sleep) pour un nombre de secondes fix� au hasard de 0 � 3 (% renvoie le reste de la division enti�re). Maintenant le code ressemble �
(09)  for (i = 0; i < 8; i++){
(->)    sleep (rand()%4);
(10)    printf("-SON-\n");
(11)  }
et de m�me pour le code du p�re. Sauvez comme fork_demo2.c, compilez et ex�cutez. C'est un peu plus lent, et nous notons une diff�rence dans l'ordre de sortie :
[leo@mobile ipc2]$ ./fork_demo2
-SON-
+FATHER+
+FATHER+
-SON-
-SON-
+FATHER+
+FATHER+
-SON-
-FIGLIO-
+FATHER+
+FATHER+
-SON-
-SON-
-SON-
+FATHER+
+FATHER+
[leo@mobile ipc2]$

Regardons en face le probl�me qui �merge maintenant : nous sommes capables de cr�er un certain nombre de processus fils selon un certain processus p�re, qui ex�cuteront des op�rations diff�rentes de celles effectu�es par le processus p�re lui-m�me, dans un environnement multit�che; le p�re doit souvent communiquer avec ses fils, ou au moins se synchroniser avec eux, afin d'ex�cuter les op�rations au bon moment. Le premier moyen d'obtenir une synchronisation entre processus est fourni par la fonction wait :

pid_t waitpid (pid_t PID, int *STATUS_PTR, int OPTIONS)
o� PID d�signe le process_id du processus dont on attend la fin, STATUS_PTR un pointeur vers un entier qui contient l'�tat du processus fils (NULL si on ne d�sire pas l'information) et OPTIONS un ensemble d'options que nous n'aborderons pas pour l'instant. Voici un exemple de programme dans lequel le p�re cr�e un processus fils et attend jusqu'� ce qu'il se termine :
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main()
{
  pid_t pid;
  int i;
  
  pid = fork();
  
  if (pid == 0){
    for (i = 0; i < 14; i++){
      sleep (rand()%4);
      printf("-SON-\n");
    }
    return 0;
  }

  sleep (rand()%4); 
  printf("+FATHER+ Waiting for son's termination...\n");
  waitpid (pid, NULL, 0);
  printf("+FATHER+ ...ended\n");

  return 0;
}				      }
La fonction sleep a �t� ins�r�e dans le code du p�re pour diff�rencier les ex�cutions. Sauvons ce code comme fork_demo3.c, compilons-le puis ex�cutons-le. Nous venons d'�crire notre premi�re application multit�che synchronis�e !

Dans le prochain article nous approfondirons nos connaissances sur la synchronisation et la communication entre processus; maintenant �crivez vos propres programmes et envoyez-les moi afin que je puisse montrer de bonnes solutions ou des erreurs graves. Envoyez-moi le fichier .c avec votre code comment� et un petit fichier texte d�crivant le programme, avec votre nom et votre adresse de courrier �lectronique. Bon travail !

Lectures recommand�es