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
communication inter-processus
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…).
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.
6
Système d’exploitation
Les débuts !
7
Système d’exploitation
Les débuts !
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.
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 ?
16
Système d’exploitation
17
Système d’exploitation
18
Système d’exploitation
19
Système d’exploitation
UNIX : caractéristiques
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)
21
Système d’exploitation
POSIX
22
Système d’exploitation
POSIX
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.
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
28
Processus : exemple
Exemple sous Unix
29
Processus : organisation
Toute exécution d’un programme déclenche la création d’un
processus dont:
30
Processus : organisation
La visualisation des processus se fait avec la commande :
ps
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
33
Processus : états
Quand un processus s’exécute, il change d’état.
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
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.
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
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 :
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);
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.
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)
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.
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é.
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
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
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.
54
Processus : ordonnancement
Scheduling du premier arrivé, premier servi (FCFS: First-come, first-
served)
55
Processus : ordonnancement
Scheduling du travail plus court d’abord (SJF:Shortest job first)
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
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.
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
61
Communication Inter-processus
Exclusion mutuelle
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
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
65
IPC : sémaphore
Sémaphores: ont été inventés en 1965 par Edsger Dijkstra
(mathématicien et informaticien néerlandais 1930 – 2002).
66
IPC : sémaphore
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) ;
2. Solution :
Processus P1
P(mutex1) ;
n=n-1 ;
V(mutex1) ;
P(mutex2) ;
Out = out +1 ;
V(mutex2) ;
70
IPC : sémaphore
Exemple 2
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 :
74
IPC : Signaux
Signaux :
commande kill -l
75
IPC : Signaux
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 .
Processus émetteur
Signaux pendants:
Signaux émis mais pas
encore pris en compte.
77
IPC : Signaux
Gestion des signaux :
Capter un signal dans une fonction :
int sigaction( int sig, struct sigaction* p_action, struct sigaction* p_action_anc ) ;
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>
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 );
80
IPC : Tubes
IPC par Tubes (pipes)
Le tube (pipe) permet la communication entre un processus
et un de ses descendants.
81
IPC : Tubes
IPC par Tubes (pipes)
82
IPC : Tubes
Lecture : read
int nb_lu;
nb_lu = read(p[0], buffer, TAILLE_READ);
Comportement de l’appel :
Si le nombre d’écrivains est nul alors c’est la fin de fichier et nb_lu est
nul.
_NDELAY nb_lu = 0.
83
IPC : Tubes
Ecriture : write
int nb_ecrit;
nb_ecrit = write(p[1], buf, n);
Sinon
o Si l’écriture est bloquante, il n’y a retour que quand les n caractères ont
été écrits dans le tube.
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.
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.
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é :
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)
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)
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é.
91
Synchronisation
Le principe de fonctionnement de wait est le suivant :
92
Synchronisation
Le principe de fonctionnement de wait est le suivant :
93
Utilisation de l’appel système wait
• Exemple :
94
Utilisation de l’appel système wait
• Exemple :
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;)
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);
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é.
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.
103
Synchronisation – Diner des philosophes
Structure d’un philosophe (V1) :
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) :
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) :
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) :
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 ;
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)
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