Vous êtes sur la page 1sur 121

SYSTÈMES D'EXPLOITATION

M1
Ingénieur Docteur Lamine BOUGUEROUA
lamine.bougueroua@efrei.fr
Plan
 Références
 Systèmes d’exploitation
 Mécanismes de bases
 Processus
 Création d’un processus
 Communication inter-processus : tubes et signaux
 Synchronisation élémentaire
 Synchronisation
 Producteurs /consommateurs
 Dîner des philosophes
 Lecteurs / rédacteurs

2
Références
Références

 Documentations :
 Tanenbaum, A.S., Woodhull, A.S.
Operating Systems : Design and Implementation.
Pearson Education, 21 nov. 2011 - 1080 pages

 Joëlle Delacroix
Linux : Programmation Système et réseau
Dunod, 21 janv. 2016 - 384 pages

 Christophe Blaess
Programmation système en C sous Linux: Signaux, processus, threads, IPC et
sockets
Editions Eyrolles, 7 juil. 2011 - 964 pages

3
Système d’exploitation

ensemble de programmes
Noyau
OS

embarqué Shell

Temps réel ? Kernel

communication inter-processus

Gestion de mémoire Supports réseaux


Ordonnanceur

4
Système d’exploitation
 Un Système d’Exploitation SE (Operating System ou OS) est un ensemble de
programmes responsables de la liaison entre les ressources matérielles d’un
ordinateur et les applications informatiques de l’utilisateur (traitement de
texte, jeu vidéo…).

 Il fournit aux programmes d'application des points d’entrée génériques pour


les périphériques.

 Gère les ressources comme le(s) processeur(s), la mémoire, les timers


etc.. Interface Applications

Applications
Interface langages
langages
Services du SE

Système d’exploitation
Interface machine physique

Machine physique

5
Système d’exploitation
 Le SE est un ensemble de programmes « systèmes » qui interfacent le
matériel d’une plateforme (ordinateur PC ou système embarqué) et
l’utilisateur ou les utilisateurs.

 Il permet de construire au-dessus du matériel une machine «virtuelle »


conviviale qui cache les spécificités matérielles de la plateforme.

 Le SE permet d’accéder de façon homogène via une interface de


programmation applicative API (Application Programming Interface) au
matériel et notamment aux Entrées/Sorties.

 Gère les processus et les travaux des différents utilisateurs

 En résumé : une machine virtuelle ou machine étendue masquant les détails du


matériel : par exemple masquage de la façon dont les périphériques sont interfacés,
ce qui rend les paramètres physiques ne sont plus visibles.

6
Système d’exploitation
 Les débuts !

7
Système d’exploitation
 Les débuts !

 Les premiers systèmes permettaient aux ordinateurs de n’exécuter qu’une seule


opération ou tâche à la fois (traitement par lot mono-utilisateur).
 Les travaux étaient soumis à un centre de service informatique au moyen de
cartes perforées (délais rencontrés : plusieurs heures voire des jours avant
de recevoir le résultat du programme).
 Les systèmes suivants permettent de partager les ressources de l’ordinateur
entre de nombreuses opérations pour permettre leur exécution en
« simultané » (multiprogrammation).
 Dans les années 60, on introduit des systèmes à temps partagé où des
utilisateurs branchés à leur propre terminal partagent une seule machine en
même temps.
 L’informatique personnelle a vu le jour en 1977 (Apple); le prix plus abordable
des ordinateurs a permis d’en faire une utilisation personnelle ou pour le
travail. En 81, IBM lançait le « IBM personal Computer ». Du jour au
lendemain, ce fut un bouleversement.
 Pour faciliter la communication entre les ordinateurs d’un réseau, des
machines appelés serveurs de fichiers, offrent un stock de programmes et de
données dont peuvent se servir les ordinateurs clients répartis sur le réseau.

8
Système d’exploitation
 Windows

9
Système d’exploitation
 UNIX : Plus d’un demi-siècle d’existence!!

10
Système d’exploitation
 UNIX :
1969 : premier système Unix écrit par Ken Thompson aux Bell Labs d'AT&T, en assembleur
sur PDP-7, puis en langage "B". A la base appelé Unics (jeu de mot formé à partir de Multics,
gros système développé fin 60 par le MIT et General Electric)

1973 : Dennis Ritchie, qui a inventé le langage "C", récrit Unix en C avec Thompson (vers.4).
Rend possible et entraîne le portage d'Unix sur de nombreuses machines (sources d'Unix
distribués à de nombreuses universités et sociétés commerciales). Une 3e personne, Brian
Kernighan, contribue également fortement aux premiers développements d'Unix.

1976 : publication de la version 6 du Manuel Unix ("manuel du programmeur")

1977 : divergence en 2 grandes familles Unix :


AT&T : System III, puis System V (1990 : release 4, abrégé SVR4)
Uni. de Californie à Berkeley : BSD (Berkeley Software Distribution, act.vers.4.4), originalités
: vi, propre système de fichier, C-shell, gestion virtuelle mémoire, job contrôle, liens
symboliques, TCP/IP...

1980 : Bjarne Stroustrup (Bell Labs) définit le langage C++ (extension "objet" du C)

11
Système d’exploitation
 UNIX :
1981 : Xenix : version populaire d'Unix pour PC, dérivée de AT&T version 7

1987 : apport du MIT en matière de graphique et fenêtrage : X-window version 11 (act. release 6,
abrégé X11R6).
Puis formation de 2 Consortiums rivaux :
OSF (Open Software Fundation) (DEC, HP, IBM...) : fenêtrage Motif...
Unix International (AT&T, Sun...) : système de fenêtrage OpenLook/OpenWindows

1990 : création par AT&T de USL (Unix System Laboratories) qui reprend les activités Unix

1993 : AT&T vend USL à Novell (juin) qui donne ensuite les droits "Unix" à l'organisation de
standardisation X/Open (octobre)

1993 : réunification des familles Unix (sous la pression de la concurrence de "Windows NT" ):
convergence des constructeurs vers Unix SVR4 qui se dote de la plupart des extensions BSD,
standardisation appels systèmes (POSIX), environnement de bureau CDE (Common Desktop
Environment). Nouveau rôle de l'OSF. Ralliement de Sun à Motif puis à l'OSF.

1994 : Linux, Unix gratuit pour plateforme Intel 486 (Linus Torvalds)

12
Système d’exploitation
Quel système d’exploitation choisir ?

(source : CMP, EE times)


13
Système d’exploitation
Quel système d’exploitation choisir ?

UBM embedded market study - 2019 14


Système d’exploitation
Quel système d’exploitation choisir ?

UBM embedded market study - 2019 15


Système d’exploitation

UBM embedded market study - 2019

16
Système d’exploitation

UBM embedded market study - 2019

17
Système d’exploitation

UBM embedded market study - 2019

18
Système d’exploitation

UBM embedded market study - 2019

