12

La gestion des processus

Nous avons vu dans les premiers chapitres que l'introduction des activités parallèles avait permis d'améliorer la rentabilité des machines. Le parallélisme peut être lié à des activités spécifiques, et donc obtenu par des processeurs spécialisés. Par exemple, un transfert d'entrées-sorties peut être exécuté en même temps qu'un calcul interne au processeur. Il peut aussi être le moyen d'obtenir plus de puissance de calcul dans le même temps imparti, en utilisant plusieurs processeurs de calcul.
Plus généralement, un processeur peut exécuter une séquence d'instructions d'un programme, puis ensuite une séquence d'instructions d'un autre programme, avant de revenir au premier, et ainsi de suite. Si la durée d'exécution des séquences est assez petite, les utilisateurs auront l'impression que les deux programmes s'exécutent en parallèle. On parle alors de pseudo-parallélisme. Évidemment, vu du processeur lui-même, il s'agit d'une activité unique et séquentielle, un programme de supervision décidant de la séquence à exécuter. C'est ce programme de supervision, inclus dans le système, qui donne en fait l'illusion du parallélisme. Si la recherche de la meilleure efficacité du matériel a été à l'origine du parallélisme et reste un aspect important, le pseudo-parallélisme permet à plusieurs utilisateurs d'utiliser une même machine au même instant, et à chacun d'eux de lancer plusieurs activités en même temps, comme par exemple compiler un module source pendant que l'on modifie un autre module source. La gestion de ces activités parallèles est en général assez complexe, et difficile à appréhender. Le concept de processus est fondamental pour la compréhension de cette gestion, tant pour le concepteur de système que pour l'utilisateur.

12.1. La notion de processus

