« INFO502 : Systèmes d'exploitation » : différence entre les versions

De Wiki du LAMA (UMR 5127)
Aller à la navigation Aller à la recherche
Ligne 441 : Ligne 441 :
Si on ne tue pas le processus fils, le père n'exécutera jamais le <tt>printf(" >> Mon fils est mort...);</tt>.
Si on ne tue pas le processus fils, le père n'exécutera jamais le <tt>printf(" >> Mon fils est mort...);</tt>.


Si on tue le père, le fils disparaît.
Si on tue le père, le fils ne disparait pas : il devient fils de <tt>init</tt> et continue son éxecution.


===États d'un processus===
===États d'un processus===

Version du 9 octobre 2009 à 13:20

Ce wiki est un complément de cours pour le cours « info-502 : systèmes d'exploitation ». La participation au wiki est fortement encouragée.

Pour pouvoir modifier les pages, inscrivez-vous (lien en haut à droite) pour obtenir un login et mot de passe. (Choisissez un login du style PrenomNom...)

Je vous conseille d'aller lire ce guide pour vous familiariser avec les wikis.


Exercice : si vous n'en avez pas, créez-vous un compte et essayez de modifier cette page (correction de fautes d'hortographe, rajout de détails, mise en page, ...)

Vous pouvez aussi utiliser la page de discussion pour ... discuter. (Ou poser des questions, faire des commentaires etc.)



Auteur du cours

Nouvelles

Supports

TD

TP




Introduction

-- ...

Repères historiques

Voici quelques évènements clés dans l'histoire des systèmes d'exploitation. Pour plus de détails, ou pour des compléments sur l'histoire de l'informatique, je vous renvoie sur Wikipedia : "History of operating systems" et "History of computing".

Les premiers ordinateurs ne comportaient pas vraiment de système d'exploitation : c'étaient des opérateurs humains qui géraient tout. (C'était juste après la seconde guerre mondiale, l faut savoir que les langages de programmation n'existaient même pas...) La petite histoire (??) raconte qu'à un moment, les processus pour l'ordinateur de l'université de Cambridge étaient accrochés sur une corde à linge et que c'était la couleur des pinces à linges qui donnait la priorité. (??)

Le passage à la notion de système d'exploitation c'est faite graduellement pour répondre à la complexité de plus en plus grande des ordinateurs et aux demandes des utilisateurs.