19
Système d’exploitation
UNIX : caractéristiques

 système d'exploitation "ouvert", non propriétaire


 en grande partie (>95%) écrit en langage évolué (langage C)
 disponible sur toutes les plateformes, y compris PC
 multitâches et multi-utilisateur
 interactif, temps partagé, mais offrant aussi des extensions pour le "temps réel"
 multiprocesseurs
 gestion de mémoire virtuelle, mémoire protégée
 compatibilité totale entre fichiers et périphériques
 facilement extensible et modifiable (ajout de commandes...)
 "case sensitive", c'est à dire qu'il distingue les majuscules des minuscules (tant
au niveau des commandes/options que des noms de fichiers) contrairement à
VMS ou MS-DOS
("ls -l" n'a pas même effet que "ls -L")
 grande richesse d'outils et fonctionnalités (résultant d'un effort de
développement collectif, contrairement aux autres systèmes d'exploitation)
 …..

20
Système d’exploitation
POSIX

 Norme POSIX :
 POSIX est l'acronyme de Portable Operating System Interface ou
interface portable pour les systèmes d'exploitation.
 Norme a été développée par l'IEEE (Institute of Electrical and
Electronic Engineering) et standardisée par l'ANSI et ISO.
 Proposée initialement pour les systèmes Unix, mais d’autres systèmes
sont conformes à POSIX (ex. Windows NT).
 Standard définie depuis 1988 (connue par : IEEE 1003)

 Objectif : obtenir la portabilité des logiciels au niveau de leur code


source.

21
Système d’exploitation
POSIX

 Les principaux sous-standard POSIX :


 IEEE 1003.2 (1992) : Interface applicative pour le shell et applications
annexes. Définit les fonctionnalités du shell et commandes annexes
pour les systèmes de type UNIX.

 IEEE 1003.1b (1993) : Interface de programmation (API) temps réel.


Ajout du support de programmation temps réel au standard précédent.
(appelé également POSIX.4)

 IEEE 1003.1c (1995) : Interface de programmation (API) pour le


multithreading.

22
Système d’exploitation
POSIX

 Temps réel dans le système d’exploitation :


 IEEE 1003.1b : the ability of the operating system to provide a
required level of service in a bounded response time

23
Système d’exploitation
 Fonctionnement de base :

24
Système d’exploitation
 Vue générale :

25
Processus : définition
 Un processus est un programme en cours d’exécution.

 Unix est système d’exploitation multitâches, il peut exécuter


plusieurs programmes en parallèles

 L’exécution d’un processus doit progresser séquentiellement

Le processus système (daemons) : assure des services


généraux accessibles à tous les utilisateurs du système. Le
propriétaire est le root et il n’est sous le contrôle d’aucun
terminal.

Le processus utilisateur : dédié à l’exécution d’une tâche


particulière. Le propriétaire est l’utilisateur qui l’exécute et il est
sous le contrôle du terminal à partir duquel il a été lancé.

26
Processus : rôle
 À faire plusieurs activités ”en même temps”.

Exemples:
 Faire travailler plusieurs utilisateurs sur la même machine.
 Chaque utilisateur a l’impression d’avoir la machine à lui tout seul.
 Compiler tout en lisant son mail

Problème:
 Un processeur ne peut exécuter qu’une seule instruction à la fois.

But:
 Partager un (ou plusieurs) processeur entre différents processus.

27
Processus : exemple
 Exemple sous Unix

 Xclock : lancement d’un processus « Xclock » en 1er plan


(foreground)
 Xclock& : lancement d’un processus « Xclock » en arrière plan
(background)
 La commande « Ctrl + c » : arrêt définitif d’un processus
 La commande « Ctrl + z» : suspend l’exécution d’un processus
 bg : affiche les processus en arrière plan
 fg : affiche les processus en 1er plan

28
Processus : exemple
 Exemple sous Unix

 Le programme s’exécute au 1er plan


 La commande « Ctrl + c » : arrêt définitif d’un processus
 La commande « Ctrl + z» : suspend l’exécution d’un processus
 Le programme est suspendu
 bg : le bascule en arrière plan
 fg : le bascule au 1er plan
 Le programme s’exécute en arrière plan
 fg : le bascule au 1er plan

29
Processus : organisation
 Toute exécution d’un programme déclenche la création d’un
processus dont:

la durée de vie = la durée d’exécution

 Le système alloue à chaque processus un numéro d’identification


unique : PID (Process Identifier)

 Chaque processus appartient à un utilisateur et à un groupe et à


les droits qui leur sont associés

 Tout processus est créé par un autre processus : son père

 Sous shell, un processus est créé pour exécuter les commandes

 Le shell est le processus père de toutes les commandes

30
Processus : organisation
 La visualisation des processus se fait avec la commande :
ps

 Les options les plus intéressantes


-a : affichage de tous les processus
-f : affichage détaillé

 kill : envoie un signal à un processus connaissant son PID


pour l’arrêter

31
Processus : organisation
 Signification des différentes colonnes

32
Processus : organisation
 La liste donnée par ps a un défaut : elle est statique (elle ne bouge
pas). Or, votre ordinateur, lui, est en perpétuel mouvement. De
nombreux processus apparaissent et disparaissent régulièrement.
 Comment avoir une liste régulièrement mise à jour ?
 Avec la commande top

On navigue à l'intérieur de ce programme en appuyant sur certaines touches du clavier :


q : ferme top.
h : affiche l'aide, et donc la liste des touches utilisables.
B : met en gras certains éléments.
f : ajoute ou supprime des colonnes dans la liste.
F : change la colonne selon laquelle les processus sont triés. En général, laisser le tri par
défaut en fonction de %CPU est suffisant.
u : filtre en fonction de l'utilisateur que vous voulez.
k : tue un processus, c'est-à-dire arrête ce processus.
On vous demandera le numéro (PID) du processus que vous voulez tuer.
s : change l'intervalle de temps entre chaque rafraîchissement de la liste (par défaut, c'est
toutes les trois secondes).

33
Processus : états
 Quand un processus s’exécute, il change d’état.

 Dans un cas général, chaque processus peut se trouver en


état :
D’exécution: instructions en cours d’exécution (occupation du
CPU).
D’attente: Le processus attend qu’un événement se produise.
Prêt: Le processus attend d’être affecté à un

 Ils existent d’autres états : Suspendu, Zombie, ….

34
Processus : hiérarchie
 Dans certains SE, lorsqu’un processus crée un autre
processus, les processus parent et enfant continuent d’être
associés d’une certaine manière. Le processus enfant peut lui
même créer plusieurs processus, formant une hiérarchie de
processus

 Un processus a un seul parent et peut avoir 0 ou plusieurs


fils

 Fork est le seul appel système de création de processus

35
Processus : hiérarchie

36
Processus : implémentation
 Point de vue conceptuel:
chaque processus possède son processeur virtuel.

 Réalité:
Le processeur bascule constamment d’un processus à
l’autre.

 Ce basculement rapide est appelé multiprogrammation.

 Lorsque le processeur passe d’un processus à un autre, la


vitesse de traitement d’un processus donné n’est pas
uniforme et probablement non reproductible si le même
processus s’exécute une nouvelle fois.

37
Processus : implémentation
Chaque processus est représenté dans le SE par un PCB
(Process Control Block).

38
Processus : changement de contexte
 Le contexte d’un processus est l’ensemble des informations
dynamiques qui représente son état d’exécution

 La commutation de contexte est le mécanisme qui permet


au SE de remplacer le processus élu par un autre processus
éligible

 Pour passer d’un processus à un autre, il faut


 Sauvegarder l’état de l’ancien processus
 Charger l’état sauvegardé pour le nouveau processus

39
Processus : changement de contexte

40
Processus : gestion
 La norme Posix définit un nombre relativement petit d’appels
système pour la gestion de processus :

 pid_t fork() : Création de processus fils.


 int execl(), int execlp(), int execvp(), int execle(), int execv() : Les
services exec() permettent à un processus d’exécuter un programme
différent.
 pid_t wait() : Attendre la terminaison d’un processus.
 void exit() : Finir l’exécution d’un processus.
 pid_t getpid() : Retourne l’identifiant du processus.
 pid_t getppid() : Retourne l’identifiant du processus père.