Le terme de programme est souvent utilisé à des sens différents. Lorsqu'il est utilisé pour désigner l'ensemble des modules sources, l'ensemble des modules objets ou le résultat de l'édition de liens, il représente toujours la même abstraction, écrite dans différents langages de représentation, de la description des actions à entreprendre pour aboutir au résultat recherché. Cet aspect description des actions est en fait fondamental, et le terme de programme ne devrait être utilisé que dans ce sens.
Le terme de processeur est utilisé pour désigner l'entité (matérielle ou logicielle) qui est capable d'exécuter des instructions. Jusqu'à maintenant, nous l'avons utilisé essentiellement pour désigner une entité matérielle, comme le processeur de calcul, ou le processeur d'entrées-sorties, et nous continuerons à l'utiliser dans ce sens, mais il peut parfois être intéressant de considérer qu'un interpréteur, par exemple, est un processeur logiciel capable d'exécuter des instructions du langage qu'il interprète.
La notion de processus a été introduite pour désigner l'entité dynamique qui correspond à l'exécution d'une suite d'instructions. C'est un concept abstrait, en ce sens qu'il désigne l'évolution d'une activité dans le temps. C'est le processeur qui fait évoluer cette activité en exécutant les instructions définies par un programme. Le système doit permettre de distinguer les différents processus qui existent à un instant donné, et doit représenter leur état. Celui-ci permet, d'abord, de localiser le programme lui-même, c'est-à-dire la suite d'instructions, ensuite, de déterminer les données propres et les variables, enfin de représenter l'état du processeur après l'exécution d'une instruction quelconque du programme (registres, compteur ordinal, mot d'état programme,...).
Pour bien montrer la différence entre programme et processus, nous reprendrons une image de Tanenbaum. Supposez qu'un informaticien décide de faire un gâteau pour l'anniversaire de sa fille. Pour être certain de le réussir, il ouvre devant lui son livre de recettes de cuisine à la page voulue. La recette lui précise les denrées dont il a besoin, ainsi que les instruments nécessaires. À ce stade, nous pouvons comparer la recette sur le livre de cuisine au programme, et l'informaticien au processeur. Les denrées sont les données d'entrées, le gâteau est la donnée de sortie, et les instruments sont les ressources nécessaires. Le processus est l'activité dynamique qui transforme les denrées en gâteau.
Supposez que, sur ces entrefaites, le fils de l'informaticien vient se plaindre qu'il a été piqué par une abeille. L'informaticien interrompt son travail, enregistre l'endroit où il en est, extrait de sa bibliothèque le livre de première urgence, et localise la description des soins à apporter dans ce cas. Lorsque ces soins sont donnés et que son fils est calmé, l'informaticien retourne à sa cuisine pour poursuivre l'exécution de sa recette.
On constate qu'il y a en fait deux processus distincts, avec deux programmes différents. Le processeur a partagé son temps entre les deux processus, mais ces deux processus sont indépendants.
Supposez maintenant que la fille de l'informaticien vienne lui annoncer de nouveaux invités. Devant le nombre, l'informaticien décide de faire un deuxième gâteau d'anniversaire identique au premier. Il va devoir partager son temps entre deux processus distincts qui correspondent néanmoins à la même recette, c'est-à-dire au même programme. Il est évidemment très important que le processeur considère qu'il y a deux processus indépendants, qui ont leur propre état, même si le programme est le même.
De même que l'informaticien aura un seul exemplaire de la recette pour les deux processus, de même dans une situation analogue en machine, le programme lui-même pourra se trouver en un seul exemplaire en mémoire, pourvu que d'une part le code ne se modifie pas lui-même, d'autre part qu'il permette d'accéder à des zones de données différentes suivant que les instructions sont exécutées pour le compte de l'un ou l'autre des processus. On dit alors que le programme est réentrant.
L'indépendance entre les processus conduit parfois à introduire la notion de processeur virtuel. Chaque processus se voit attribuer un tel processeur virtuel qu'il possède en propre, et qui exécute les instructions du programme pour le compte du processus. Le système implante ces processeurs virtuels en attribuant le (ou les) processeur réel alternativement aux différents processeurs virtuels. Chaque processus avance au rythme de son processeur virtuel, induisant un temps virtuel, qui correspond au temps d'utilisation du processeur virtuel. Il correspond aussi à la fraction du temps réel pendant laquelle un processeur réel a été attribué au processeur virtuel. En conséquence un temps virtuel de 1 seconde correspond à l'exécution d'un certain nombre d'instructions par le processeur réel, que ces instructions aient été réalisées en une seule fois, ou en plusieurs tranches de temps.

12.2. La hiérarchie de processus

Nous avons dit que la représentation d'un processus comportait trois composantes principales, le programme, les variables et l'état du processeur.

12.2.1. Un nombre fixe de processus banalisés

Certains systèmes créent, à l'initialisation, un nombre fixe de processus banalisés.
Ces processus peuvent être attachés chacun à un terminal particulier. Le programme du processus consiste alors en l'identification de l'usager (login) suivi de l'exécution de ses commandes et se termine par la remise dans l'état initial lors de la déconnexion (logout).
Ces processus peuvent être attachés à un terminal au moment de la demande de connexion depuis de terminal. Le programme est alors comparable au précédent. Le nombre de processus peut être inférieur au nombre de terminaux potentiels, entraînant un refus de connexion si aucun processus n'est disponible. Il est également possible d'allouer le processus à un couple de fichiers <entrée, sortie>, qui simulent le comportement d'un usager à son terminal, ce qui fournit une certaine forme de traitement par lot.
Ces processus peuvent être alloués à la demande pour une exécution d'une commande unique. Le programme, qui décrit leur comportement, est une boucle infinie qui consiste en l'attente d'une commande, suivie de son exécution. Suivant le cas, plusieurs processus pourront être alloués à un même usager en fonction de ses besoins. Cependant, il est nécessaire de disposer de plus de processus que dans les cas précédents, et la représentation de la partie propre de leur état occupe de la place en mémoire.

12.2.2. La création dynamique de processus