Les premier systèmes d'exploitation datent probablement de 1956. Ils permettaient simplement d'exécuter un nouveau programme lorsque le précédent était terminé. Les ordinateurs d'IBM de la famille System/360 ont ensuite eu toute une série de systèmes d'exploitation plus ou moins similaires : OS/360, DOS/360 (rien à voir avec MS-DOS), TSS/360... C'est à cette époque qu'est apparu la notion de multiprogrammation (possibilité d'avoir plusieurs processus entrelacés.) C'est également au début des années 60 qu'on a commencé à voir des systèmes avec temps partagé : plusieurs utilisateurs pouvaient utiliser un ordinateur. Le système Multics a été, à ce niveau comme à d'autres, assez révolutionnaire. Multics a été utilisé jusqu'en 2000 !

Multics n'était par contre pas approprié pour les mini-ordinateurs où le nombre d'utilisateur est relativement restreint. En 1969, Ken Thomson a développé une variante simplifié de Multix : Unix...

Ce n'est qu'un peu plus tard (1979) que la première version de DOS (86-DOS ou QDOS) apparaît. 86-DOS sera racheté par Microsoft un 1980...


Liens et compléments :

Vue d'ensemble

Ce cours reste assez généraliste et essaie de regarder les principales fonction d'un système d'exploitation. Les systèmes de type Unix (Linux) sera un exemple privilégié. À cause de leur complexité intrinsèque, les évolutions modernes (architectures multicoeur ou multiprocesseur, ...) seront en partie ignorée dans un premier temps.

Les problèmes fins de communication entres processus ne seront que très peu abordés, car ils font l'objet d'un cours séparé (info604) pour la filière info.

Nous allons suivre l'ordre classique consistant à regarder :

  1. les processus
  2. la mémoire
  3. les entrées / sorties

La fin du cours dépendra du temps restant...

Les processus

Un processus est simplement un programme « en exécution ». À tout instant, un ordinateur de bureau contemporain contient de nombreux processus qui doivent partager le (les) processeur(s) pour donner l'impression qu'ils s'exécutent « en même temps ». Un des rôles du système d'exploitation consiste à décider dans quel ordre les processus vont effectivement pouvoir utiliser le processus.

La mémoire

La mémoire est, comme le processeur, une ressource partagée par les différents processus. Il faut donc gérer la quantité de mémoire allouée et utilisée pour que les processus n'aient pas à s'occuper de ceci. Un des concepts fondamentaux est celui de mémoire virtuelle. Ceci permet d'offrir un espace mémoire pour chacun des processus de manière transparente.

Les entrées / sorties

Un ordinateur n'est pas (plus) un système indépendant du reste du monde : il interagit avec l'extérieur à travers des périphériques d'entrées/sorties (clavier / écran / souris / ...). La gestion de ces périphériques pose un ensemble de problèmes que nous aborderons brièvement...

...

Une fois ces notions vues, nous regarderons peut-être les problèmes de sécurité, de multimédia ou des architectures multiprocesseurs.


Préliminaires

Quoi ?

Les ordinateurs, ou plus simplement les microcontroleur sont des circuit électronique avec une puce « processeur ». Le développement d'applications n'est pas facile si on se place au niveau électronique et que l'on parle directement au CPU. Pour faciliter la vie des programmeurs, plusieurs couches d'abstraction sont nécessaires. La première se place au niveau matériel et s'occupe directement des problème électroniques ou de très bas niveau. Il s'agit du firmware. Il répond par exemple aux questions comme :

  • qu'elle est l'amplitude des signaux envoyés par l'horloge ?
  • est-ce que l'UC possède un pipeline ?
  • ...

Un firmware est donc forcement très lié au matériel sur lequel il tourne.

La couche suivante est le système d'exploitation à proprement parler. Dans le cas que vous connaissez le mieux (ordinateur personnel), le système d'exploitation fournit une interface pour :

  • exécuter un programme
  • exécuter plusieurs programmes « en même temps »
  • gérer les ressources (temps processeur, mémoire disponible, entrées / sorties)
  • les opérations sur les fichiers
  • offrir des garanties de sécurité


Où ?

On trouve des système d'exploitation partout :

  • dans les ordinateurs personnels (Linux, BSD, MacOS, Solaris, Windows, ChromeOS (?))
  • dans les PDA (PalmOS, ...)
  • dans les téléphones portables (Android, OpenMoko,Symbian,)
  • console de jeux
  • lecteur MP3 (Rockbox, ...)
  • les microcontroleurs « avancés »

Comment ?

Langage de programmation

Le système d'exploitation se situe entre le firmware (très bas niveau) et l'utilisateur. La nécessité d'accéder à ces ressources de très bas niveau implique que les langages de haut niveaux (Java, Ada, Python, ...) ne sont pas du tout adaptés à l'écriture des systèmes d'exploitation. Le langage le plus utilisé reste à ce jours le langage C : Linux, BSD, MacOS, Windows, ... sont tous écrits pour leur plus grande partie en C.

Vous avez normalement un cours de C en parallèle, et les TP utiliseront le langage C. (La référence sur le langage C reste à mon goût le livre de Kernighan et Ritchie : « the C Programming language ». (Disponible en francais à la BU sous le titre « Le langage C : norme Ansi ».) Il existe de nombreux autres livres / documents sur la programmation C comme le polycopié de Bernard Cassagne « introduction au langage C ».

Les premiers systèmes d'exploitation étaient écrits directement en langage machine, ou bien en langage d'assembleur ; et les systèmes actuels comportent encore quelques parties de très bas niveau en langage d'assemblage...

Notion d'appel système, norme POSIX

La programmation de haut niveau en C est assez différentes de la programmation système. De nombreuses fonctions C utilisent en fait des « appels système », c'est à dire des appels de fonctionnalités propres du système d'exploitation. Les appels système typiques sont :

  • les fonctions relatives aux fichiers (création, suppression, modification...)
  • les fonctions relatives à la gestion de la mémoire
  • les fonctions relatives aux processus (fork)
  • ...

De nombreux système d'exploitation proposent une interface POSIX (« Portable Operating System Interface for Unix »). Cette norme définit entre autres un ensemble de fonctions pour utiliser les appels systèmes. Le nombre de ces fonctions est relativement faible : une centaine ; et chacune correspond en gros à un appel système. Certaines de ces fonctions ne sont pas définies directement dans le système d'exploitation, mais dans des librairies. Pour Linux, il s'agit en général de la librairie Glibc (GNU C Library).

Par exemple, la fonction open (qui permet d'ouvrir un fichier) correspond exactement à un appel système ; alors que la fonction malloc (qui permet d'allouer dynamiquement de la mémoire dans le tas) peut générer plusieurs appels système (brk et sbrk).

Pour visualiser les appels systèmes effectués par un programme sous Linux, vous pouvez utilisez l'utilitaire strace ou ltrace. Par exemple, dans le shell :

 $ strace -e trace=file ls -l 2> trace

permet de récupérer tous les appels systèmes relatifs aux fichiers lors de l'appel à ls -l. La liste des appels système est redirigée dans le fichier trace qui contiendra par exemple des lignes telles que

lstat64("Desktop", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
lgetxattr("Desktop", "security.selinux", 0x8cc08e0, 255) = -1 ENODATA (No data available)

dénotant ainsi un appel aux appels système lstat64 et lgetxattr...

Les principaux systèmes compatibles POSIX sont :

  • Linux
  • BSD (et variantes)
  • Mac OS X
  • Solaris

De nombreux appels systèmes sont disponibles directement (sans avoir besoin d'écrire un programme C) à travers le shell. Exécuté dans un terminal, le shell permet, en première approximation, de passer directement des commandes au système d'exploitation. (La norme POSIX définit également un ensemble de fonctions du shell. Il est donc relativement aisé de changer de système d'exploitation tant qu'on reste dans les systèmes compatibles POSIX.)


Windows d'un autre coté utilise l'interface Win32 API, qui définit plusieurs milliers de fonctions. La plupart de ces fonctions peuvent utiliser plusieurs appels système...

Les systèmes d'exploitation qui utilisent l'API Win32 sont :

  • Windows, depuis Windows95.

Il est possible d'installer un environnement compatible POSIX sur une machine Windows grace à Cygwin. (C'est d'ailleurs fortement conseillé si vous ne voulez pas installer une version de Linux ou BSD sur votre ordinateur personnel...)

Mode noyau / mode utilisateur

La partie fondamentale du système d'exploitation est le noyau (kernel en anglais). C'est la couche de plus bas niveau.

Les fonctionnalités de très bas niveau offertes par le noyau peuvent facilement planter l'ordinateur. Il faut donc une politique de restriction pour que tous les programmes ne puissent y accéder dans leur totalité. On parle de

  • mode noyau
  • mode utilisateur

Dans le mode utilisateur, on ne peut accéder à ces fonctionnalités qu'a travers les appels systèmes ; dans le mode noyau, on peut tout faire... Bien entendu, un appel système doit passer momentanément en mode noyau pour pouvoir exécuter les commandes pertinentes, puis il repasse automatiquement en mode utilisateur.

La définition d'un appel système pourrait donc être « fonctionnalité atomique nécessitant un passage en mode noyau ».

Noyau, noyau monolithique, micro-noyau

La partie principale du système d'exploitation. C'est lui qui gère les ressources, les entrées / sorties, la communication entre processus, ... Les deux architectures principales pour un noyau sont :

  • noyau monolithique
  • micro-noyau

La plupart des systèmes actuels sont basés sur un noyau monolithique (Linux, BSD, Mac OS X, Solaris, Windows).

Les noyaux monolithiques sont parfois décrits comme « sans vraie structure » : une grosse soupe où cohabitent toutes les fonctionnalités du système d'exploitation. L'idée d'avoir un système unique qui gère tout est effectivement peu attirante et peux poser quelques problèmes de génie logiciel... Un noyau monolithique est donc la couche entre le matériel et les programmes utilisateurs. Pour éviter de compiler des noyaux énormes, les noyaux monolithiques actuels utilisent en plus la notion de module. Ce sont des morceaux du noyau qu'on peut charger ou décharger au besoin. Si l'interface est connue, ces modules peuvent éventuellement être en binaire, ce qui permet d'utiliser des modules propriétaires avec un noyau libre. Sous Linux, la commande

$ lsmod

permet d'obtenir la liste des modules chargés dans le noyau.

Les micro-noyaux d'un autre coté sont basés sur la remarque que seule une toute petite partie du noyau accède effectivement au matériel. Seule cette petite partie nécessite donc des privilèges et effectue des appels système. Le reste du système d'exploitation peut être programmé en mode utilisateur, « au dessus » de cette partie. Par exemple, le micro noyau offrira la possibilité de charger un nouveau processus, mais l'ordonnanceur (qui choisit quel processus devra être chargé) ne fera pas parti du micro-noyau. On parle parfois de services / serveurs au dessus du micro-noyau. Ceci permet d'avoir un noyau bas niveau beaucoup plus petit. Bien que ceci soit une bonne idée, et que les micro-noyaux puissent fournir de meilleurs garanties de sécurité, peu de noyaux sont effectivement des micro-noyaux. Un des problèmes est qu'un tels micro-noyau est un peu plus lent que ses cousins monolithiques. La raison principale est que dans le cas d'un micro-noyau, un appel système nécessite en fait des communications inter-processus. Andrew Tanenbaum est un fervent défenseur des micro-noyaux.

Une autre architecture possible est d'utiliser des couches successives, chacune avec des privilèges réduits. Le système Multics avait cette architecture. Par exemple, dans le système THE (1965), les couches étaient les suivantes :

  1. noyau bas niveau, multiprogrammation
  2. allocation de la mémoire pour les processus
  3. IPC (communication inter processus)
  4. entrées / sorties (buffer, etc.)
  5. programmes utilisateurs (compilation, exécution, ...)
  6. utilisateur (Dijkstra a annoté la description de cette couche par « not implemented by us »)


Une dernière catégorie de noyau est la catégorie des machine virtuelles. Ces noyaux sont un peu à part, car il s'agit d'émuler un système d'exploitation ou du matériel particulier à l'intérieur d'un autre système d'exploitation ou matériel. Le mode noyau de la machine virtuelle est en fait dans le mode utilisateur du noyau original...

Les processus

Introduction

Allez hop commençons par une petite définition du type wikipédia :

Un processus (en anglais, process), en informatique, est défini par :

  • un ensemble d'instructions à exécuter (un programme) ;
  • un espace mémoire pour les données de travail ;
  • éventuellement, d'autres ressources, comme des descripteurs de fichiers, des ports réseau, etc.

Un ordinateur équipé d'un système d'exploitation à temps partagé est capable d'exécuter plusieurs processus de façon « quasi- simultanée ». Par analogie avec les télécommunications, on nomme multiplexage (A. Tanenbaum parle de multiprogrammation) ce procédé. S'il y a plusieurs processeurs, le système essaie de répartir les processus de manière équitable sur les processeurs disponibles.

Le processus doit être imaginé comme quelque chose qui prend du temps, donc qui a un début et (parfois) une fin. De nombreux processus ne se terminent normalement jamais : système d'exploitation, serveurs web, serveurs mail, ... D'autres processus plus légers qui ne se terminent pas sont les daemon (acronyme a posteriori de "disk and execution monitor") du monde Unix.

Certains processus sont démarrés très tôt lors de la séquence de boot. Le système d'exploitation est un tels processus. Sinon, les processus sont normalement démarrés grâce à un appel système à partir d'un autre processus. L'utilisateur peut par exemple demander explicitement un nouveau processus (sous POSIX, grâce à un fork puis exec) ; mais la plupart du temps, il le fera à travers une application, par exemple en double-cliquant sur l'icône d'un programme dans l'explorateur de fichiers.

Le système d'exploitation est chargé d'allouer les ressources (mémoires, temps processeur, entrées/sorties) nécessaires aux processus et d'assurer que le fonctionnement d'un processus n'interfère pas avec celui des autres (isolation).


Pas sûr d'avoir tout compris ? Ce n'est pas grave, essayons autrement.

Définition : un processus est un programme en train de s exécuter.

Cette définition implique en particulier que un programme (navigateur Firefox par exemple) peut correspondre à

  • zéro processus si on ne l'a pas lancé,
  • un processus si on l'a lancé,
  • plusieurs processus si on l'a lancé plusieurs fois.

(Remarque : pour un programme comme Firefox, il y a fort à parier que l'exécution d'une seule instance de Firefox génère de multiples sous-processus...)


Que se passe-t'il quand je démarre une application?

L'ordinateur à une nouvelle tâche à accomplir, les instructions correspondantes sont donc envoyées au processeur. Comme il à plusieurs choses à faire le choix des instructions à exécuter n'est pas forcement simple. Il faut faire en sorte de partager le processeur entre les différentes tâches de manière équitable : c'est la fameuse gestion des processus.
Ceci permet de réaliser plusieurs activités en ~même temps~. Grâce à la gestion des processus on peut regarder un film (obtenu par des moyens tout à fait légaux cela va de soit), tout en jouant à la dame de pique.
(et dire que certain(e)s affirment que l'homme est monotâche)


Plusieurs processus indépendants peuvent être actif en même temps, mais sauf si l'ordinateur possède suffisamment de processeurs, on ne peut pas les exécuter simultanément. L'impression que plusieurs programmes s'exécutent en même temps vient simplement du fait que le processeur alterne rapidement entre eux. Comme le temps d'exécution d'une instruction par le processeur est très court, de l'ordre de la nano-seconde, le processeur peut réaliser plusieurs centaines de millions d'instructions par seconde. Ainsi, une tranche de temps de quelques fractions de seconde, partagée entre plusieurs processus, donne à l'échelle macroscopique l'illusion de la simultanéité.


En résumé : un système d'exploitation doit la plupart du temps traiter plusieurs tâches en même temps, et comme il n'a, en général, qu'un seul processeur, il devra contourner ce problème grâce à un pseudo-parallélisme. Il traite une tâche à la fois, s'interrompt et passe à la suivante. La commutation de tâches étant très rapide, il donne l'illusion d'effectuer un traitement simultané.


La table des processus

L'information sur les processus est conservé par le système dans une structure de données appelée table des processus. Cette table contient toutes les informations necessaires à la gestion des processus :

  • état du processus
  • PID
  • droits
  • répertoire de travail
  • pointeur vers la pile
  • priorités
  • ...

Sous Linux, on peut accéder à ces information à travers le système de fichiers virtuel "Procfs". Il suffit d'aller dans le repertoire /proc/.

Création d'un processus

Pour les système POSIX, la création d'un processus est très simple : on utilise la fonction fork(). En C, le prototype de la fonction se trouve dans le fichier d'entêtes unistd.h.

Cette fonction prend 0 argument et permet de faire la chose suivante : elle crée un nouveau processus qui est une copie conforme du processus appelant. La seule différence visible entre l'ancien et le nouveau processus est que les processus ont un numéro (PID) différent. Pour le processus appelant, il peut obtenir le PID du nouveau processus en regardant la valeur de retour de fork. Pour le nouveau processus, cette valeur vaudra 0, et il devra utiliser la fonction getpid() pour obtenir son numéro.

Le processus appelant est appelé le père alors que le nouveau processus est appelé le fils.

Notez que l'espace mémoire du processus est dupliqué plutôt que partagé. Ceci veut dire que les processus ne peuvent pas communiquer en utilisant leur variables... Par contre, les descripteurs de fichiers du fils sont hérités directement de ceux du père ; ils peuvent donc essayer de communiquer en passant par ces fichiers. (Mais cela pose de nombreux problèmes de synchronisation.)

Voici par exemple un petit programme C qui lance un processus fils:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {

  int pid ;
  int pid_fils ;

  pid = getpid();
  printf("> Le processus %i démarre...\n", pid);

  pid_fils = fork();

  if(pid_fils) {
    printf(" >> Je suis le père (pid du fils = %i).\n",pid_fils);
  } else {
    printf(" >> Je suis le fils.\n");
  }

  return(0);

}

Après compilation, voici un exemple d'exécution du programme résultant :

$ ./pere_fils
> Le processus 8248 démarre...
 >> Je suis le fils.
 >> Je suis le père (pid du fils = 8249).

Voici un autre exemple d'exécution :

$ ./pere_fils
> Le processus 8269 démarre...
 >> Je suis le père (pid du fils = 8270).
 >> Je suis le fils.

Notez que les numéros sont différents, et que l'ordre d'affichage n'est pas le même. Cela vient de l'ordonnancement des deux processus qui peut différer.


La technique consistant à faire

pid = fork();
if (pid) { /* le père */
  ...
} else { /* le fils */
  ...
}

montre que malgré le fait que les processus soient identiques à l'origine, on peut faire beaucoup de chose.

Remarque : fork() n'est pas directement un appel système, mais une fonction qui utilise l'appel système clone(). Cet appel système ne fait pas partie de la norme POSIX...


La deuxième technique utilisée dans les systèmes POSIX consiste à utiliser fork() pour dupliquer le processus, puis à complètement remplacer le fils par un nouveau processus. La fonction correspondante est la fonction execv :

pid = fork();
if (pid) { /* le père */
  ...
} else { /* le fils */
  execv("/usr/bin/...", argv);
  ...
}

La fonction execv prend 2 arguments : une chaîne contenant le chemin d'accès à un programme, et un tableau d'arguments à donner au programme. (C'est un peu plus compliqué que ça : le premier élément du tableau est le nom du programme, et le tableau doit se terminer par un pointeur NULL.)

Une dernière fonction importante est la fonction wait(pid) qui permet à un processus d'attendre l'arrêt du processus pid.

-- ...


L'API WIN32 possède de nombreuses fonctions avec des fonctionnalités similaires. Par exemple, la fonction CreateProcess permet de créer un nouveau processus (comme un fork suivi d'un exec), mais elle nécessite ... 10 arguments !


Arborescence des processus dans les systèmes POSIX

Nous avons vu que la fonction fork() permet de créer un processus fils. Le père connaît le PID de son fils : il lui est donné par la fonction fork(). Comme le fils peut lui même créer des processus fils, on obtient rapidement une arborescence de processus.

Vous pouvez visualiser cette arborescence à partir d'un terminal avec la commande pstree (l'option -p permet de demander l'affichage des PID)

$ pstree -p
init(1)─┬─acpid(25425)
        ├─atd(14365)
        ├─console-kit-dae(3632)─┬─{console-kit-dae}(3633)
        │                       ├─{console-kit-dae}(3641)
...
        ├─cron(14315)
        ├─dbus-daemon(15590)
        ├─dbus-daemon(3511)
...
        ├─getty(4095)
        ├─getty(4096)
        ├─getty(4097)
        ├─getty(4098)
        ├─getty(4099)
        ├─gpm(3528)
...
        ├─kded4(15769)
        ├─kdeinit4(15766)───klauncher(15767)
        ├─kdm(15564)─┬─Xorg(15566)
        │            └─kdm(15573)───sh(15608)─┬─conky(15751)
        │                                     ├─fluxbox(15703)───firefox-bin(6063)─┬─{firefox-bin}(6064)
        │                                     │                                    ├─{firefox-bin}(6065)
...
        │                                     ├─ssh-agent(15698)
        │                                     ├─unclutter(15742)
        │                                     ├─wicd-client(15749)
        │                                     └─xscreensaver(15683)
...
        ├─rsyslogd(6850)─┬─{rsyslogd}(6140)
        │                └─{rsyslogd}(6141)
        ├─screen(4427)─┬─bash(4428)───pstree(9307)
        │              ├─bash(4429)───man(7952)───pager(7965)
        │              ├─bash(4430)───vi(30568)
        │              ├─bash(18395)───vi(28512)
        │              └─mutt(8763)
...

Le processus init est l'ancêtre de tout les processus, il a toujours 1 comme PID.


Remarque : sous Windows, les processus ne sont pas organisé en arborescence...

Mort d'un processus dans un système POSIX

Un processus peut se terminer pour plusieurs raisons :

  • volontairement, grâce à la fonction exit() (POSIX) ou ExitProcess() (Windows),
  • à cause d'une erreur,
  • en recevant un signal d'un autre processus, grâce à la fonction kill() (POSIX) ou TerminateProcess() (Windows).

Lorsqu'un processus se termine, plusieurs choix sont possibles :

  • que fait-on de ses fils ?
  • que fait-on de son père ?

Linux fait la chose suivante : quand un processus s'arrête,

  • tous ces fils s'arrêtent,
  • son père reçoit un signal le prévenant que son fils vient de mourir.

Pour que le père puisse recevoir le signal correspondant, il faut qu'il utilise la fonction wait() ou waitpid(). Tant que le parent existe et qu'il n'a pas reçu le signal disant qu'un de ces fils est mort, le processus fils n'est pas entièrement supprimé : il devient un zombie. (Il n'occupe plus vraiment de place en mémoire, mais le système garde juste suffisamment d'information pour pouvoir prévenir le père si celui ci demande l'état du fils.)


Remarque : les processus Windows ne sont pas organisés sous forme d'arborescence. Chaque processus peut surveiller l'état d'un autre processus, s'il connaît son PID. Il n'y a donc pas de notion de processus zombie sous Windows.


Voici un petit programme C qui crée un processus fils, ne fait rien pendant 20 secondes, puis demande l'état de son fils :

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
  int pid ;
  int pid_fils ;
  int w;

  pid = getpid();
  printf("> Le processus %i démarre...\n", pid);
 
  pid_fils = fork();

  if(pid_fils) {
    printf(" >> Je suis le père (pid du fils = %i).\n",pid_fils);
    printf(" >> Je suis le père et j'attends un peu.\n");
    sleep(20);
    printf(" >> Je suis le père et je demande l'état de mon fils.\n");
    wait(&w);
    printf(" >> Mon fils est mort (signal %i).\n", w);
    while(1) sleep(1);
  } else {
    printf(" >> Je suis le fils.\n");
    while (1) sleep(1);
    exit(0);
  }
  return(0);
}

Si on tue le processus fils pendant que le père ne fait rien, le processus fils devient un zombie. Dès que le père fait le wait, il reçoit la valeur du signal qui a terminé le processus et le zombie disparaît.

Si on ne tue pas le processus fils, le père n'exécutera jamais le printf(" >> Mon fils est mort...);.

Si on tue le père, le fils ne disparait pas : il devient fils de init et continue son éxecution.

États d'un processus

On peut imaginer un SE dans lequel les processus pourraient être dans trois états :

  • en cours d'exécution,
  • bloqué : il attend un événement extérieur pour pouvoir continuer (par exemple une ressource; lorsque la ressource est disponible, il passe à l'état "prêt")
  • prêt : suspendu provisoirement pour permettre l'exécution d'un autre processus.

Diagrammedétatdunprocessus.png

La mémoire

Les entrées / sorties




Références