41
Processus : création
Int fork()
Cette primitive crée un nouveau processus (appelé fils) qui est une
copie exacte du processus appelant (processus père). La différence
est faite par la valeur de retour de fork() :
 qui est égale à zéro chez le processus fils,
 et elle est égale au pid du processus fils chez le processus père,
 la primitive renvoie -1 en cas d'erreur

Création de processus
#include<unistd.h>
#include<sys/types.h>
pid_t fork(void);

Valeur de retour du fork()


0 pour le processus fils
Strictement positive pour le processus père
Négative si la création de processus a échoué

42
Processus : création
Le processus fils hérite du processus père :
 la priorité
 la valeur du masque
 le propriétaire
 les descripteurs des fichiers ouverts
 le pointeur de fichier (offset) pour chaque fichier ouvert
 le comportement vis à vis des signaux

43
Processus : création
#include <unistd.h>
#include <sys/types.h>

int main() {
pid_t fils_pid ;
fils_pid=fork ();

if (fils_pid==0)
printf("Je suis le fils avec pid%d\n",getpid());
else if (fils_pid > 0)
printf("Je suis le pere avec pid%d\n", getpid());
else
printf("Erreur dans la creation du fils\n");
}

44
Processus : création
 Après le fork les deux processus, père et fils, ont la même
image mémoire et les mêmes fichiers ouverts.

 Généralement, le processus enfant exécute l’appel système


execv pour exécuter un nouveau programme
int execv(char *ref,char *argv[])

 Une fois qu’un processus est créé, le père et le fils disposent


de leur propre espace d’adressage: s’il arrive que l’un des
processus modifie un mot dans son espace d’adressage, le
changement n’est pas visible par l’autre.

45
Processus : création
 Exemple execv
Hello.c
#include<stdio.h>
$ cc hello.c -o hello main(int argc, char *argv[], char *envp[]){
printf("Filename: %s\n",argv[0]);
printf("%s %s\n",argv[1],argv[2]);
}

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

main() {
char *temp[] = {NULL,"hello","world",NULL}; $ Filename: hello
$ hello world
temp[0]="hello";
execv("hello", temp);
printf("error");
}

46
Processus : terminaison
 Un processus peut se terminer suite à l’un des 4 événements:
 Sortie normale, lorsque le processus a terminé sa tâche (sous
Unix par l’appel système exit)
 Sortie suite à une erreur (e.g. division par 0, inexistence d’un
fichier passé en paramètre)
 Tué par un autre processus (sous Unix par l’appel système
kill)

 Toutes les ressources du processus sont libérées par le SE.

47
Processus : priorité
 Dans certains cas, il peut être sensé d’attribuer plus ou moins
de temps de calcul à un processus.
 C’est ce que permet la commande nice, qui donne une priorité
réduite ou augmentée à un programme. Elle lui attribue une
priorité allant de 20 (très faible) à -20 (très élevée).
 Par défaut, les programmes sont lancés avec une priorité 0.

 L’exemple suivant montre comment lancer un script de


sauvegarde avec une priorité plus faible, afin qu’il ne dérange
pas les autres processus (la sauvegarde peut sans problème
durer deux ou trois secondes supplémentaires) :
$ nice -n 10 sauvegarde

48
Processus : priorité
 renice permet de modifier la priorité d’un programme déjà lancé.
 Elle prend pour paramètre un identifiant de processus, obtenu avec
top ou ps.
 Vous trouverez plus de détails à propos de renice dans man renice.
 top permet également, grâce à la commande R, de modifier
interactivement la priorité d’un processus.
 Seul root peut lancer des processus avec une priorité supérieure à 0
(donc négative).

49
Processus : ordonnancement
 Dans un système multitâche plusieurs processus peuvent être en
exécution simultanément, mais le processeur ne peut, à un moment
exécuter qu’une instruction (d’un programme) à la fois. Le
processeur travaille donc en temps partagé.

 L’ordonnanceur (scheduler) est le module du SE qui s’occupe de


sélectionner un processus à exécuter parmi ceux qui sont prêts.

 Un processus passe entre les diverses files d’attente pendant sa


durée de vie (file d’attente des processus prêts, file d’attente sur une
E/S,...).

 Le SE doit sélectionner les processus à partir de ces files d’attente


d’une manière quelconque.

• Le processus de sélection est mené à bien par le scheduleur


approprié.

50
Processus : ordonnancement
Classification des processus
 Tributaire des E/S: dépense la plupart du temps à effectuer des
E/S plutôt que des calculs
 Tributaire de la CPU: génère peut fréquemment des requêtes
d’E/S, i.e., passe plus de temps à effectuer des calculs

Les scheduleur à long terme (ou scheduleur de travaux)


 Sélectionne le processus qui doit aller dans la file de processus
prêts.
 Contrôle le degré de multiprogrammation (le nombre de
processus dans la mémoire).
 Réalise un bon mélange de processus tributaires de la CPU et
tributaires des E/S.

51
Processus : ordonnancement
Les scheduleur à court terme (scheduleur de la CPU):
 Choisit parmi les processus prêts celui à être exécuté (alloue la
CPU).
 Appelé assez fréquemment: doit être TRÈS rapide

Possibilité d’introduire un scheduleur à moyen terme


 Idée clé: Il peut être avantageux de supprimer des processus de
la mémoire et réduire ainsi le degré de multiprogrammation.
 Plus tard, un processus peut être réintroduit dans la mémoire et
son exécution peut reprendre là ou elle s’était arrêtée.
 Ce schéma est appelé swapping (transfert des informations de
la mémoire principale à la mémoire auxiliaire et vice-versa).

52
Processus : ordonnancement
 Les algorithmes d’ordonnancement peuvent être classés en
deux catégories:

1. Non préemptif :
 Sélectionne un processus, puis le laisse s’exécuter jusqu’à
ce qu’il bloque (soit sur une E/S, soit en attente d’un autre
processus) où qu’il libère volontairement le processeur.

2. Préemptif:
 Sélectionne un processus et le laisse s’exécuter pendant
un délai déterminé.

53
Processus : ordonnancement
 Les divers algorithmes de scheduling de la CPU possèdent des propriétés
différentes et peuvent favoriser une classe de processus plutôt qu’une
autre

Critères utilisés:
 Utilisation CPU: maintenir la CPU aussi occupée que possible
 Capacité de traitement (Throughput): nombre de processus terminés
par unité de temps
 Temps de restitution (Turnaround time): temps nécessaire pour
l’exécution d’un processus.
 Temps d’attente : quantité de temps qu’un processus passe à
attendre dans la file d’attente des processus prêts.
 Temps de réponse: temps écoulé à partir du moment où on lance une
requête jusqu’à l’arrivée de la première réponse.

Temps de restitution= temps passé à attendre d’entrer dans la


mémoire + temps passé à la file d’attente des processus prêts +
temps passé en exécution (CPU) + temps passé à effectuer des E/S

54
Processus : ordonnancement
 Scheduling du premier arrivé, premier servi (FCFS: First-come, first-
served)

 Gérée avec une file d’attente FIFO.


 Une fois que la CPU a été allouée à un processus, celui-ci la garde
jusqu’à la fin ou E/S.
 Incommode pour le temps partagé où il est important que chaque
utilisateur obtienne la CPU à des intervalles réguliers
 Temps moyen d’attente: généralement n’est pas minimal et peut varier