La création statique de processus banalisés présente l'inconvénient d'occuper de la place en mémoire, même lorsqu'ils ne sont pas utilisés. La deuxième méthode consiste à fournir une opération de création dynamique de processus ainsi qu'une opération de destruction de ces processus. Un telle opération de création doit permettre d'initialiser l'état du nouveau processus. Elle doit donc définir d'une part le programme (ou la suite d'instructions) décrivant l'activité du processus, ainsi que l'état initial de ses données, de ses variables et des registres du processeur. Elle doit donc se présenter sous la forme suivante:
	id := créer_processus (programme, contexte)
où les paramètres définissent cet état initial. La valeur retournée éventuellement par cette opération permet d'identifier le processus qui a été ainsi créé. Cette dernière valeur n'a d'intérêt que si le processus créateur peut agir ultérieurement sur le processus créé au moyen d'autres opérations.
La plupart des systèmes qui permettent la création dynamique de processus considèrent que la relation entre le processus créateur et le processus créé est importante, ce qui conduit à structurer l'ensemble des processus sous la forme d'un arbre, et à maintenir cette structure. Lors de la création de processus, le processus créé est relié automatiquement comme fils du processus créateur. Si un processus est détruit, on peut parcourir sa descendance pour la détruire au préalable. Lors de la fin d'exécution normale d'un processus P, deux solutions sont possibles:

12.2.3. L'exemple de Unix