substantiellement si les temps de cycles de la CPU du processus varient
beaucoup
 Effet d’accumulation(Convoy effect): provoque une utilisation de la CPU
et des périphériques plus lent que si on permettait au processus le plus
court de passer en premier

55
Processus : ordonnancement
Scheduling du travail plus court d’abord (SJF:Shortest job first)

 Associe à chaque processus la longueur de son prochain cycle de


CPU.
 La CPU est assignée au processus qui possède le plus petit cycle.
 Si deux processus possèdent la même longueur, le FCFS est utilisé.
 Deux schémas: Non préemptif et préemptif.

56
Processus : ordonnancement
Priorité
 Une priorité est associée à chaque processus.
 Les processus ayant la même priorité sont Schedulés dans un ordre
FCFS.
 SJF: priorité déterminée par la durée du prochain cycle de CPU.
 Problème (Famine ou blocage indéfinie): processus avec des
basses priorités peuvent attendre indéfiniment(ne jamais être
exécutés).
 Solution (Vieillissement): technique qui consiste à augmenter
graduellement la priorité des processus en attente

57
Communication Inter-processus
 Communication interprocessus - IPC (Inter Processus
Communications)

 Communication bidirectionnelle

Processus A IPC Processus B

 Communication unidirectionnelle

IPC 1

Processus A Processus B

IPC 2

58
Communication Inter-processus
 Communication interprocessus - IPC (Inter Processus
Communications)
 Le système d’exploitation doit fournir aux processus coopératifs
les moyens de communiquer entre eux par l’intermédiaire d’une
fonction de communication interprocessus.

 Plusieurs mécanismes de communication peuvent être


utilisés entre deux processus locaux :
 Les signaux
 La mémoire
 Les files de messages
 Les fichiers
 Les tubes sans noms (pipe)
 Les tubes nommés (named pipe)
 Les Sockets
 …

59
Communication Inter-processus
 Les problèmes de base de l’IPC

 Accès concurrent
 Le résultat dépend de l’ordonnacement
 Problème de cohérence des données partagés (objet critique)
 Synchronisation des processus

60
Communication Inter-processus
Accès concurrent
 Les conditions de concurrence (Race Conditions)
 Situation où plusieurs processus accèdent et manipulent en
concurrence les mêmes données et où le résultat de l’exécution
dépend de l’ordre dans lequel on accède aux instructions

 Comment éviter les race conditions?


 Interdire les processus de lire/écrire des données partagées au
même moment. (l’exclusion mutuelle)

61
Communication Inter-processus
Exclusion mutuelle

 Objet critique : Objet qui ne peut être accédé simultanément.


Comme par exemple, les imprimantes, la mémoire, les fichiers, les
variables

 Section critique: Ensemble de suites d’instructions qui opèrent sur


un ou plusieurs objets critiques et qui peuvent produire des résultats
imprévisibles lorsqu’elles sont exécutées simultanément par des
processus déférents.

62
IPC : exclusion mutuelle
 Structure générale d’un processus
 Section non critique.
 Demande d’entrée en section critique.
 Section critique.
 Demande de sortie de la section critique.
 Section non critique

 Conditions pour avoir une bonne solution:


 Deux processus ne peuvent être en même temps en section
critique.
 Aucune hypothèse ne doit être faite sur les vitesses relatives
des processus et sur le nombre de processeurs.
 Aucun processus suspendu en dehors d’une section critique ne
doit bloquer les autres processus.
 Aucun processus ne doit attendre indéfiniment avant d’entrer en
section critique.

63
IPC : Verrous
Verrous:
 Pour chaque ressource on utilise par exemple une Méthode de
Verrou : chaque ressource a deux états libre et occupé
 Une variable de verrou est utilisée afin d'entrer dans la section
critique

while (true)
{
while (VARIABLE_VERROUX == 1);
VARIABLE_VERROUX = 1;
// ...
// section critique
// ...
VARIABLE_VERROUX = 0;
// ...
// section critique terminé
// ...
}

64
IPC : Verrous
Verrous:
 Pour chaque ressource on utilise par exemple une Méthode de
Verrou : chaque ressource a deux états libre et occupé
 Une variable de verrou est utilisée afin d'entrer dans la section
critique

while (true) Problèmes :


{ attente active (spin waiting)
while (VARIABLE_VERROUX == 1);

VARIABLE_VERROUX = 1; possibilité que le processus perd


// ... la main et qu'un autre processus
// section critique entre alors en section critique.
// ...
VARIABLE_VERROUX = 0;
// ...
// section critique terminé
// ...
}

65
IPC : sémaphore
Sémaphores: ont été inventés en 1965 par Edsger Dijkstra
(mathématicien et informaticien néerlandais 1930 – 2002).

 variable entière qui représente le nombre d’autorisations d’accès


à une SC (section critique), elle est définis par un nom et une
valeur initiale :
Ex.: semaphore S=5;

On accède seulement à travers 2 opérations standards atomiques:


 P(S) ou down(S) - (Puis-je) : décrémente la valeur du sémaphore. Si
la valeur est négative, le processus appelant est mis en attente.
V(S) ou up(S) - (Vas-y) : incrémente la valeur du sémaphore. S’il y a
des processus bloqués (par l’opération P(S)), l’un d’entre eux sera
choisi et redeviendra prêt.

66
IPC : sémaphore

l’exclusion mutuelle d’une section critique a++.

67
IPC : sémaphore
La valeur que les sémaphores prennent lors des appels des P (wait) et V
(signal), pour trois processus P0, P1 et P2 :

int a;
semaphore mutex = 1;

Processus Pi :
P(mutex);
a++;
V(mutex);

68
IPC : sémaphore
Exemple
 Soient trois processus concurrents P1, P2 et P3 qui partagent
les variables n et out. Pour contrôler les accès aux variables
partagées, un programmeur propose les codes suivants :

 Semaphore mutex1 = 1 ;
 Semaphore mutex2 = 1 ;
Code du processus p1 : Code du processus p2 : Code du processus p3 :
P(mutex1); P(mutex2); P(mutex1);
P(mutex2); out=out-1; n=n+1;
out=out+1; V(mutex2); V(mutex1) ;
n=n-1;
V(mutex2);
V(mutex1) ;

1. Cette proposition est-elle correcte? Sinon, indiquer parmi les 4


conditions requises pour réaliser une exclusion mutuelle
correcte, celles qui ne sont pas satisfaites?
2. Proposer une solution correcte
69
IPC : sémaphore
Exemple : réponses
1. Non, car si P2 est en section critique et P1 a exécuté
P(mutex1) alors P1 est bloqué et empêche P3 d’entrer en
section critique.
 Condition non vérifiée : un processus en dehors de sa section
critique bloque un autre processus.

2. Solution :

Processus P1
P(mutex1) ;
n=n-1 ;
V(mutex1) ;
P(mutex2) ;
Out = out +1 ;
V(mutex2) ;

70
IPC : sémaphore
Exemple 2

 Trois processus concurrents P1, P2 et P3 exécutent chacun le


programme suivant : Pi () // i = 1,2,3
{ int n=0;
while(true)
printf("cycle %d de %d", n++, i);
}
Synchronisez les cycles des processus à l’aide de sémaphores de
manière à ce que :
1. Chaque cycle de P1 s’exécute en concurrence avec un cycle de
P2
2. Le processus P3 exécute un cycle, lorsque P1 et P2 terminent
tous les deux l’exécution d’un cycle.
3. Lorsque P3 termine un cycle, les processus P1 et P2 entament
chacun un nouveau cycle et ainsi de suite ..

71
IPC : sémaphore
Exemple 2
Semaphore S1=1, S2=1, S13=0, S23=0 ;
P1 ()
P(S1); 1. Chaque cycle de P1 s’exécute en
printf("cycle %d de %d", n++, i); concurrence avec un cycle de P2
V(S13) ;

P2 ()
P(S2); 2. Processus P3 exécute un cycle,
printf("cycle %d de %d", n++, i); lorsque P1 et P2 terminent tous les
V(S23) ; deux l’exécution d’un cycle.

P3 () .
P(S13);
P(S23); 3. Lorsque P3 termine un cycle, les
printf("cycle %d de %d", n++, i); processus P1 et P2 entament
V(S1) ; chacun un nouveau cycle et ainsi
V(S2) ; de suite ..

72
IPC : Signaux
 Signal :
 une interruption asynchrone
 envoyée d’un processus envoyeur vers un processus
destinataire (ou pour lui-même) via le noyau
 notifié l’apparition d’un événement
 Le SE interrompt l'exécution du processus destinataire du signal
 Le processus destinataire
 ignore le signal si il le souhaite
 lance l’exécution de la routine de traitement du signal reçu si
elle existe
 lance l’exécution de la routine de traitement des signaux par
défaut, dans le cas contraire
 reprend l’exécution du traitement interrompu après le traitement
du signal

73
IPC : Signaux
 Exemples de Signaux :

2 SIGINT interruption du processus (Ctlr-c)


3 SIGQUIT arrêt du processus (Ctrl-\)
4 SIGILL détection d’un instruction illégale
5 SIGABRT arrêt par la fonction abort
9 SIGKILL terminaison du processus
10 SIGBUS erreur de bus
11 SIGSEGV violation de la mémoire
13 SIGPIPE écriture dans un tube sans lecteur
14 SIGALARM fin de temporisation
16 SIGUSR1 libre d’utilisation
17 SIGUSR2 libre d’utilisation
18 SIGCHLD terminaison du fils
21 SIGURG événements réseau

74
IPC : Signaux
 Signaux :
 commande kill -l

75
IPC : Signaux

 Gestion des signaux :


 La synchronisation de processus est nécessaire pour :
• attendre la valeur de retour d’un processus
• contrôler un processus (l’arrêter, l’endormir, …)
 Les signaux sont utilisés pour synchroniser des processus
• exemple de l’attente d’une valeur de retour :

76
IPC : Signaux
 Gestion des signaux :
 Emission des signaux
• pid > 0 : processus d’identité pid ;
• pid = 0 : tous les processus du même groupe ;
• pid < -1 : tous mes processus du groupe pid .

> Kill –INT pid


Ou Ctrl-c

Processus émetteur
Signaux pendants:
Signaux émis mais pas
encore pris en compte.

Kill (pid, SIGUSR1);

Processus émetteur Processus récepteur

77
IPC : Signaux
 Gestion des signaux :
 Capter un signal dans une fonction :

La fonction d’interception d’un signal peut être changée :

int sigaction( int sig, struct sigaction* p_action, struct sigaction* p_action_anc ) ;

• sig : signal à intercepter


• p_action : précise le nouveau comportement
• p_action_anc : comportement à restaurer lors du retour de la
fonction

struct sigaction{
void (*sa_handler)(); /* pointeur vers la fonction de capture */
sigset_t sa_mask; /* signaux à bloquer */
int sa_flag;
};

78
IPC : Signaux
 Gestion des signaux :
 Capter un signal dans une fonction :
 Exemple d’un programme qui ne peut plus être arrêté avec Ctrl-c :

#include <stdio.h>

void controle( int sig ){


if( sig == SIGINT ) {
printf( "Ha!,Ha!,Ha!, même pas mal \n" );
}
}

int main(){
struct sigaction action;
action.sa_handler = controle;
sigaction( SIGINT, &action, NULL );
while( 1 );
}

79
IPC : Signaux
 Gestion des signaux :
 Il n’y a pas de file de signaux =>
• être prêt à recevoir un signal par la mise en attente d’un processus
• blocage des signaux pour retarder leurs délivrances.

bloquer les signaux tant qu’on est pas prêt à les recevoir :
int sigprocmask( int op, sigset_t* p_ens, sigset_t* p_ens_anc );

• op précise quel est le nouveau masque:


• SIG_SETMASK : *p_ens ;
• SIG_BLOCK : *p_ens + *p_ens_anc ;
• SIG_UNBLOCK : *p_ens_anc - *p_ens.
• les signaux SIGKILL, SIGCONT et SIGSTOP ne peuvent pas être
bloqués.

 Pour connaître la liste des signaux bloqués :


int sigpending( sigset_t* p_ens );

80
IPC : Tubes
 IPC par Tubes (pipes)
 Le tube (pipe) permet la communication entre un processus
et un de ses descendants.

 La nature d’un pipe est de type FIFO.

 Un pipe est unidirectionnel : un processus peut soit lire, soit


écrire dans le pipe.

 L’information disparaît après lecture.

 Le pipe est un objet de type fichier (il n’est possible de l’ouvrir


avec open).

 Un processus ne peut utiliser que les tubes qu’il a créés lui-


même par la primitive pipe ou qu’il a hérités de son père
grâce à l’héritage des descripteurs à travers fork et exec.

81
IPC : Tubes
 IPC par Tubes (pipes)

Ouverture d’un pipe Héritage d’un pipe

Liens – Redirection de la sortie standard de A


d’un pipe dans le tube et de l’entrée standard de B
dans le tube, et fermeture des descripteurs
inutiles.

82
IPC : Tubes
 Lecture : read
int nb_lu;
nb_lu = read(p[0], buffer, TAILLE_READ);

 Comportement de l’appel :

 Si le tube n’est pas vide et contient taille caractères :

 lecture de nb_lu = min(taille, TAILLE_READ) caractères.

 Si le tube est vide

 Si le nombre d’écrivains est nul alors c’est la fin de fichier et nb_lu est
nul.

 Si le nombre d’écrivains est non nul :

o Si lecture bloquante alors sommeil

o Si lecture non bloquante alors en fonction de l’indicateur :


_NONBLOCK nb_lu= -1 et errno=EAGAIN.

_NDELAY nb_lu = 0.
83
IPC : Tubes
 Ecriture : write
int nb_ecrit;
nb_ecrit = write(p[1], buf, n);

 L’écriture est atomique si le nombre de caractères à écrire est inférieur à


PIPE_BUF, la taille du tube sur le système. (<limits.h>).

 Si le nombre de lecteurs est nul : envoi du signal SIGPIPE à l’écrivain.

 Sinon

o Si l’écriture est bloquante, il n’y a retour que quand les n caractères ont
été écrits dans le tube.

o Si écriture non bloquante :

 Si n > PIPE_BUF, retour avec un nombre inférieur à n


(éventuellement -1 )

 Si n ≤ PIPE_BUF et si n emplacements libres, écriture nb_écrit = n,


sinon retour -1 ou 0.
84
IPC : Tubes
 Exemple