Dans le système Unix, la création dynamique de processus est simplifiée à l'extrême, puisqu'il s'agit de créer un processus qui est une exacte copie de celui qui demande la création. Aucun paramètre n'est donc nécessaire, puisque le programme est le même, et que le système réalise une copie des données, variables et registres du processus demandeur pour créer le nouveau processus. La seule distinction entre le processus créateur et le processus créé, réside dans la valeur retournée par la fonction de création. Le processus créateur reçoit l'identité du processus créé, alors que ce dernier reçoit la valeur 0. Par ailleurs, le système structure les processus en arbre. Considérons le petit morceau de programme suivant:
	id_fils := fork ();  { création du fils }
	si id_fils = 0 alors { il s'agit du processus fils }
				 sinon { il s'agit du processus père }
La fonction fork () crée le processus fils comme copie conforme du processus père. Les deux processus repartent donc après l'appel de la fonction, avec le même programme, et leur propres zones de données et de variables identiques à ce moment. La mémorisation du résultat dans la variable id_fils signifie la mémorisation dans un emplacement mémoire propre à chacun d'eux. Notons que, puisque le fils obtient une copie des données du père, celui-ci peut lui transmettre tout ce qu'il désire à ce moment. Par la suite, les deux processus sont effectivement indépendants et n'ont pas de données communes à l'exception des objets externes.
Si le processus père se termine avant le processus fils, le fils est rattaché au processus racine de façon à conserver la structure d'arbre. Le processus père peut attendre la terminaison d'un de ses fils par la fonction (en langage C):
	id_fils := wait (&status);
qui retourne le numéro d'un processus fils qui est terminé, en mettant dans la variable status un code indiquant la façon dont ce processsus s'est terminé. Si aucun fils n'est terminé alors qu'il y en a encore d'actifs, le processus père est mis en attente d'une terminaison de l'un de ses fils. S'il n'y a plus de processus fils, la fonction retourne la valeur -1.
Par ailleurs le système Unix fournit une fonction exec qui permet à un processus de changer le programme en cours d'exécution. Cette fonction n'affecte évidemment que le processus demandeur. Elle change l'état du processus en remplaçant le code des instructions, en supprimant les données, variables et registres qui étaient relatifs à l'ancien programme et en les initialisant pour le nouveau. La combinaison de la fonction fork et de cette fonction exec par le processus fils permet de réaliser la fonction créer_processus mentionnée ci-dessus.

12.3. La notion de ressources

Si nous reprenons notre exemple du processus de réalisation du gâteau d'anniversaire, ce processus ne peut s'exécuter que si les denrées nécessaires pour la recette sont disponibles, ainsi que les ustensiles de cuisines correspondants. Ce sont des ressources dont a besoin ce processus. Si on désire faire un deuxième gâteau identique, un deuxième processus doit être créé pour le réaliser. Cet autre processus aura besoin également de ressources, et peut alors être en conflit avec le premier processus s'il n'y a pas assez de certaines ressources. Par exemple, si la cuisson doit être faite au four, il est possible que le four soit assez grand pour admettre les deux gâteaux en même temps. Le batteur ne pourra pas par contre être utilisé en même temps par les deux processus. Le processeur étant unique (l'informaticien) ne pourra pas commencer de battre la pâte pour le compte de l'un des processus, si l'autre possède la ressource batteur et ne l'a pas restituée (lavé et essuyé les embouts). Par contre, l'horloge peut être partagée par tous les processus éventuels gérés par l'informaticien.
On appelle ressource toute entité dont a besoin un processus pour s'exécuter. Il en est ainsi du processeur physique, de la mémoire, des périphériques. Il en est également ainsi des données dont a besoin le processus et qui seraient momentanément indisponibles. Il en est enfin ainsi de l'événement de fin d'exécution d'un processus Unix pour le processus père qui l'attend. Pour chaque ressource, une caractéristique importante est le nombre de processus qui peuvent utiliser la ressource au même moment.
Pour les ressources physiques critiques, nous avons déjà proposé une première solution pour le processeur, qui peut être généralisée dans certains cas, en remplaçant une telle ressource physique par une ressource virtuelle. Dans ce cas, le système met en œuvre des mécanismes de coopération entre les processus, qui sont cachés dans le système et donc invisibles des processus eux-mêmes, pour leur permettre de “faire comme si” ils avaient la ressource pour eux seuls. On peut ainsi simuler un nombre quelconque d'imprimantes virtuelles sous forme de fichiers sur disque dont les impressions sont réalisées successivement par le système sur une même imprimante physique.

12.4. Les états d'un processus

Lorsqu'un processus n'a pas toutes les ressources dont il a besoin pour s'exécuter, il est nécessaire de le bloquer en attendant que ces ressources soient disponibles. La figure 12.1 montre les différents états que peut prendre un processus, lorsqu'il existe. Lors de la création il est mis, en général, dans l'état bloqué, en attendant qu'il ait toutes les ressources dont il a besoin initialement. Sa destruction peut subvenir dans n'importe quel état à la suite d'une décision interne s'il est actif, ou externe s'il est dans un autre état. Dans ce cas, il faut récupérer toutes les ressources qu'il possédait.

Fig. 12.1. Les principaux états d'un processus.
Il est assez naturel de considérer de façon particulière la ressource processeur physique. Pour le moment c'est encore une ressource chère, qu'il vaut mieux ne pas gaspiller. Par ailleurs il est inutile de la donner à un processus à qui il manque une autre ressource, puisque ce processus ne peut alors évoluer: le processeur ne pourrait que constater ce manque et attendre la disponibilité de cette ressource (c'est ce que l'on appelle l'attente active). C'est pourquoi on distingue trois états:
La transition 1 survient lorsque, le processus étant actif, il exprime le besoin de disposer d'une nouvelle ressource. Cette expression peut être explicite, sous forme d'une demande au système, ou implicite, sous forme d'un accès à cette ressource non allouée. Le processus doit évidemment disposer du processeur pour pouvoir exprimer ce besoin. Il peut se faire que la transition vers l'état bloqué par manque de ressources, entraîne que le système lui retire d'autres ressources qu'il possédait, en plus de la ressource processeur physique, comme par exemple de la mémoire centrale. En général, on ne lui retire que des ressources chères, qu'il est relativement facile et peu coûteux de lui restituer ultérieurement, dans l'état où elles étaient au moment où on les lui a enlevées. Il est assez naturel de considérer de façon particulière la ressource processeur physique. L'état bloqué correspond alors au manque d'une ressource autre que le processeur physique, et l'état prêt correspond alors au manque de la seule ressource processeur.
La transition 4 est la conséquence d'un événement extérieur au processus. Celui-ci était bloqué en attente d'une ou plusieurs ressources. Lorsque le système constate qu'il a pu lui allouer toutes les ressources (autres que le processeur) dont il a besoin, il le fait passer dans l'état prêt. La disponibilité de la ressource attendue se présente, en général, sous l'une des deux formes suivantes:
Les transitions 2 et 3 sont sous la responsabilité de la partie du système qui alloue le processeur physique. C'est à ce niveau que s'implante la notion de processeur virtuel dont nous avons déjà parlé. Certains systèmes n'implantent pas la transition 2. Il s'ensuit que lorsqu'un processus est actif, il le reste jusqu'à ce qu'il soit terminé ou qu'il ait besoin d'une ressource qu'il n'a pas. L'allocation du processeur consiste alors à choisir un processus parmi les processus prêts, et à le faire passer dans l'état actif. Cela peut entraîner que les processus prêts peuvent attendre très longtemps pour devenir actif, si le choix a conduit à rendre actif un processus qui calcule sans exprimer le besoin de nouvelles ressources. Ceci peut être évité par le biais de la transition 2 qui doit être la conséquence d'une action extérieure au processus actif. C'est en général un des rôles attribués à l'interruption d'horloge de déclencher cette transition.
Il est important de noter que ces transitions ne sont pas instantanées, puisqu'il s'agit de ranger en mémoire l'état du processeur relatif à l'ancien processus actif, et de restituer l'état du processeur avec celui relatif au nouveau processus actif, une fois le choix de celui-ci effectué. L'exécution de ces transitions consomme du temps de processeur physique, qui n'est pas utilisé par les processus eux-mêmes. C'est ce que l'on appelle la déperdition (overhead) résultant de la gestion des processus. Tous les systèmes visent à réduire cette déperdition dans des limites raisonnables. C'est le prix à payer pour une meilleure fonctionnalité.

12.5. Conclusion

+ Un processus est l'entité dynamique qui correspond à l'exécution d'une suite d'instructions définie par le programme. Le processeur est l'entité qui exécute les instructions pour le compte du processus.
+ Lorsqu'un processus n'a pas le processeur, il ne peut évoluer, et son état doit être conservé en mémoire. Lorsqu'il a le processeur, son état évolue, et est représenté en partie par les registres du processeur.
+ Il peut y avoir un nombre fixe de processus dans un système, ou bien les processus peuvent être créés dynamiquement. Dans ce cas, l'ensemble des processus est structuré en arborescence suivant la relation créateur-créé.
+ La création d'un processus doit définir le programme qu'il doit exécuter, et le contexte initial de cette exécution. Sur Unix, le processus créé est une copie conforme du processus créateur.
+ Une ressource est une entité dont a besoin un processus à un instant donné pour s'exécuter. Elle est caractérisée, en particulier, par le nombre de processus qui peuvent l'utiliser au même moment. Ce nombre peut être illimité, et aucun contrôle n'est nécessaire lors de l'allocation.
+ Si le nombre de processus pouvant utiliser une ressource est borné, il faut contrôler lors de l'allocation que la borne n'est pas atteinte. Lorsque cette borne est égale à 1, on dit que la ressource est critique, et que les processus sont en exclusion mutuelle pour cette ressource.
+ Les ressources induisent trois états sur les processus: l'état actif lorsque le processus a toutes ses ressources, l'état prêt lorsque le processus a toutes ses ressources sauf le processeur et l'état bloqué lorsqu'il lui manque des ressources autres que le processeur.
+ Le passage de l'état actif à l'état bloqué est en général volontaire, ou à la suite d'une réquisition. Le passage de l'état bloqué à l'état prêt est dû à une action externe au processus.