#include <stdio.h>
#include <sys/signal.h>
main(){ int fils1, fils2, n, m, p[2]; char buf[5];
pipe(5);
if (fils1=fork()==0){
printf(“je suis le fils qui écrit \n”);
printf(“voici 5 caractères sur le pipe \n”);
write(p[1],”ABCDE”,5);
printf(“fin de transfert sur le pipe \n”);
exit(3);
}
else{
if (fils2=fork()==0){
printf(“je suis le fils qui lis \n”);
read(p[0],buf,5);
printf(“voici le message lu sur le pipe \n”);
write(1,buf,5); /* affichage sur output standard*/
printf(“\n”);
exit(3);
}else{
printf(“ Le processus père est terminé \n”);
}
}

85
IPC : Tubes nommés
 IPC par Tubes nommés (named pipe)
 Les tubes nommés sont des tubes (pipe) qui existent dans le système
de fichiers, et donc peuvent être ouverts grâce à une référence.

 Il faut préalablement créer le tube nommé dans le système de fichiers,


grâce à la primitive mknod (mkfifo);

 avant de pouvoir l’ouvrir avec la primitive open.

int mknod(reference, mode | S_IFIFO,0);


• mode est construit comme le paramètre de mode de la fonction open

• L’ouverture d’un tube nommé se fait exclusivement soit en mode


O_RDONLY soit en mode O_WRONLY.

En POSIX, un appel simplifié :


#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *ref, mode_t mode);

86
IPC : Tubes nommés
 IPC par Tubes nommés (named pipe)
 L’opération d’ouverture sur un tube nommé est bloquante en lecture. Le
processus attend qu’un autre processus ouvre la fifo en écriture.

 L’ouverture en écriture est aussi bloquante, avec attente qu’un autre


processus ouvre la fifo en lecture.

 L’ouverture bloquante se termine de façons synchrone pour les deux


processus.

 En mode non bloquant (O_NONBLOCK, O_NDELAY), seule l’ouverture


en lecture réussit dans tous les cas.

87
IPC : Tubes nommés
 IPC par Tubes nommés (named pipe)
 Suppression d’un tube nommé : utilisation de rm ou unlink ne fait que
détruire la référence, le tube n’est réellement détruit que lorsque son
compteur de liens internes et externes est nul.

 Une fois que tous les liens par référence sont détruits, le tube nommé
devient un tube ordinaire

 Un pipe nommé :

 est accessible par les processus n’ayant pas de lien de parenté

 subit les mêmes règles qu’un fichier ordinaire

88
IPC : Tubes nommés
 IPC par Tubes nommés (named pipe)
 Exemple : écriture non bloquante

#include
ouverture bloquante pour L/E
<stdio.h>
#include <sys/signal.h>
open(/uo/tcom/mypipe,
#include <fcntl.h> O_RDWR, 2)

main(){ int d,i,n; char


Ouverture non buf[2000];
bloquante pour L/E
d= open(“/home/doc/mypipe”, O_RDWR | O_NDELAY, 2);
if (d<0){
open(/uo/tcom/mypipe, O_RDWR | O_NDELAY,
printf(“ problème ouverture du pipe \n”);
2)
exit(0);
}

for (i=0;i<2000;i++) buf[i]=“S”;

if((n=write(d, buf, sizeof(buf)))>0){


printf(“écritue %d\n”,n);
exit(0);
}
}

89
IPC : Tubes nommés
 IPC par Tubes nommés (named pipe)
 Exemple : lecture bloquante

#include
ouverture bloquante pour L/E
<stdio.h>
#include <sys/signal.h>
open(/uo/tcom/mypipe,
#include <fcntl.h> O_RDWR, 2)

main(){ int d,i,n; char


Ouverture non buf[2000];
bloquante pour L/E
d= open(“/home/doc/mypipe”, O_RDWR, 2);
if (d<0){
open(/uo/tcom/mypipe, O_RDWR
printf(“ problème de lecture du pipe \n”);
| O_NDELAY, 2)
exit(0);
}

n = read (d, buf, 2000);

printf(“affichage du contenu du pipe %d\n”,n);


write(1, buf, n);
printf(“/n”);
}

90
Synchronisation
 L’appel système wait permet de synchroniser les processus
père et fils.

wait
(
adresse adr // int *
);
retour: // pid_t
Numéro pid du processus fils qui s’est terminé.

 adr est l’adresse où l’on peut récupérer la valeur de terminaison du


processus fils.
 Mettre NULL si on ne veut pas récupérer la valeur de terminaison.

91
Synchronisation
 Le principe de fonctionnement de wait est le suivant :

Cas 1 : lorsque le processus père fait un appel système wait


alors que son fils est en cours d’exécution, il est suspendu

92
Synchronisation
 Le principe de fonctionnement de wait est le suivant :

Cas 2 : lorsque le processus fils se termine avant que le


père ait fait un appel système wait.

93
Utilisation de l’appel système wait
• Exemple :

94
Utilisation de l’appel système wait
• Exemple :

Lorsqu’un processus se termine, ses régions CODE et DU sont détruites.


Seule subsiste sa région de DS.
On dit de façon imagée que le processus est un zombie.
Puisque la DS de 27574 existe toujours après la destruction du processus, le
processus père peut aller récupérer l’exit_code avec l’appel système wait.
95
Utilisation de l’appel système wait
• Dans le cas particulier où le processus fils ne se termine pas, le
déroulement est différent.
– Pour le constater, il suffit de « décommenter » l’instruction for(;;);
dans le code précédent.
– Le processus fils ne peut pas se terminer, il est nécessaire de le
détruire avec la commande kill -9 27574 exécutée dans un autre
terminal.

96
Utilisation de l’appel système wait
 En résumé :
– Lorsqu’un processus se termine normalement sur un exit ou un return:
 exit_code >= 256 OU exit_code = 0 (cas de exit(0); ou de return 0;)

– Lorsqu’un processus se termine anormalement parce que détruit par


un signal :
 exit_code = numéro du signal destructeur

 Cas particulier :
– lorsque la fin anormale est provoquée par un signal destructeur de
type C (Communication entre processus - Signaux), il y a création d’un
fichier core, l’exit_code est alors égal au numéro du signal + 128.

97
Utilisation de l’appel système waitpid
 L’appel système waitpid fait partie du standard POSIX, il est plus complet
que wait.

waitpid
(
désignation, // pid_t
adresse adr, // int *
option // int
);
retour: // pid_t
Numéro du processus fils qui s’est terminé (>0).
OU
0
OU
-1 si échec.

98
Utilisation de l’appel système waitpid
 Plusieurs appels sont possibles suivant les valeurs combinées des trois
paramètres.
– Si option = 0 et désignation > 0, alors attendre la fin du fils dont le pid
est désigné.
 m = waitpid (453, &p, 0);
 Attente de la fin du processus 453. Valeur de retour m=453.
– Si option = 0 et désignation < 0 (mais différente de -1), alors attendre
la fin d’un processus fils qui appartient au groupe dont le leader est
désigné.
 m = waitpid(-451,&p,0);
 Attente de la fin d’un processus fils qui appartient au groupe dont le
leader est le processus 451.
 Valeur de retour m=pid du processus fils qui s’est terminé.
– Si option = 0 et désignation = 0, alors attendre la fin d’un processus fils
appartenant au groupe de l’appelant.
 m=waitpid(0,&p,0);
 En retour m=pid du processus fils qui s’est terminé.

99
Utilisation de l’appel système waitpid
 Plusieurs appels sont possibles suivant les valeurs combinées des trois
paramètres.
– Si option = 0 et désignation = -1, alors attendre la fin d’un fils du
processus appelant.
 m=waitpid(-1,&p,0);
 En retour m=pid du processus fils qui s’est terminé.
 Cet appel est équivalent à m=wait(&p);

– Si option = WNOHANG (définie dans le fichier d’en-tête wait.h)


 Identique à option = 0 sauf que si aucun processus fils n’est
terminé au moment de l’appel système waitpid(); alors l’appelant
continue son exécution et la valeur de retour est égale à 0.

100
Synchronisation - Diner des philosophes
 Ce paradigme modélise l’ordonnancement des processus et
l’allocation des ressources critiques à ces derniers [Dij72].
 La résolution de ce problème met en œuvre 2 techniques d’utilisations
différentes des sémaphores: l’exclusion mutuelle classique mais aussi la
possibilité de bloquer un processus grâce à un sémaphore privé.

• N philosophes assis autour d’une table.


• Une fourchette entre chaque paire de voisins.
• Pour manger, un philosophe a besoin de deux fourchettes.
• Comment assurer que les philosophes vont pouvoir manger ?

Chaque philosophe passe par les états suivants :


• Penser (sans interaction avec les autres),
• Essayer de manger, et pour cela attraper une des
fourchettes à sa disposition, puis l’autre et ce dans
l’ordre souhaité.
• Déposer les fourchettes.

Si à un moment un philosophe a faim, alors un philosophe va


manger un jour, et ce quel que soit l’ordonnancement
101
Synchronisation - Diner des philosophes

 Ce qui peut arriver :


 Interblocage : chaque philosophe tient sa fourchette de gauche et attend
la deuxième.
 Idée : obliger les philosophes à reposer la fourchette au bout d’un
certain temps si la deuxième n’est pas libre
 mais... on peut avoir un cycle infini sans que personne ne mange :
• chaque philosophe prend la fourchette de gauche
• puis chaque philosophe la repose
• puis chaque philosophe prend la fourchette de droite
• …

102
Synchronisation - Diner des philosophes

 Solutions :
 Un serveur : chaque philosophe lui demande la permission avant de
prendre une fourchette.
 Hiérarchie des ressources : les fourchettes sont numérotées et chaque
philosophe prend d’abord la fourchette avec le plus grand numéro.
 “Right left dining philosophers” : les philosophes sont numérotés et
prennent en premier la fourchette de gauche ou de droite suivant leur
parité.
 Ne donner à un philosophe ses baguettes que si elles sont TOUTES
DEUX disponibles.

 Attention il ne faut pas affamer un philosophe !!

103
Synchronisation – Diner des philosophes
 Structure d’un philosophe (V1) :

Var baguettes: array[0..4] of semaphore;

Repeat
wait (baguettes[i]);
wait (baguettes[i+1 mod 5]);
manger; Garantit que 2 voisins ne
signal(baguettes [i]); mangent pas ensemble
signal(baguettes[i+1 mod 5]);
penser Que se passe-t-il si tous
Until false ; prennent leur baguette ?

=>dead lock

104
Synchronisation – Diner des philosophes
 Structure d’un philosophe (V2) :

Var semaphore test;

Repeat
wait (test); Fonctionne..
manger; Mais n’est pas optimal
signal(test); Un seul des philosophes
penser peut manger à la fois
Until false ;

Solution intéressante
Ssi n philosophes manger << penser

105
Synchronisation – Diner des philosophes
 Structure d’un philosophe (V3) :

Var semaphore test;

Repeat
wait (test);
prendre baguettes si possible;
signal (test);
manger;
lacher baguettes;
penser Seule la prise de baguettes est Sérialisée
Until false ; Problème si beaucoup de philosophes

106
Synchronisation – Diner des philosophes
 Structure d’un philosophe (V3) :

Var semaphore test;

Repeat
wait (test);
prendre baguettes si possible;
signal (test);
manger;
lacher baguettes;
penser Seule la prise de baguettes est Sérialisée
Until false ; Problème si beaucoup de philosophes

107
Synchronisation – Diner des philosophes
 Structure d’un philosophe (Langage C) :
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define N 5
#define G ( (N+i -1)%N) // philosophe de gauche de i
#define D ( i ) // philosophe de droite de i
enum etat { penser , faim , manger } ;
etat Etat [N] = { penser , penser , penser , penser , penser } ;
sem_t S [N] ;
sem_t mutex ; int main ( )
{
void *philosophe ( void *) ; int i , NumPhi[N] = { 0 , 1 , 2 , 3 , 4 } ;
pthread_t ph[N] ;
void Test ( int i ) ; sem_init(&mutex , 0 , 1 ) ;
for ( i = 0 ; i < N; i++)
sem_init(&S[i] , 0 , 0) ;
for ( i = 0 ; i < N; i++)
pthread_create (&ph[ i ] ,NULL, philosophe ,&(NumPhi[ i ] ) ) ;
// attendre la fin des threads
for ( i = 0 ; ( i<N && pthread_join (ph[ i ] ,NULL) = = 0 ); i ++);
printf ( " fin des threads \n" ) ;
return 0 ;
}

108
Synchronisation – Diner des philosophes
 Structure d’un philosophe (Langage C) :
void *philosophe ( void *num) {
int i =* ( int *) num ;
int nb = 2 ;

while ( nb ) {
nb-- ;
sleep ( 1 ) ; // penser
sem_wait(&mutex ) ; // tenter de manger
Etat [ i ]= faim ;
Test ( i ) ;
sem_post(&mutex ) ;
sem_wait(&S [ i ] ) ;
printf ( " philosophe [%d ] mange\n" , i ) ; // manger
sleep ( 1 ) ;
printf ( " philosophe [%d ] a fini de manger\n" , i ) ;
// libérer les fourchettes
sem_wait(&mutex ) ; void Test ( int i )
Etat [ i ] = penser ; {
if ( ( Etat [ i ] == faim ) && ( Etat [G] ! = manger) &&
Test (G ) ; Test (D) ; ( Etat [D] != manger ) ) {
sem_post(&mutex ) ; } Etat [ i ] = manger ;
} sem_post(&S [ i ] ) ;
}
}

109
Synchronisation – Diner des philosophes
 Structure d’un philosophe (Langage C) : Exécution
$ g++ -o philosophe philosophe.c –lpthread
$ philosophe
philosophe[0] mange
philosophe[2] mange
philosophe[4] mange
philosophe[0] a fini de manger
philosophe[2] a fini de manger
philosophe[1] mange
philosophe[4] a fini de manger
philosophe[3] mange
philosophe[0] mange
philosophe[1] a fini de manger
philosophe[2] mange
philosophe[3] a fini de manger
philosophe[4] mange
philosophe[0] a fini de manger
philosophe[2] a fini de manger
philosophe[1] mange
philosophe[3] mange
philosophe[4] a fini de manger
philosophe[1] a fini de manger
philosophe[3] a fini de manger
fin des threads

110
Synchronisation – producteurs /consommateurs
 Pour le problème du producteur et du consommateur, nous pouvons
utiliser deux compteurs d’événements in et out.
 Le premier compte le nombre d’insertions dans le tampon alors que le
second compte le nombre de retraits du tampon depuis le début.
 in : est incrémenté de 1 par le producteur après chaque insertion dans le
tampon.
 out : est incrémenté de 1 par le consommateur après chaque retrait du
tampon.
 Le producteur doit s’arrêter de produire lorsque le tampon est plein.
 Le consommateur peut consommer uniquement si le tampon n’est pas
vide.

Consommateur
Producteur
in out
tampon Consommateur
Producteur

111
Synchronisation – producteurs /consommateurs
 Exemple P/C par compteur d’évènement (E)
 Read(E) : donne la valeur de E
 Advance(E) : incrémente E de 1 de manière atomique
 Await(E,v) : attend que E atteigne ou dépasse la valeur v

// producteur //consommateur
#define N 100 // taille du tampon void consommateur (void) {
int in = 0, out = 0 ; int objet , sequence =0, pos =0 ;

void producteur (void) { while (1) {


int objet , sequence =0, pos =0 ; sequence = sequence + 1 ;
// attendre que le tampon devienne non vide
while (1) { Await(in,sequence) ;
objet = objet+1 ; objet = tampon[pos] ;
sequence = sequence + 1 ; // incrémenter le compteur de retraits
// attendre que le tampon Advance(&out) ;
//devienne non plein printf(" objet consommé [%d]:%d ", pos, objet) ;
Await(out, sequence - N) ; pos = (pos+1)%N ;
tampon[pos]= objet ; }
pos = (pos+1)%N ; }
// incrémenter le nombre de dépôts
Advance(&in) ;
}
}
112
Synchronisation – producteurs /consommateurs
 Exemple P/C par tubes
 Le producteur dépose ses objets un à un dans le tube
 Le consommateur récupère un à un des objets du tube
# include < stdio.h>
# include < unistd.h>

int main ( void ) {


int fildes [ 2 ] ; // pipe pour synchroniser
char c ; // caractère pour synchroniser
pipe ( fildes ) ;
write ( fildes [ 1 ], & c , 1 ) ; // nécessaire pour entrer dans la section critique la première fois
if ( fork ( ) = = 0 ) { // processus fils
for ( ; ; ) {
read ( fildes [ 0 ], & c , 1 ) ; // entre section critique
// < Section critique >
write ( fildes [ 1 ], & c , 1 ) ; // quitte section critique
}
} else { // processus père
for ( ; ; ) {
read ( fildes [ 0 ], & c , 1 ) ; // entre section critique
// < section critique >
write ( fildes [ 1 ], & c , 1 ) ; // quitte section critique
}
}
return 0 ;
}
113
Synchronisation – rédacteurs / lecteurs
 Ce problème modélise les accès à une base de données.

 Un ensemble de processus tente constamment d’accéder à la base de


données soit pour écrire, soit pour lire des informations.

 Pour assurer une certaine cohérence des données de la base, il faut


interdire l’accès (en lecture et en écriture) à tous les processus, si un
processus est en train de modifier la base (accède à la base en mode
écriture).

114
Synchronisation – rédacteurs / lecteurs
 Rédacteurs / lecteurs
 Nbl : le nombre de lecteurs qui sont en train de lire
 L’accès à Nbl doit être exclusif (sémaphore mutex)

semaphore Redact // écrivain Lecteur :


semaphore Mutex // accès à la base repeat
Nbl = 0 //Nb lecteurs P(Mutex)
if (NbL == 0) then
P(Redact) // Premier lecteur empêche écriture end if
NbL = NbL + 1
Rédacteur : V(Mutex)
repeat // lecture de la base
P(Redact) // fin de l’accès à la base
// modifier les données de la base P(Mutex)
V(Redact) NbL = NbL - 1
until TRUE if (NbL == 0) then
V(Redact) // Dernier lecteur habilite l’écriture end if
V(Mutex)
until TRUE

115
Synchronisation – rédacteurs / lecteurs
 Rédacteurs / lecteurs (Langage C) # include < stdio.h>
# include < unistd.h>
int main ( ) { # include < pthread.h>
int i ; # include < semaphore.h>
int numred[N] = { 0 , 1 , 2 } ; # define N 3
int numlec [N] = { 0 , 1 , 2 } ; # define ACCES 4
int NbL = 0 ;
pthread_t red [N] ;
sem_t Redact ;
pthread_t lec [N] ; sem_t mutex ;
sem_init(&mutex , 0 , 1 ) ;
sem_init(&Redact , 0 , 1 ) ; void * lecteur ( void *) ;
for ( i = 0 ; i <N; i ++) { void * redacteur ( void *);
// création des rédacteurs
pthread_create (&red [ i ] ,NULL, lecteur ,&(numred[ i ] ) ) ;
// création des lecteurs
pthread_create (&lec [ i ] ,NULL, redacteur,&( numlec [ i ] ) ) ;
}
// attendre la fin des threads
for ( i = 0 ; i <N; i++){
pthread_join ( red [ i ] ,NULL) ;
pthread_join ( lec [ i ] ,NULL) ;
}
printf ( " fin des threads\n" ) ;
return 0 ;
}

116
Synchronisation – rédacteurs / lecteurs
 Rédacteurs / lecteurs (Langage C) void * lecteur ( void * num) {
int i = * ( int * ) num ;
int x=ACCES;
do {
printf ( " Lecteur %d\n" , i ) ;
void * redacteur ( void * num) { sem_wait(&mutex ) ;
int i = * ( int *) num ; if ( ! NbL)
int x=ACCES; sem_wait(&Redact );
NbL++;
do {
printf ( " Redacteur %d\n" , i ) ; sem_post(&mutex ) ;
sem_wait(&Redact ) ; // lecture de la base
// modifier les données de la base sleep ( 1 ) ;
sleep ( 1 ) ; // fin de l’accès à la base
sem_post(&Redact ) ; sem_wait(&mutex ) ;
} while( - - x ) ; NbL- - ;
}
if ( ! NbL)
sem_post(&Redact ) ;
sem_post(&mutex ) ;
// traitement des données lues
sleep ( 1 ) ;
} while( - -x ) ;
}

117
Synchronisation – rédacteurs / lecteurs
 Rédacteurs / lecteurs (Langage C)
$ gcc -o redlec redlec.c -lpthread ; redlec
Lecteur 0
Redacteur 0 Exécution de 2 commandes
Lecteur 1
Redacteur 1
Lecteur 2
Redacteur 2
Redacteur 0
Lecteur 0
Lecteur 1
Lecteur 2
Redacteur 1
Redacteur 2
Redacteur 0
Lecteur 0
Lecteur 1
Redacteur 1
Lecteur 2
Redacteur 2
Redacteur 0
Lecteur 0
Lecteur 1
Redacteur 1
Lecteur 2
Redacteur 2
fin des threads

118
Synchronisation – rédacteurs / lecteurs
 Plusieurs processus peuvent accéder à la base, en même temps, en mode
lecture
 Solution : placer les lecteurs en attente derrière le rédacteur lorsque
celui-ci est en attente. 1.void reader()
2.{
3. while (true)
4. {
1.void writer()
5. P (Redact);
2.{
6. V (Redact);
3. while (true)
7. P (Mutex);
4. {
8. NbrLecteurs++;
5. P (Redact);
9. if (NbrLecteurs == 1) P(bd);
6. work_on_data();
10. V(Mutex);
7. P(bd);
11. read_data();
8. write_data();
12. P(Mutex);
9. V(bd);
13. NbrLecteurs--;
10. V(Redact);
14. if (NbrLecteurs == 0) V (bd);
11. }
15. V(Mutex);
12.}
16. use_data();
17. }
18.}

119
Gestion des fichiers
 Un fichier est une unité de stockage logique de l’information
 La correspondance avec l’unité est réalisée par le système
d’exploitation
 Chaque fichier est doté de méta-data :
 Nom, taille, type, autorisation, date, propriétaire, ….
 Il existe plusieurs opérations pour la manipulation des fichiers
 Création, lecture/écriture, suppression, concaténation, ….

120
Gestion de la mémoire
 Disque UNIX

121

Vous aimerez peut-être aussi