Programmation Systeme Handout
Programmation Systeme Handout
(RS)
2016-2017
À propos de ce document
Document diffusé selon les termes de la licence
Licence Creative Commons version 3.0 France (ou ultérieure)
Attribution ; Partage dans les mêmes conditions
http://creativecommons.org/licenses/by-sa/3.0/fr/
Remerciements
I Sacha Krakoviack pour son cours et Bryant et O’Hallaron pour leur livre
I Les (autres) emprunts sont indiqués dans le corps du document
Aspects techniques
I Document LATEX (classe latex-beamer), compilé avec latex-make
I Schémas : Beaucoup de xfig, un peu de inkscape
Implication
I Manipulation : programmez ! Expérimentez !
I Questions bienvenues : pendant/après le cours, par mail, etc.
(4/210)
Pourquoi un cours de système ?
I Quatre concepts fondamentaux de l’Informatique (G. Dowek) :
Information, Machine, Algorithme, Langage
I Les architectures et infrastructures modernes sont complexes
I Wikipedia : 1145 serveurs, 30 900 CPUs
I OVH : 250 000 serveurs
I OpenStack (pile logicielle permettant de créer un Cloud privé)
(5/210)
Pourquoi un cours de système ? (2)
I Noyau Linux (et outils d’observation)
(6/210)
Pourquoi un cours de système ? (3)
L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB) L2 (256KB)
PCI 8086:154d
L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) eth2 L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB) L1d (32KB)
L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB) L1i (32KB)
PCI 8086:154d
Core P#0 Core P#1 Core P#2 Core P#3 Core P#4 Core P#5 Core P#6 Core P#7 eth3 Core P#0 Core P#1 Core P#2 Core P#3 Core P#4 Core P#5 Core P#6 Core P#7
PU P#0 PU P#16 PU P#2 PU P#18 PU P#4 PU P#20 PU P#6 PU P#22 PU P#8 PU P#24 PU P#10 PU P#26 PU P#12 PU P#28 PU P#14 PU P#30 PU P#1 PU P#17 PU P#3 PU P#19 PU P#5 PU P#21 PU P#7 PU P#23 PU P#9 PU P#25 PU P#11 PU P#27 PU P#13 PU P#29 PU P#15 PU P#31
PCI 8086:10fb
eth0
PCI 8086:10fb
eth1
PCI 8086:8d62
PCI 8086:1521
eth4
PCI 8086:1521
eth5
PCI 102b:0534
PCI 8086:8d02
Host: grisou-9.nancy.grid5000.fr
Indexes: physical
(7/210)
Pourquoi un cours de système ? (4)
Comment les utiliser efficacement ? Comment les concevoir ?
; Performances, sécurité, résilience, efficacité énergétique
Motivations
I OS = systèmes complexes les plus courants ; Concepts et abstractions claires
I Impossible de faire un programme efficace sans comprendre l’OS
I Comprendre ceci aide à comprendre les abstractions supérieures
Livres
I Bryant, O’Hallaron : Computer Systems, A Programmer’s Perspective.
Autres cours disponibles sur Internet
I Introduction aux systèmes et aux réseaux (S. Krakowiak, Grenoble)
Source de nombreux transparents présentés ici.
http://sardes.inrialpes.fr/~krakowia/Enseignement/L3/SR-L3.html/
I Programmation des systèmes (Ph. Marquet, Lille)
http://www.lifl.fr/~marquet/cnl/pds/
I Operating Systems and System Programming (B. Pfaff, Stanford)
http://cs140.stanford.edu/
Sites d’information
I http://systeme.developpez.com/cours/
Index de cours et tutoriels sur les systèmes
(10/210)
Plan de cette partie du module :
2 Processus
Processus et programme ; Utilisation des processus UNIX et Réalisation ; Signaux.
3 Fichiers et entrées/sorties
Fichiers et systèmes de fichiers ; Utilisation ; Réalisation.
6 Programmation concurrente
Qu’est ce qu’un thread ; Modèles d’implémentation ; POSIX threads.
(11/210)
Premier chapitre
Introduction
Qu’est ce qu’un système d’exploitation ?
Pourquoi UNIX ?
Conclusion
Qu’est ce qu’un système d’exploitation
Système d’exploitation
Son
Disques durs
Matériel Cartes graphiques
(13/210)
Évolution des systèmes d’exploitation : Étape 0
À l’origine, pas de système d’exploitation
Une machine, un utilisateur, un logiciel
I Tout au plus une bibliothèque de services standards
Exemples : premières machines, systèmes embarqués (voitures, ascenseurs)
I Complexité conceptuelle réduite . . .
I . . . mais complexité technique accrue : on refait tout à chaque fois (⇒ cher)
Application
OS
Matériel
gcc emacs
OS
Matériel
Jim Bob
gcc emacs
OS
Matériel
Système d’exploitation
Son
Disques durs
Matériel Cartes graphiques
(17/210)
Fonctions du système d’exploitation
Organe Entité
physique logique
I Gestion de l’activité
I Déroulement de l’exécution (processus) Processeur Processus
I Événements matériels
I Gestion de l’information
I Accès dynamique (exécution, mémoire) Mémoire Mémoire
I Conservation permanente principale virtuelle
I Partage Disque Fichier
(18/210)
Premier chapitre
Introduction
Qu’est ce qu’un système d’exploitation ?
Pourquoi UNIX ?
Conclusion
UNIX, POSIX et les autres
(20/210)
Historique
Rewrite Minix
Linus Torvalds
80-90 Unix War : BSD vs. System V
in C Andrew Tannenbaum
Pourquoi UNIX ?
Conclusion
Notion d’interface
Un service est caractérisé par son interface
I L’interface est l’ensemble des fonctions accessibles aux utilisateurs du service
I Chaque fonction est définie par :
I son format : la description de son mode d’utilisation (syntaxe)
I sa spécification : la description de son effet (sémantique)
I Ces descriptions doivent être :
précises, complètes (y compris les cas d’erreur), non ambigües.
Dans POSIX
I C’est l’interface qui est normalisée, pas l’implémentation
(23/210)
Interfaces d’un système d’exploitation
Interface de
Shell 2 Shell 1
programmation
Noyau UNIX
(24/210)
Exemple d’usage des interfaces d’UNIX
while (nb_lus = read(fich1, buf, BUFFSIZE )) {
I Interface de programmation if ((nb_lus == -1) && (errno != EINTR)) {
break; /* erreur */
Ci-contre : programme copiant un } else if (nb_lus > 0) {
fichier dans un autre pos_buff = buf;
a_ecrire = nb_lus;
(read et write : appels système) while (nb_ecrits =
write(fich2,pos_buff,a_ecrire)) {
I Interface de commande if ((nb_ecrits == -1) && (errno != EINTR))
break; /* erreur */
Commande shell : else if (a_ecrire == 0)
break; /* fini */
$ cp fich1 fich2 else if (nb_ecrits > 0) {
recopie fich1 dans fich2 pos_buff += nb_ecrits;
a_ecrire -= nb_ecrits;
} } } }
I Documentation
man 1 <nom> : documentation des commandes (par défaut)
man 2 <nom> : documentation des appels système
man 3 <nom> : documentation des fonctions
d’autres sections, mais plus rares. Voir man 7 man ;)
(25/210)
Découpage en couches du système
Programmes utilisateurs
Utilisateur
Bibliothèques
Niveau utilisateur mode utilisateur
Appel système
I Applications utilisateur Trappe
mode superviseur
I Logiciels de base
I Bibliothèque système Système de Gestion des
Noyau gestion de processus
I Appels de fonction fichiers Contrôle
péripériques
Niveau noyau Contrôle/programmation
I Gestion des processus Matériel Interruption
Périphériques
Pourquoi UNIX ?
Conclusion
Protection des ressources (rapide introduction – cf. RSA)
Méthodes d’isolation des programmes (et utilisateurs) dangereux
I Préemption : ne donner aux applications que ce qu’on peut leur reprendre
I Interposition : pas d’accès direct, vérification de la validité de chaque accès
I Mode privilégié : certaines instructions machines réservées à l’OS
Exemples de ressources protégées
I Processeur : préemption
I Interruptions (matérielles) à intervalles réguliers → contrôle à l’OS
I (l’OS choisit le programme devant s’exécuter ensuite)
I Mémoire : interposition (validité de tout accès mémoire vérifiée au préalable)
I Exécution : mode privilégié (espace noyau) ou normal (espace utilisateur)
I Le CPU bloque certaines instructions assembleur (E/S) en mode utilisateur
programme appel continue
utilisateur syscall Changement de contexte mode bit = 1
exécution
Espace utilisateur
Espace noyau Interruption logicielle
exécute mode bit = 0
syscall
(28/210)
Limites de la protection
Les systèmes réels ont des failles
Les systèmes ne protègent pas de tout
I while true ; do mkdir toto; cd toto; done (en shell)
I while(1) { fork(); } (en C)
I while(1) { char *a=malloc(512); *a=’1’; } (en C)
Unix was not designed to stop people from doing stupid things,
because that would also stop them from doing clever things.
– Doug Gwyn
(29/210)
Résumé du premier chapitre
(30/210)
Deuxième chapitre
Processus
Introduction
Conclusion
Qu’est ce qu’un processus ?
Définition formelle :
I Entité dynamique représentant l’exécution d’un programme sur un processeur
Exemples de processus :
I L’exécution d’un programme
I La copie d’un fichier sur disque
I La transmission d’une séquence de données sur un réseau
(32/210)
Utilité des processus : simplicité
emacs
DOOM OS
WWWOSls DOOM
emacs WWW
ls
(33/210)
Ne pas confondre processus et programme
I Programme : I Processus :
Code + données (passif) Programme en cours d’exécution
Pile
int i;
int main() { Tas
printf("Salut\n"); Données int i;
}
Code main()
(34/210)
Utilité des processus : efficacité
(35/210)
Parallélisme et pseudo-parallélisme
Le pseudo-parallélisme
I fonctionne grâce aux interruptions matérielles régulières rendant contrôle à OS
I permet également de recouvrir calcul et communications
On y reviendra.
(36/210)
Relations entre processus
Compétition
I Plusieurs processus veulent accéder à une ressource exclusive
(i.e. ne pouvant être utilisée que par un seul à la fois) :
I Processeur (cas du pseudo-parallélisme)
I Imprimante, carte son
I Une solution possible parmi d’autres :
FCFS : premier arrivé, premier servi (les suivants attendent leur tour)
Coopération
I Plusieurs processus collaborent à une tâche commune
I Souvent, ils doivent se synchroniser :
I p1 produit un fichier, p2 imprime le fichier
I p1 met à jour un fichier, p2 consulte le fichier
I La synchronisation se ramène à :
p2 doit attendre que p1 ait franchi un certain point de son exécution
(37/210)
Faire attendre un processus
Primordial pour les interactions entre processus
Attente active
Processus 1 Processus 2
while (ressource occupée) ressource occupée = true;
{ }; utiliser ressource;
ressource occupée = true; ressource occupée = false;
... ...
I Gaspillage de ressource si pseudo-parallélisme
I Problème d’atomicité (race condition – on y reviendra)
Blocage du processus
I Définition d’un nouvel état de processus : bloqué
(exécution suspendue ; réveil explicite par un autre processus ou par le
système)
blocage ...
actif bloqué sleep(5); /* se bloquer pour 5 secondes */
réveil ...
(38/210)
Deuxième chapitre
Processus
Introduction
Conclusion
Processus UNIX
(40/210)
Environnement d’un processus
(41/210)
Vie et mort des processus
Tout processus a un début et une fin
I Début : création par un autre processus
I init est le processus originel : pid=1 (launchd sous mac)
Créé par le noyau au démarrage, il lance les autres processus système
I Fin
I Auto-destruction (à la fin du programme) (par exit)
I Destruction par un autre processus (par kill)
I Destruction par l’OS (en cas de violation de protection et autres)
I Certains processus ne se terminent pas avant l’arrêt de la machine
I Nommés démons (disk and execution monitor → daemon)
I Réalisent des fonctions du système (login utilisateurs, impression, serveur web)
I Le processus créé (fils) est une copie conforme du processus créateur (père)
Copies conformes comme une bactérie qui se coupe en deux
I Ils se reconnaissent par la valeur de retour de fork() :
I Pour le père : le pid du fils (ou –1 si erreur) n=fork() n=235
n=0
I Pour le fils : 0
Exemple :
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
} else {
printf("je suis le fils, mon PID est %d; mon père est %d\n",
getpid(), getppid());
/* en général exec(), (exécution d’un nouveau programme) */
}
(43/210)
Création des processus dans Unix (2/3)
int i=3;
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
i += 4;
} else {
printf("je suis le fils, mon PID est %d; mon père est %d\n",
getpid(), getppid());
i += 9;
}
printf("pour %d, i = %d\n", getpid(), i);
je suis le fils, mon PID est 10271; mon père est 10270
pour 10271, i = 12
je suis le père, mon PID est 10270 i=3 i+=4
pour 10270, i = 7 i+=9
(44/210)
Création des processus dans Unix (3/3)
testClonage.c
int main() {
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
sleep(10) /* blocage pendant 10 secondes */
exit(0);
} else {
printf("je suis le fils, mon PID est %d\n", getpid());
sleep(10) /* blocage pendant 10 secondes */
exit(0);
}
}
(45/210)
Hiérarchie de processus Unix
1 Processus init
Système
Utilisateur 1 Utilisateur 2
(46/210)
Quelques interactions entre processus (1/2)
(47/210)
Quelques interactions entre processus (2/2)
(48/210)
Faire attendre un processus
Fonction sleep()
I Bloque le processus courant pour le nombre de secondes indiqué
I unsigned int sleep(unsigned int seconds);
I usleep() et nanosleep() offrent meilleures résolutions (micro, nanoseconde)
mais interfaces plus compliquées et pas portables
(49/210)
Exemple de synchronisation entre père et fils
testClone2.c
int main() {
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
while (1) ; /* boucle sans fin sans attendre le fils */
} else {
printf("je suis le fils, mon PID est %d\n", getpid());
sleep(2) /* blocage pendant 2 secondes */
printf("fin du fils\n");
exit(0);
} }
(50/210)
Autre exemple de synchronisation père fils
testClone3.c
#include <sys/types.h>
#include <sys/wait.h> (suite de testClone3.c)
int main() {
if (fork() != 0) { } else { /* correspond au if (fork() != 0)*/
int statut; pid_t fils; printf("je suis le fils, PID=%d\n",
printf("Le père (%d) attend.\n", getpid()); getpid());
fils = wait(&statut); sleep(2) /* blocage pendant 2 secondes */
if (WIFEXITED(statut)) { printf("fin du fils\n");
printf("%d : fils %d terminé (code %d)\n", exit(1);
getpid(), fils, WEXITSTATUS(statut)); } }
};
exit(0);
(51/210)
Exécution d’un programme spécifié sous UNIX
Appels systèmes exec
I Pour faire exécuter un nouveau programme par un processus
I Souvent utilisé immédiatement après la création d’un processus :
fork+exec = lancement d’un programme dans un nouveau processus
I Effet : remplace la mémoire virtuelle du processus par le programme
I Plusieurs variantes existent selon le mode de passage des paramètres
(tableau, liste, passage de variables d’environnement)
I C’est aussi une primitive du langage de commande (même effet)
Exemple :
main() {
if (fork() == 0) {
(52/210)
L’exemple du shell
exec(command) exit()
(53/210)
Résumé du début du deuxième chapitre
(54/210)
Deuxième chapitre
Processus
Introduction
Conclusion
Réalisation des processus
Processus = mémoire virtuelle + flot d’exécution
Objectif maintenant :
En savoir assez sur le fonctionnement de l’OS pour utiliser les processus
Pile
I À propos de mémoire Tas
Données
I Organisation interne de la mémoire virtuelle d’un processus Unix Code
I À propos de processeur
I Pseudo-parallélisme : allocation successive aux processus par tranches de temps
Conclusion
Communication inter-processus (IPC) dans Unix
(60/210)
Signaux
Intérêts et limites
I Simplifient le contrôle d’un ensemble de processus (comme le shell)
I Pratiques pour traiter des événements liés au temps
I Mécanisme de bas niveau à manipuler avec précaution
(risque de perte de signaux en particulier)
(61/210)
Fonctionnement des signaux
1) Arrivée du signal
processus
3) Exécution du gestionnaire
(63/210)
États d’un signal
Signal traité
I Le gestionnaire a commencé (et peut-être même fini)
(64/210)
Deuxième chapitre
Processus
Introduction
Conclusion
États d’un travail
Entrée clavier
Commande Commande &
Travail en Travail en
fg %<job>
premier plan arrière-plan
Ctrl-C
Ctrl-Z Travail
suspendu
I Travail (job) = (groupe de) processus lancé par une commande au shell
I Seul le travail en premier plan peut recevoir des signaux du clavier
I Les autres sont manipulés par des commandes
(66/210)
Terminaux, sessions et groupes en Unix
I Concept important pour comprendre les signaux sous Unix (détaillé plus tard)
I Une session est associée à un terminal, donc au login d’un utilisateur par shell
Le processus de ce shell est le leader de la session.
I Plusieurs groupes de processus par session. On dit plusieurs travaux (jobs)
I Au plus un travail interactif (avant-plan, foreground)
Interagissent avec l’utilisateur via le terminal, seuls à pouvoir lire le terminal
I Plusieurs travaux en arrière plan (background)
Lancés avec & ; Exécution en travail de fond
I Signaux SIGINT (frappe de <ctrl-c>) et SIGTSTP (frappe de <ctrl-z>)
sont passés au groupe interactif et non aux groupes d’arrière-plan
(67/210)
Exemple avec les sessions et groupes Unix
loop.c
int main() {
printf"processus %d, groupe %d\n", getpid(), getpgrp());
while(1) ;
}
(68/210)
Exemple de travaux
Shell
Commande &
p2 p3 p6 pid=5610
pgid=5600
pid=5124 pid=5125
pgid=5123 pgid=5123
(69/210)
Deuxième chapitre
Processus
Introduction
Conclusion
Envoyer un signal à un autre processus
Interfaces
I Langage de commande : kill -NOM victime
#include <signal.h>
I Appel système :
int kill(pid_t victime, int sig);
(71/210)
Redéfinir le gestionnaire associé à un signal (POSIX)
Structure à utiliser pour décrire un gestionnaire
struct sigaction {
void (*sa handler)(int); /* gestionnaire, interface simple */
void (* sa_sigaction) (int, siginfo_t *, void *); /* gestionnaire, interface complète */
sigset t sa mask; /* signaux à bloquer pendant le traitement */
int sa flags; /* options */
}
I Gestionnaires particuliers : sig dfl : action par défaut ; sig ign : ignorer signal
I Deux types de gestionnaires
I sa handler() connait le numéro du signal
I sa sigaction() a plus d’infos
Primitive à utiliser pour installer un nouveau gestionnaire
#include <signal.h>
int sigaction(int sig, const struct sigaction *newaction, struct sigaction *oldaction);
(73/210)
Exemple 2 : temporisation
unsigned int alarm(unsigned int nb sec)
I nb sec > 0 : demande l’envoi de SIGALRM après environ nb sec secondes
I nb sec = 0 : annulation de toute demande précédente
I Retour : nombre de secondes restantes sur l’alarme précédente
I Attention, sleep() réalisé avec alarm() ⇒ mélange dangereux
#include <signal.h>
void handler(int sig) {
printf("Entrez un nombre avant 5 sec:");
printf("trop tard !\n");
alarm(5);
exit(1);
scanf("%d", &reponse);
}
restant = alarm(0);
int main() {
printf("bien reçu (en %d secondes)\n",
struct sigaction nvt,old;
5 - restant);
int reponse,restant;
exit (0);
memset(&nvt, 0, sizeof(nvt));
}
nvt.sa_handler = handler;
sigaction(SIGALRM, &nvt, &old);
(74/210)
Exemple 3 : synchronisation père-fils
#include <signal.h>
void handler(int sig) { /* nouveau gestionnaire */
pid t pid;
int statut;
pid = waitpid(-1, &statut, 0); /* attend un fils quelconque */
return;
}
int main() {
struct sigaction nvt,old;
memset(&nvt, 0, sizeof(nvt));
nvt.sa_handler = &handler;
sigaction(SIGCHLD, &nvt, &old); /* installe le gestionnaire */
... <création d’un certain nombre de fils> ...
exit (0);
}
(75/210)
Résumé de la fin du deuxième chapitre
I Quelques définitions
I pgid : groupe de processus ; un par processus lancé par shell (et tous ses fils)
I Travail d’avant-plan, d’arrière-plan : lancé avec ou sans &
I Communication par signaux
I Envoyer un signal <NOM> à <victime> :
I Langage commande : kill -NOM victime
I API : kill(pid t victime, int sig)
victime > 0 : numéro du pid visé
victime = 0 : tous les process de ce groupe
victime = −1 : tous les process accessibles
victime < −1 : tous les process du groupe abs(victime)
I Changer le gestionnaire d’un signal : sigaction (en POSIX)
I Exemples de signaux
I sigint, sigstop : Interaction avec le travail de premier plan (ctrl-c, ctrl-z)
I sigterm, sigkill : demande de terminaison, fin brutale
I sigalarm : temporisation
I sigchld : relations père-fils
(76/210)
Troisième chapitre
Fichiers et entrées/sorties
Fichiers et systèmes de fichiers
Introduction
Désignation des fichiers
Conclusion
Fichiers
Définitions
I Fichier : un ensemble d’informations groupées pour conservation et utilisation
I Système de gestion de fichiers (SGF) : partie de l’OS conservant les fichiers et
permettant d’y accéder
(78/210)
Unix et les fichiers
(79/210)
Désignation des fichiers
(80/210)
Raccourcis pour simplifier la désignation
Noms relatifs au répertoire courant
I Depuis /home/bob, conf/main.tex = /home/bob/conf/main.tex
Abréviations
I Répertoire père : depuis /home/bob, ../jim/code = /home/jim/code
I Répertoire courant : depuis /bin, ./prog1 = /bin/prog1
I Depuis n’importe où, ∼bob/ = /home/bob/ et ∼/ = /home/<moi>/
Liens symboliques
bin etc home
Depuis /home/jim
I Création du lien : ln -s cible nom du lien jim bob
prog1
Exemple : ln -s /bin/prog1 monprog
I /home/jim/prog1 désigne /bin/prog1 code monprog
I Si la cible est supprimée, le lien devient invalide
Liens durs : ln cible nom
I Comme un lien symbolique, mais copies indifférenciables (ok après rm cible)
I Interdit pour les répertoires (cohérence de l’arbre)
(81/210)
Règles de recherche des exécutables
(82/210)
Utilisations courantes des fichiers
I Unix : fichiers = suite d’octets sans structure interprétée par utilisateur
I Windows : différencie fichiers textes (où \n est modifié) des fichiers binaires
Programmes exécutables
I Commandes du système ou programmes créés par un utilisateur
I Exemple : gcc -o test test.c ; ./test
Produit programme exécutable dans fichier test ; exécute le programme test
I Question : pourquoi ./test ? (car test est un binaire classique : )
if test $n -le 0;
Fichiers de données
I Documents, images, programmes sources, etc.
I Convention : le suffixe indique la nature du contenu
Exemples : .c (programme C), .o (binaire translatable, cf. plus loin), .h (entête
C), .gif (un format d’images), .pdf (Portable Document Format), etc.
Remarque : ne pas faire une confiance aveugle à l’extension (cf. man file)
Fichiers temporaires servant pour la communication
I Ne pas oublier de les supprimer après usage
I On peut aussi utiliser des tubes (cf. plus loin)
(83/210)
Troisième chapitre
Fichiers et entrées/sorties
Fichiers et systèmes de fichiers
Introduction
Désignation des fichiers
Conclusion
Utilisation des fichiers dans le langage de commande
Créer un fichier
I Souvent créés par applications (éditeur, compilateur, etc), pas par shell
I On peut néanmoins créer explicitement un fichier (cf plus loin, avec flots)
(85/210)
Interface de programmation POSIX des fichiers
Interface système
I Dans l’interface de programmation, un fichier est représenté par descripteur
Les descripteurs sont de (petits) entiers (indice dans des tables du noyau)
I Il faut ouvrir un fichier avant usage :
fd = open("/home/toto/fich", O RDONLY, 0);
I Ici, ouverture en lecture seule (écriture interdite)
I Autres modes : o rdwrite, o wronly
I fd stocke le descripteur alloué par le système (ou –1 si erreur)
I Fichier créé s’il n’existe pas (cf. aussi creat(2))
I man 2 open pour plus de détails
I Il faut fermer les fichiers après usage :
close (fd)
I Descripteur fd plus utilisable ; le système peut réallouer ce numéro
Interface standard
I Il existe une autre interface, plus simple (on y revient)
(86/210)
Interface de programmation POSIX : read()
L’objet fichier
I Chaque fichier a un pointeur courant : tête de lecture/écriture logique
I Les lectures et écritures le déplacent implicitement
I On peut le déplacer explicitement (avec lseek() – voir plus loin)
(87/210)
Interface de programmation POSIX : lseek()
Objectif
I Permet de déplacer le pointeur de fichier explicitement
Exemples
lseek(fd, 30, seek cur) lseek(fd, 71, seek set)
0 21 51 62 0 21 62 71
fd fd
(88/210)
Interface de programmation POSIX : write()
I Écriture dans les conditions normales :
p=write(fd, buf, 6)
avant après
Bibliothèque C standard
I Fonctions fopen, fscanf, fprintf et fclose.
I Plus haut niveau (utilisation des formats d’affichage)
I Meilleures performances sans effort (POSIX : perfs au tournevis)
I Un syscall ≈ 1000 instructions ⇒ écritures dans un tampon pour les grouper
#include <stdio.h>
#include <stdio.h>
FILE *fd = fopen("mon fichier","w");
FILE *fd = fopen("mon fichier","w");
if (fd != NULL) {
if (fd != NULL) {
char toto[50];
fprintf(fd,"Résultat %d\n", entier);
fscanf(fd,"%s%d",toto, &entier);
fprintf(fd,"un autre %f\n", reel);
fscanf(fd,"%s%s%f",toto,toto, &reel);
fclose(fd);
fclose(fd);
}
}
(90/210)
Fichiers et flots d’entrée sortie
entrée normale
0 stdin clavier
sortie normale
1 stdout écran
sortie erreurs 2 stderr écran
(91/210)
Flots et interface de commande
(92/210)
Interface de programmation des tubes
Appel système pipe()
I Crée un tube : deux descripteurs (l’entrée et la sortie du tube)
int fd[2];
pipe(fd);
I Ce qui est écrit sur l’entrée (fd[1]) est disponible sur la sortie (fd[0])
par défaut, cela permet de se parler à soi-même :)
Mnemotechnique : On lit sur 0 (stdin), et on écrit sur 1 (stdout)
(93/210)
Programmation d’un tube père/fils
#define BUFSIZE 10
int main(void) {
char bufin[BUFSIZE] = "empty"; père fils
char bufout[ ] = "hello";
pid t childpid;
empty bufin empty
int fd[2]; hello bufout hello
pipe(fd); fd[0]
if (fork() > 0) { /* père */
close(fd[0]); fd[1]
write(fd[1], bufout, strlen(bufout)+1);
} else { /* fils */
close(fd[1]); write tube read
read(fd[0], bufin, BUFSIZE);
} $ ./parentwritepipe
printf("[%d]: bufin = %.*s, bufout = %s\n", [29196]: bufin=empty, bufout=hello
getpid(), BUFSIZE, bufin, bufout); [29197]: bufin=hello, bufout=hello
return 0; $
}
I Problème des tubes : seulement entre descendants car passage par clonage
I Solution : tubes nommés (même principe, mais nom de fichier symbolique)
#include <sys.types.h>
#include <sys/stat.h>
int mkfifo(char *nom, mode t mode);
(96/210)
Copie de descripteurs : dup() et dup2()
ls > fich
I Cette commande inscrit dans fich ce que ls aurait écrit.
I Comment ça marche ?
On utilise les appels systèmes dup() et dup2() pour copier un descripteur :
(97/210)
L’exemple du shell
sh sh sh wait() sh
pipe(fd) fork() fork() close(fd[0])
(pidp) stdout (pidp) stdin close(fd[1]) wait() (pidp)
close(fd[0]) fd close(fd[1])
sh fd[1] fd[0] sh
dup2() dup2()
sh stdout stdin sh
exec(command1) exec(command2)
stdout stdin
command1 command2
(pidf1) (pidf2)
exit() exit()
Réalisation
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
Fichiers et entrées/sorties
Fichiers et systèmes de fichiers
Introduction
Désignation des fichiers
Conclusion
Représentation physique et logique d’un fichier
Type fichier
Propriétaire et groupe I Pointeurs et tables d’indirection
Droits d’accès
contiennent adresses de clusters
Taille (octets)
Dates (création, modif) I ≈ 10 blocs adressés directement
Pointeurs directs ⇒ accès efficace petits fichiers
Données
(101/210)
Noms symboliques et inodes
I Les inodes sont rangés dans une table au début de la partition
I On peut accéder aux fichiers en donnant leur inode (cf. ls -i pour le trouver)
I Les humains préfèrent les noms symboliques aux numéros d’identification
⇒ notion de répertoire, sous forme d’arborescence (chainage avec racine)
I Les répertoires sont stockés sous forme de fichiers normaux
I Chaque entrée d’un répertoire associe nom symbolique ↔ numéro d’inode
un répertoire
fich1 12415 table des inodes
rep1 6754
fich2 2362
rep2 26261
fich3 63663
rep2
main.c 46326
util.c 8432
util.h 8361
(102/210)
Manipulation de répertoires
int main() {
DIR *dirp;
struct dirent *dp;
dirp = opendir(".");
while ((dp = readdir(dirp)) != NULL) {
printf ("%s\t%d\n", dp->d name, dp->d ino);
}
closedir(dirp);
}
(103/210)
Protection des fichiers : généralités
Règles d’éthique
I Protéger ses informations confidentielles (comme les projets et TP notés !)
I Ne pas tenter de contourner les mécanismes de protection (c’est la loi)
I Règles de bon usage avant tout :
La possibilité technique de lire un fichier ne donne pas le droit de le faire
(104/210)
Protection des fichiers sous Unix
Sécurité des fichiers dans Unix
I Trois types d’opérations sur les fichiers : lire (r), écrire (w), exécuter (x)
I Trois classes d’utilisateurs vis à vis d’un fichier :
propriétaire du fichier ; membres de son groupe ; les autres
rwx rwx rwx
propriétaire groupe autres
Granularité plus fine avec les Access Control List (pas étudiés ici)
I Pour les répertoires, r = ls, w = créer des éléments et x = cd.
I ls -l pour consulter les droits ; chmod pour les modifier
Mécanisme de délégation
I Problème : programme dont l’exécution nécessite des droits que n’ont pas les
usagers potentiels (exemple : gestionnaire d’impression, d’affichage)
I Solution (setuid ou setgid) : ce programme s’exécute toujours sous l’identité
du propriétaire du fichier ; identité utilisateur momentanément modifiée
identité réelle (celle de départ) vs identité effective (celle après setuid)
(105/210)
Résumé du troisième chapitre
(106/210)
Quatrième chapitre
Exécution des programmes
Schémas d’exécution
Interprétation
Principe de fonctionnement d’un interpréteur
Exemple d’interpréteur : le shell
Conclusion
Schémas d’exécution d’un programme
(108/210)
Compilation et interprétation
Principe Programme Chaque instruction
Phase de Phase instruction I
compilation d’exécution
compil
source compilateur objet suite
exécution d’instructions
Compilation Machine Machine Machine traduisant I
virtuelle physique ( ?) physique actions
exécution
Phase
source d’interprétation instruction I
programme
Interprétation interpréteur actions simulant
l’effet de I
Machine
physique
exécution
Exemples
I Compilation : C, C++, Fortran, Ada, assembleur
I Interprétation : shell Unix, Tcltk, PostScript, langage machine
I Autre : Lisp (les deux), Java/Scala (machine virtuelle), Python (interprétation ou
machine virtuelle), Ocaml (interprétation, compilation ou machine virtuelle)
I On peut faire de la compilation croisée voire pire
(109/210)
Compilation et interprétation : comparaison
Compilation Interprétation
, Efficacité / Efficacité
I Génère du code machine natif I 10 à 100 fois plus lent
I Ce code peut être optimisé I appel de sous-programmes
I pas de gain sur les boucles
/ Mise au point , Mise au point
I Lien erreur ↔ source complexe I Lien instruction ↔ exécution trivial
I Trace et observation simples
/ Cycle de développement , Cycle de développement
I cycle complet à chaque modification : I cycle très court :
compilation, édition de liens, modifier et réexécuter
exécution
(110/210)
Schéma mixte d’exécution
Objectifs
I Combiner les avantages de la compilation et de l’interprétation
I Gagner en portabilité sans trop perdre en performances
Principe
I Définir une machine virtuelle (qui n’existe donc pas)
I Écrire un simulateur de cette machine virtuelle
I Écrire un compilateur du langage source vers le langage machine virtuel
(111/210)
Quatrième chapitre
Exécution des programmes
Schémas d’exécution
Interprétation
Principe de fonctionnement d’un interpréteur
Exemple d’interpréteur : le shell
Conclusion
Principe de fonctionnement d’un interpréteur
(113/210)
Exemple d’interpréteur : le shell
Définition
I Programme interface entre OS et utilisateur interactif shell
OS
Interpréteur le langage commande (→ appels Matériel
systèmes)
I Mot à mot, shell=coquille, car ça englobe l’OS
I Il existe plusieurs shells (sh, csh, tcsh, bash, zsh, etc.)
(114/210)
Quatrième chapitre
Exécution des programmes
Schémas d’exécution
Interprétation
Principe de fonctionnement d’un interpréteur
Exemple d’interpréteur : le shell
Conclusion
Composition de programmes
Problème : la liaison
I Un programme P appelle une procédure proc définie dans Q
I Appel de routine en assembleur : jump à une adresse donnée
I Question : quelle adresse associer à proc dans le code machine de P ?
I Réponse : on sait pas tant que le code machine de Q est inconnu (pourquoi ?)
I Solution : Dans P, liaison entre proc et son adresse faite après compilation
Édition de liens
(116/210)
Cycle de vie d’un programme compilé
I Exemples
I gcc -c prog.c ⇒ prog.o = programme objet translatable
I gcc -o prog prog.c ⇒ prog programme objet absolu (compilo + chargeur)
(117/210)
Édition de liens
Table symboles
proc
(118/210)
Exemples de commandes de compilation
(119/210)
Quelques problèmes de l’édition de liens
Théorie relativement simple...
I Définition manquante
$ gcc -o prog prog1.c prog2.c prog3.c
... /tmp/cckdgmLh.o(.text+0x19):
proc(x); In function ‘main’:
... undefined reference to ‘proc’
/* pas de définition de proc */ collect2: ld returned 1 exit status
$
I Définitions multiples
...
proc(x); $ gcc -o prog prog1.c prog2.c prog3.c
... $
int proc; /* déf. comme entier */
...
void proc(int x) {
... /* déf. comme fonction */
} I Pas de message d’erreur !
I Que se passe-t-il ?
(121/210)
Pièges des définitions multiples
Module A Module B
int proc(int i) { int proc(int i) {
Deux symboles forts : interdit
int x; int x;
int p1(int i) { int p2(int i) {
Deux symboles faibles
désignent le même objet
int x; /* 4o */ double x; /* 8o */
int y; /* 4o */ Deux symboles faibles.
int p1(int i) { int p2(int i) {
Modifier B.x écrase A.y ! !
int x=3;
double x;
int y;
int p2(int i) {
A.x fort ; B.x faible ;
int p1(int i) {
Modifier B.x écrase encore A.y ! !
I Contrôle de types strict entre modules pas obligatoire en C
⇒ Déclaration explicite des références externes nécessaire
Exercice : que se passe-t-il dans l’exemple précédent (deux pages avant) ?
int proc = symbole faible ; void proc(int x) = symbole fort, c’est lui qui est choisi
(mais comportement étrange quand on change int proc)
(122/210)
Déclarations des variables externes
I Règle 1 : utiliser des entête (.h) pour unifier les déclarations
I Règle 2 : éviter les variables globales autant que possible !
I déclarer static toute variable hors des fonctions (visibilité = fichier courant)
I Règle 3 : bon usage des globales (si nécessaire)
I chaque globale déclarée fortement dans un module M
I extern dans tous les autres (avec son type)
I Seulement consultation depuis l’extérieur, modification seulement depuis M
Fonctions dans M pour la modifier depuis ailleurs, au besoin (setters)
I Règle 4 : bon usage des fonctions
I déclaration explicite des fonctions externes (avec signature)
I mot clé extern optionnel : la signature (ou prototype) suffit
Fichier un.c Fichier deux.c Fichier trois.c
int x; extern int x; extern double y;
extern double y; double y; int f(int i);
static int z;
int f(int i) {... }
static int g(int i) {... }
/* x: écriture /* x: lecture /* x: invisible
y: lecture y: écriture y: lecture
z: écriture z: non-référençable z: non-référençable
f: accessible f: invisible f: accessible
g: accessible */ g: non-référençable */ g: non-référençable */
(123/210)
Exercices : (à compléter)
Module A Module B Que se passe-t-il ?
int f(int i) { ... } static double f=4.5; Pas de problème
f.B static ⇒ invisible d’ailleurs
#include <stdio.h>
Ca marche (et affiche 0x55).
void p2(void);
int main() {
char main; Link sans soucis : main.B est faible,
p2();
void p2() {
il est écrasé
return 0;
}
printf("Ox%x\n", Execution : ref(main.B) =
main);
}
def(main.A), et 0x55 est l’adresse du
main() à gauche
Exercice : pourquoi chaque programme doit contenir une fonction main() ?
car par convention, exec() passe le controle à une fonction de ce nom
(124/210)
Cycle de vie d’un programme : résumé
pile
gcc -c prog1.c
source binaire translatable
binaire exécutable
prog1.c prog1.o
copie
gcc -o prog prog1.o prog2.o prog (fork+exec) text
data
source binaire translatable
prog2.c prog2.o
prog <params>
gcc -c prog2.c
(125/210)
Quatrième chapitre
Exécution des programmes
Schémas d’exécution
Interprétation
Principe de fonctionnement d’un interpréteur
Exemple d’interpréteur : le shell
Conclusion
Bibliothèques
(127/210)
Bibliothèques statiques
I /usr/lib/libc.a : bibliothèque standard du langage C (printf, strcpy, etc.)
I /usr/lib/libm.a : bibliothèque mathématique (cos, sin, floor, etc.)
Utilisation :
I Passer bibliothèques utilisées à l’éditeur de lien (libc.a ajoutée par défaut)
gcc -o prog prog.c /usr/lib/libm.a
I L’éditeur de liens ne copie que les fonctions effectivement utilisées
I Notation abrégée : -l<nom> équivalent à /usr/lib/lib<nom>.a
Exemple : gcc -o prog prog.c -lsocket (inclut /usr/lib/libsocket.a)
I Chemin de recherche : Variable LIBPATH, ajout avec gcc -L<rep>
Format et création
I Archives de binaires translatables
I Manipulées par ar(1)
ar rcs libamoi.a fonct1.o fonct2.o fonct3.o
I Remarque : historiquement, tar est une version particulière de ar
(128/210)
Dépendances entre bibliothèques statiques
(129/210)
Bibliothèques dynamiques
I Défauts des bibliothèques statiques :
I Code dupliqué dans chaque processus les utilisant
I Liaison à la compilation (nouvelle version de bibliothèque ⇒ recompilation)
I Solution : Bibliothèques dynamiques :
Partage du code entre applications et chargement dynamique
I Exemples : Windows : DLL (dynamically linkable library) ; Unix : .so (shared object)
Construction
I gcc -shared -fPIC -o libpart.so fonct1.c fonct2.c fonct3.c
I -fPIC (compil.) : demande code repositionnable (Position Independent Code)
I -shared (éd. liens) : demande objet dynamique
(131/210)
Chargement dynamique de greffons sous linux
Greffon (plugin) :
I Bout de code chargeable dans une application pour l’améliorer
→ Choisir l’implémentation d’une fonctionnalité donnée (affichage, encodage)
→ Ajouter des fonctionnalités (mais bien plus dur, nécessite API dynamique)
bibliothèque
binaire statique
translatable
Compilateur
éditeur
générateur binaire de liens
pré-processeur de code translatable
programme
chargeur
arbre
analyseur syntaxique
bibliothèque binaire
dynamique exécutable
générateur machine
traducteur interpréteur de code physique
Actions
(135/210)
Résumé du quatrième chapitre
(136/210)
Cinquième chapitre
Synchronisation entre processus
Introduction : notions de base
Condition de compétition et exclusion mutuelle
Exclusion mutuelle par verrouillage de fichiers
Notion d’interblocage
Exclusion mutuelle par attente active
Problèmes de synchronisation (résumé)
Sémaphores et schémas de synchronisation
Sémaphores
Exclusion mutuelle
Cohorte
Rendez-vous
Producteurs / Consommateurs
Lecteurs / Rédacteurs
Autres outils de synchronisation
Moniteurs
Synchronisations POSIX
compare-and-swap
Conclusion
Problème de l’exclusion mutuelle
Exemple : deux banques modifient un compte en même temps
Agence Nancy Agence Karlsruhe
1. courant = get account(1867A) 1. aktuelles = get account(1867A)
2. nouveau = courant + 10 2. neue = aktuelles - 10
3. update account (1867A, nouveau) 3. update account(1867A, neue)
(138/210)
Réalisation d’une section critique
Schéma général
Processus 1 Processus 2
... ...
entrée en section critique entrée en section critique
section critique section critique
sortie de section critique sortie de section critique
... ...
Réalisation
I Attente active : processus à l’entrée section critique boucle un test d’entrée
I Inefficace (sur mono-processeur)
I Parfois utilisé dans conditions praticulière dans le noyau
I Primitives spéciales : fournies par le système
I Primitives générales : sémaphores, mutex (on y revient)
I Mécanismes spécifiques : comme verrouillage de fichiers (idem)
I Les primitives doivent être atomiques...
(139/210)
Exclusion mutuelle par verrouillage de fichiers
I Verrouiller une ressource garanti un accès exclusif à la ressource
I Unix permet de verrouiller les fichiers
I Appels systèmes de verrouillage/déverrouillage (noms symboliques) :
I verrouille(fich) : verrouille fich avec accès exclusif
I déverrouille(fich) : déverrouille fich
I Propriétés :
I Opérations atomiques (assuré par l’OS)
I Verrouillage par au plus un processus
I Tentative de verrouillage d’un fichier verrouillé ⇒ blocage du processus
I Deverrouillage ⇒ réveille d’un processus en attente du verrou (et un seul)
... ...
verrouille(fich); verrouille(fich);
accès à fich (section critique); accès à fich (section critique);
deverrouille(fich); deverrouille(fich);
... ...
(140/210)
Verrouillage partagé
(141/210)
Verrouillage de fichier sous Unix
Opérations disponibles
I Deux types de verrous : exclusifs ou partagés
I Deux modes de verrouillage :
I Impératif (mandatory ) : bloque les accès incompatibles avec verrous présents
(mode décrit précédemment – pas POSIX)
I Consultatif (advisory ) : ne bloque que la pose de verrous incompatibles
I Tous les verrous d’un fichier sont de même mode
I Deux primitives :
I fcntl : générale et complexe
I lockf : enveloppe plus simple, mais mode exclusif seulement (pas partagé)
I Possibilité de verrouiller fichier entier ou une partie
(142/210)
Interface du verrouillage de fichier sous Unix
#include <unistd.h>
int lockf(int fd, int commande, off t taille);
(143/210)
Exemple de verrouillage de fichiers Unix
testlock.c
#include <unistd.h>
int main (void) {
int fd;
fd = open("toto", O_RDWR); /* doit exister */ $ testlock & testlock &
while (1) { 15545: verrouille le fichier
if (lockf(fd, F_TLOCK, 0) == 0) { [4] 15545
printf("%d: verrouille le fichier\n", 15546: déjà verrouillé...
getpid()); [5] 15546
sleep(5); $ 15546: déjà verrouillé...
if (lockf(fd, F_ULOCK, 0) == 0) 15546: déjà verrouillé...
printf("%d: déverrouille le fichier\n", 15545: déverrouille le fichier
getpid()); 15546: verrouille le fichier
return; 15546: déverrouille le fichier
} else { $
printf("%d: déjà verrouillé...\n",
getpid());
sleep (2);
}
}
}
Remarques
I Fichier partagé entre deux banques difficile à réaliser
I Autres solutions techniques pour les systèmes distribués ; l’idée reste la même
(145/210)
Notion d’interblocage
Utilisation simultanée de plusieurs verrous ⇒ problème potentiel
Situation Déroulement
I Deux processus verrouillent deux fichiers Exécution (pseudo-)parallèle
Processus 1 Processus 2
I Première possibilité :
... ... 1a ; 1b ; 2a ; 2b
verrouille (f1) /* 1A */ verrouille (f2) /* 2A */
accès à f1 accès à f2 I Seconde possibilité :
... ... 2a ; 2b ; 1a ; 1b
verrouille (f2) /* 1B */ verrouille (f1) /* 2B */
accès à f1 et f2 accès à f1 et f2 I Troisième possibilité :
deverrouille (f2) deverrouille (f2)
deverrouille (f1) deverrouille (f1) 1a ; 2a ; 1b ; 2b
Définition
I Plusieurs processus bloqués dans l’attente d’une action de l’un des autres
I Impossible de sortir d’un interblocage sans intervention extérieure
Conditions d’apparitions
I Plusieurs processus en compétition pour les mêmes ressources
I Cycle dans la chaı̂ne des attentes
(147/210)
Situation réelle d’interblocage
(148/210)
Comment prévenir l’interblocage ?
Solution 1 : réservation globale
I Demandes en bloc de toutes les ressources nécessaires
I Inconvénient : réduit les possibilités de parallélisme
I Analogie du carrefour : mettre des feux tricolores (et les respecter)
Solution 2 : requêtes ordonnées
I Tous les processus demandent les ressources dans le même ordre
I Interblocage alors impossible
I Analogie du carrefour : construire un rond-point
verrouille (f1) verrouille (f1)
verrouille (f2) verrouille (f2)
accès à f1 et f2 accès à f1 et f2
deverrouille (f2) deverrouille (f2)
deverrouille (f1) deverrouille (f1)
Possibilités de guérison
I Faire revenir un processus en arrière
I Nécessite d’avoir un point de reprise (checkpoint)
I Travail depuis dernier point de reprise perdu
I Tuer l’un des processus pour casser le cycle
Conclusion
I Prévention et guérison sont tous les deux coûteux
I Prévention : l’application doit faire attention
I Guérison : détection + pertes dues au retour en arrière
I La meilleure solution dépend de la situation
(150/210)
Section critique par attente active
Principe (rappel)
I On demande tant qu’on a pas obtenu
Défaut
I Manque d’efficacité car gaspillage de ressources
Avantage
I Implémentable sans primitive de l’OS ni du matériel
⇒ utilisé dans certaines parties de l’OS, par exemple
(151/210)
Réalisation d’une section critique par attente active
(152/210)
Algorithme correct d’attente active
1. flag[MOI] = VRAI; /* Annonce ^
etre intéressé */
2. tour = 1-MOI; /* mais on laisse la priorité à l’autre */
3. while ((flag[1-MOI]==VRAI) /* on entre si l’autre ne veut pas entrer */
&& (tour == 1-MOI)); /* ou s’il nous a laissé la priorité */
4. CS();
5. flag[MOI] = FAUX; /* release */
Progression du processus 0
FF1 FV1 FV0 FV0 FF?
ligne 5
(154/210)
Cinquième chapitre
Synchronisation entre processus
Introduction : notions de base
Condition de compétition et exclusion mutuelle
Exclusion mutuelle par verrouillage de fichiers
Notion d’interblocage
Exclusion mutuelle par attente active
Problèmes de synchronisation (résumé)
Sémaphores et schémas de synchronisation
Sémaphores
Exclusion mutuelle
Cohorte
Rendez-vous
Producteurs / Consommateurs
Lecteurs / Rédacteurs
Autres outils de synchronisation
Moniteurs
Synchronisations POSIX
compare-and-swap
Conclusion
Sémaphore : outil de base pour la synchronisation
Cohorte : généralisation de l’exclusion mutuelle
I Ressource partagée par au plus N utilisateurs
I Exemples :
I Parking payant
I Serveur informatique
P(v) V(v)
Sémaphore
I Objet composé :
I D’une variable : valeur de la sémaphore (nombre de places restantes)
I D’une file d’attente : liste des processus bloqués sur la sémaphore
I Primitives associées :
I Initialisation (avec une valeur positive ou nulle)
I Prise (P, Probeer, down, wait) = demande d’autorisation (puis-je ?)
Si valeur = 0, blocage du processus ; Si non, valeur = valeur − 1
I Validation (V, Verhoog, up, signal) = fin d’utilisation (vas-y)
Si valeur = 0 et processus bloqué, déblocage d’un processus ;
Si non, valeur = valeur + 1
E.W. Dijkstra. Solution of a Problem in Concurrent Programming Control. 1965. (lire l’article)
(156/210)
Schémas de synchronisation
Situations usuelles se retrouvant lors de coopérations inter-processus
I Exclusion mutuelle : ressource accessible par une seule entitée à la fois
I Compte bancaire ; Carte son
I Problème de cohorte : ressource partagée par au plus N utilisateurs
I Un parking souterrain peut accueillir 500 voitures (pas une de plus)
I Un serveur doom peut accueillir 2000 joueurs
I Rendez-vous : des processus collaborant doivent s’attendre mutuellement
I Roméo et Juliette ne peuvent se prendre la main que s’ils se rencontrent
I Le GIGN doit entrer en même temps par le toit, la porte et la fenêtre
I Processus devant échanger des informations entre les étapes de l’algorithme
I Producteurs/Consommateurs : un processus doit attendre la fin d’un autre
I Une Formule 1 ne repart que quand tous les mécaniciens ont le bras levé
I Réception de données sur le réseau puis traitement
I Lecteurs/Rédacteurs : notion d’accès exclusif entre catégories d’utilisateurs
I Sur une section de voie unique, tous les trains doivent rouler dans le même sens
I Un fichier pouvant être lu par plusieurs, si personne ne le modifie
I Tâches de maintenance (défragmentation) quand pas de tâches interactives
Comment résoudre ces problèmes avec les sémaphores ?
(157/210)
Exclusion mutuelle par sémaphore
Très simple
Initialisation
sem=semaphore(1)
(158/210)
Cohortes et sémaphores
P(v) V(v)
(159/210)
Rendez-vous et sémaphores
I Envoi de signal
I Un processus indique quelque chose à un autre (disponibilité donnée)
Initialisation Processus 1 Processus 2
... ...
top=semaphore(0)
calcul(info) P(top) /*Bloque en attente*/
V(top) utilisation(info)
... ...
Réalisation
Initialisation Producteur Consommateur
placeDispo=semaphore(N) répéter répéter
infoPrete=semaphore(0) calcul(info) P(infoPrete)
P(placeDispo) extraire(info)
déposer(info) V(placeDispo)
V(infoPrete) utiliser(info)
I Le tampon doit être circulaire pour traiter données dans l’ordre de production
I Attention aux compétitions entre producteurs (idem entre consommateurs)
(161/210)
Lecteurs et rédacteurs
Contrôle d’accès exclusif entre catégories d’utilisateurs
Première solution
Initialisation Lecteur Rédacteur
lecteur=semaphore(1) P(lecteur) P(rédacteur)
rédacteur=semaphore(1) NbLect = NbLect + 1 lecturesEtEcritures()
NbLect=0 si NbLect == 1 alors V(rédacteur)
P(rédacteur)
V(lecteur)
lectures()
P(lecteur)
NbLect = NbLect - 1
si NbLect == 0 alors
V(rédacteur)
V(lecteur)
(163/210)
Cinquième chapitre
Synchronisation entre processus
Introduction : notions de base
Condition de compétition et exclusion mutuelle
Exclusion mutuelle par verrouillage de fichiers
Notion d’interblocage
Exclusion mutuelle par attente active
Problèmes de synchronisation (résumé)
Sémaphores et schémas de synchronisation
Sémaphores
Exclusion mutuelle
Cohorte
Rendez-vous
Producteurs / Consommateurs
Lecteurs / Rédacteurs
Autres outils de synchronisation
Moniteurs
Synchronisations POSIX
compare-and-swap
Conclusion
Moniteur
Problème des sémaphores
I Tous les processus doivent les utiliser correctement
I Mauvais comportement d’un seul ⇒ problème pour l’ensemble
I Oubli d’un P(mutex) : CS pas respectée
I Oubli d’un V(mutex) : deadlock (Deni de Service – DoS)
I Causes possibles :
I Erreur de programmation
I Programme malveillant
Solution : le moniteur (synchronized en Java)
I Sorte d’objet contenant :
I Des variables partagées (privées)
I Une sémaphore protectrice
I Des méthodes d’accès
I Le compilateur ajoute les appels à la sémaphore pour protéger les méthodes
I Erreur impossible car usage seulement à travers l’API protégée
C.A.R. Hoare. Monitors: An Operating System Structuring Concept. 1974. (lire l’article)
(165/210)
L’exemple des banques avec un moniteur Java
(166/210)
Synchroniser conjointement des méthodes Java
(167/210)
Synchronisation explicite en Java
Parfois, on veut contrôler finement la synchronisation
I synchroniser un morceau de la méthode, pas toute la méthode
I deux groupes de méthodes en exclusion mutuelle par groupe, mais pas globale
class Tutu {
synchronized void addObj(Object o){}
}class Toto { I Moniteur de Toto acquis dans addName
Tutu myTutu;
synchronized void addName(String n){ I Invocation de Tutu.addObj
nameCount++;
myTutu.addObj(name); ⇒ Moniteur Tutu verrouillé
} } ; deadlock toto ↔ tutu possible
class Tutu {
synchronized void addObj(Object o){} Verrouillage d’une partie de la méthode :
}class Toto {
Tutu myTutu; I Il faut spécifier l’objet verrouillé (ici : this)
void addName(String n){
synchronized(this) { I Verrou relaché avant Tutu.addObj
nameCount++;
} myTutu.addObj(name);
⇒ plus de deadlock possible
} } (attention à d’éventuelles compétitions)
Les verrous Java sont réentrants : Reprendre le même verrou n’est pas bloquant
(168/210)
Moniteurs et conditions
Moniteurs ne permettent pas d’attendre un événement
I Comparable au P() sur sémaphore à 0 (cf. place libérée sur le parking)
I Nouveau type de variable : condition x; (sorte de file d’attente)
I Primitives associées :
I x.wait() : Entre dans la file d’attente associée
I x.notify() : Libère un processus en attente (ou noop si personne n’attend)
I En Java, chaque objet a une condition implicite associée
John D. Valois. Lock-Free Linked Lists Using Compare-and-Swap. 1995. (lire l’article)
(171/210)
Résumé du cinquième chapitre
Problèmes à éviter lors des synchronisations (et définitions)
I Interblocage : groupe de processus en attente mutuelle
I Compétition : le résultat dépend de l’ordre d’exécution
I Famine : un processus n’obtient pas la ressource car les autres l’en empêchent
Les sémaphores
I Principe : distributeur de jetons
I Opérations :
I initialisation : mettre des jetons dans la boite
I P : prendre un jeton (ou bloquer s’il y en a plus)
I V : rendre un jeton pris
Schémas de synchronisation classiques
I Exclusion mutuelle : Ressource utilisée par au plus un utilisateur
I Cohorte : Ressource partagée entre au plus N utilisateurs
I Rendez-vous : un processus attend un autre, ou attente mutuelle
I Producteurs/consommateurs : utilisation après création
I Lecteurs/rédacteurs : concurrence entre catégories d’utilisateurs
(172/210)
Résumé du cinquième chapitre (suite)
Moniteurs
I Principe : Objet auto-protégé par sémaphore
I Avantage : Pas d’erreur de manipulation possible
I Usage en Java : synchronized
Variable de condition
I Principe : Comme une sémaphore privée pour la communication entre threads
I Avantage : Permet le passage de relai entre threads
I Usage en Java : wait()/notifyAll()
CAS (Compare-And-Swap)
I Principe : Tentative de modif. si personne n’a modifié depuis dernière lecture
I Avantages : Implementer les sémaphores ; algo lock-free
(173/210)
Conclusion générale
Il n’y a pas de magie noire dans l’ordinateur
I Même un OS est finalement assez simple, avec des idées très générales
I Concepts : processus, mémoire, système de fichiers, pseudo-parallélisme
I Idées : interposition, préemption, tout-est-fichier, bibliothèque de fonctions
I Synchro : compétition, interblocage, famine. Sémaphore, mutex, moniteur . . .
Modèles de threads
Threads utilisateur ou noyau, modèles M:1, 1:1 et M:N
Études de cas
Conclusion
Qu’est ce qu’un thread
Programme multi-threadé
I Un programme contenant diverses fonctions et procédures
I Ces procédures peuvent être exécutées simultanément et/ou indépendamment
(176/210)
Comparaison de processus et threads (1/2)
(177/210)
Comparaison de processus et threads (2/2)
Processus légers
routine2 Stack Pointer
I Plusieurs threads coexistent dans le Prog Counter
Pile varA
même processus lourd varB Registres
I Ils sont ordonnançables séparément
Stack Pointer
I Informations spécifiques routine1
Prog Counter
Pile var1 Registres
I Pile
var2
I Registres
I Priorité (d’ordonnancement) text
main()
I Données spécifiques (code) routine1()
I Liste des signaux bloqués routine2()
process id
user id
I Le reste est partagé data tableauA group id
I Si un thread ferme un fichier, (globales) tableauB
fichiers
il est fermé pour tous verrous
I Si un thread fait exit(), Tas sockets
tout s’arrête
I Globales et pointeurs vers le tas : Processus legers
variables partagées (à synchroniser)
(178/210)
Processus vs. thread en résumé
I Processus : environnement d’exécution pour les threads au sein de l’OS
État vis-à-vis de l’OS (fichiers ouverts) et de la machine (mémoire)
I Processus = Thread + Espace d’adressage
Process−specific
Registres Pile Registres Registres Registres
Thread−specific
Pile Pile Pile
(180/210)
Usage des threads
(181/210)
Quand (pourquoi) ne pas utiliser les threads
Problèmes du partage de la mémoire
I Risque de corruption mémoire (risque de compétition)
I Besoin de synchronisation (risque d’interblocage)
⇒ Communication inter-threads rapide mais dangereuse
I Segfault d’un thread → mort de tous
I Casse l’abstraction en modules indépendants
I Extrêmement difficile à debugger (dépendances temporelles ; pas d’outils)
Programmeurs
Débutants Magiciens
Programmer avec les threads, Programmeurs Visual Basic
Programmeurs C
c’est enlever les gardes-fous de l’OS Programmeurs C++
pour gagner du temps Programmeurs threads
Applications candidates
I Applis organisées en tâches indépendantes s’exécutant indépendamment
I Toutes routines pouvant s’exécuter en (pseudo-)parallèle sont candidates
I Interface interactive (; latence indésirable) avec des calculs ou du réseau
R2 R1 RoutineFinale()
Routine1() RoutineFinale()
Routine2()
(183/210)
Code réentrant et threads-safeness
Code réentrant
I Définition : Peut être appelé récursivement ou depuis plusieurs endroits
I Ne pas maintenir d’état entre les appels
I Contre-exemple : strtok, rand (strtok r est réentrante)
I Ne pas renvoyer un pointeur vers une statique
I Contre-exemple : ctime (ctime r est réentrante)
Code thread-safe
I Définition : Fonctionne même si utilisé de manière concurente
I Si le code n’est pas réentrant, il faut le protéger par verrous
Problème des dépendances
I Votre code est thread-safe, mais vos bibliothèques le sont-elles ?
I La libc est réentrante (sous linux modernes)
I Exemple de errno : chaque thread a maintenant son propre errno
I Pour le reste, il faut vérifier (voire, supposer qu’il y a un problème)
I En cas de problème, il faut protéger les appels grâce à un verrou explicite
(184/210)
Patterns classiques avec les threads
Maitre/esclaves
I Un thread centralise le travail à faire et le distribue aux esclaves
I Pool d’esclaves statique ou dynamique
I Exemple : serveur web
Pipeline
I La tâche est découpée en diverses étapes successives
Chaque thread réalise une étape et passe son résultat au suivant
I Exemple : traitement multimédia (streaming)
Peer to peer
I Comme un maitre/esclaves, mais le maitre participe au travail
(185/210)
Sixième chapitre
Programmer avec les threads
Introduction aux threads
Comparaison entre threads et processus
Avantages et inconvénients des threads
Design d’applications multi-threadées
Modèles de threads
Threads utilisateur ou noyau, modèles M:1, 1:1 et M:N
Études de cas
Conclusion
Implantation des threads : Modèle M:1
Tous les threads d’un processus mappés sur un seul thread noyau
Service implémenté dans une bibliothèque spécifique
I Gestion des threads au niveau utilisateur
I Avantages :
Espace utilisateur
I Pas besoin de modifier le noyau Processus Processus Processus
I Rapides pour créer un thread, et pour
changer de contexte
I Portables entre OS
Ordonnanceur Ordonnanceur Ordonnanceur
I Inconvénients :
I Ordonnancement limité à celui d’un
processus système Ordonnanceur
⇒ blocage de tous
I Pas de parallélisme (D’après F. Silber)
(187/210)
Modèle 1:1
Chaque thread utilisateur mappé sur un thread noyau
Modification du noyau pour un support aux threads
I Gestion des threads au niveau système (LightWeight Process – LWP)
I Avantages Espace utilisateur
(188/210)
Modèle M:N
M threads utilisateur mappés sur N threads noyau (M ≥ N ≥ 1)
Services utilisateur basés sur des services noyau
(189/210)
Études de cas (1/3)
Pthreads
I POSIX threads : norme standardisé par IEEE POSIX 1003.1c (1995)
I Ensemble de types et fonctions C dont la sémantique est spécifiée
I Seulement une API : implémentation sous tous les UNIX (et même Windows)
Threads Solaris
I Modèle M:N
I Threads intermédiaires (LWP)
Threads Windows 2000
I Modèle 1:1 pour les WinThreads
I Modèle M:1 pour les fibres (l’OS ne voit pas les fibres)
Threads Java
I Extension de la classe Thread ; Implantation de l’interface Runnable
I Implémentation dépend de la JVM utilisée
(190/210)
Études de cas (2/3)
Threads sous linux
I Threads mappé sur des tâches (tasks)
I Appel système clone() pour duppliquer une tâche (fork() implémenté avec ça)
Plusieurs bibliothèques implémentant le standard Pthreads
I Depuis 1996 : LinuxThreads
I Modèle 1:1, par Xavier Leroy (INRIA, créateur d’Ocaml)
I Pas complètement POSIX (gestion des signaux, synchronisation)
I ≈ 1000 threads au maximum
I Next Generation POSIX Threads
I Modèle M:N, par IBM ; abandonné
(192/210)
Sixième chapitre
Programmer avec les threads
Introduction aux threads
Comparaison entre threads et processus
Avantages et inconvénients des threads
Design d’applications multi-threadées
Modèles de threads
Threads utilisateur ou noyau, modèles M:1, 1:1 et M:N
Études de cas
Conclusion
Généralités sur l’interface Pthreads
Trois grandes catégories de fonctions / types de données
Gestion des threads
I Créer des threads, les arrêter, contrôler leurs attributs, etc.
Synchronisation par mutex (mutual exclusion)
I Créer, détruire verrouiller, déverrouiller des mutex ; Contrôler leurs attributs
Variables de condition :
I Communications entre threads partageant un mutex
I Les créer, les détruire, attendre dessus, les signaler ; Contrôler leurs attributs
(194/210)
L’interface Pthreads en pratique
I Types de données sont structures opaques, fonctions d’accès spécifiques
I L’interface compte 60 fonctions, nous ne verrons pas tout
I Il faut charger le fichier d’entête pthread.h dans les sources
I Il faut spécifier l’option -pthread à gcc
thread-ex1.c
#include <pthread.h>
void *hello( void *arg ) {
int *id = (int*)arg;
printf("%d: hello world \n", *id); $ gcc -pthread -o thread-ex1 thread-ex1.c
pthread_exit(NULL); $ ./thread-exemple1
} Crée thread 1
int main (int argc, char *argv[ ]) { Crée thread 2
pthread_t thread[3]; Crée thread 3
int id[3]={1,2,3}; 1 : hello world
int i; 2 : hello world
3 : hello world
for (i=0;i<3;i++) { $
printf("Crée thread %d\n",i);
pthread_create(&thread[i], NULL,
hello, (void *)&id[i]);
}
pthread_exit(NULL);
}
.
(195/210)
Identification et création des threads
Identifier les threads
I pthread t : équivalent du pid t (c’est une structure opaque)
I pthread t pthread self () : identité du thread courant
I int pthread equal (pthread t, pthread t) : test d’égalité
for (i=0;i<3;i++) {
printf("Crée thread %d\n",i);
pthread_create(&th[i], NULL, hello, (void *)&i);
}
pthread_exit(NULL);
}
.
(197/210)
Exemple de passage d’arguments complexes
int main(int argc, char *argv[ ]) {
pthread_t threads[NUM_THREADS];
int rc, t, sum=0;
#include <pthread.h>
#include <stdio.h>
char *messages[NUM_THREADS] = {
#include <stdlib.h>
"EN: Hello World!", "FR: Bonjour, le monde!",
#define NUM_THREADS 8
"SP: Hola al mundo", "RU: Zdravstvytye, mir!",
"DE: Guten Tag, Welt!", "Klingon: Nuq neH!",
typedef struct data {
"JP: Sekai e konnichiwa!",
int thread_id,sum;
"Latin: Orbis, te saluto!" };
char *msg;
} data_t;
for(t=0;t<NUM_THREADS;t++) {
sum = sum + t;
data_t data_array[NUM_THREADS];
thread_data_array[t].thread_id = t;
thread_data_array[t].sum = sum;
void *PrintHello(void *arg) {
thread_data_array[t].message = messages[t];
data_t *mine = (data_t *)arg;
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL,
sleep(1);
PrintHello,
printf("Thread %d: %s Sum=%d\n",
(void*)&thread_data_array[t]);
mine->thread_id,
if (rc) {
mine->msg,
printf("ERROR; _create() returned %d\n", rc);
mine->sum);
exit(-1);
pthread_exit(NULL);
}
}
}
pthread_exit(NULL);
}
(198/210)
Terminaison des threads
(199/210)
Attendre la fin d’un thread
Joindre un thread
I int pthread join ( pthread t, void ** )
I Le thread A joint le thread B : A bloque jusqu’à la fin de B
I Utile pour la synchronisation (rendez-vous)
I Second argument reçois un pointeur vers la valeur retour du pthread exit()
I Similaire au wait() après fork()
(mais pas besoin d’être le créateur pour joindre un thread)
Détacher un thread
I int pthread detach ( pthread t )
I Détacher un thread revient à dire que personne ne le joindra à sa fin
I Le système libère ses ressources au plus vite
I Évite les threads zombies quand on ne veut ni synchro ni code retour
I On ne peut pas ré-attacher un thread détaché
(200/210)
Attributs des threads
C’est le second argument du create
Les fonctions associées
I Création et destruction d’un objet à passer en argument à create :
I int pthread attr init ( pthread attr t * )
I int pthread attr destroy ( pthread attr t * )
I Lecture : int pthread attr getX ( const pthread attr t *, T * )
I Mise à jour : int pthread attr setX ( pthread attr t *, T )
Attributs existants
I detachstate (int) : PTHREAD CREATE [JOINABLE|DETACHED]
I schedpolicy (int)
I SCHED OTHER : Ordonnancement classique
I SCHED RR : Round-Robin (chacun son tour)
I SCHED FIFO : Temps réel
I schedparam (int) : priorité d’ordonnancement
I stackaddr (void*) et stacksize (size t) pour régler la pile
I inheritsched, scope
(201/210)
Exemple de gestion des threads
#include <pthread.h>
for(t=0;t<NUM_THREADS;t++) {
#include <stdio.h>
printf("Creating thread %d\n", t);
#include <stdlib.h>
if ((rc = pthread_create(&thread[t], &attr,
#define NUM_THREADS 3
travail, NULL))) {
printf("ERREUR de _create() : %d\n", rc);
void *travail(void *null) {
exit(-1);
int i;
}}
double result=0.0;
/* Libère l’attribut */
for (i=0; i<1000000; i++)
pthread_attr_destroy(&attr);
result = result + (double)random();
/* Attend les autres */
printf("Resultat = %e\n",result);
for(t=0;t<NUM_THREADS;t++) {
pthread_exit(NULL);
if ((rc = pthread_join(thread[t],
}
/*pas de retour attendu*/NULL))) {
printf("ERREUR de _join() : %d\n", rc);
int main(int argc, char *argv[ ]) {
exit(-1);
pthread_t thread[NUM_THREADS];
}
pthread_attr_t attr;
printf("Rejoint thread %d. (ret=%d)\n",
int rc, t;
t, status);
/* Initialise et modifie l’attribut */
}
pthread_attr_init(&attr);
pthread_exit(NULL);
pthread_attr_setdetachstate(&attr,
}
PTHREAD_CREATE_JOINABLE);
(202/210)
Sixième chapitre
Programmer avec les threads
Introduction aux threads
Comparaison entre threads et processus
Avantages et inconvénients des threads
Design d’applications multi-threadées
Modèles de threads
Threads utilisateur ou noyau, modèles M:1, 1:1 et M:N
Études de cas
Conclusion
Les mutex Pthreads
Interface de gestion
I Type de donnée : pthread mutex t
I Création :
I statique : pthread mutex t mutex = PTHREAD MUTEX INITIALIZER ;
I dynamique : pthread mutex init(pthread mutex t *, pthread mutexattr t *) ;
Premier argument : adresse de (pointeur vers) la variable à initialiser
I Destruction : pthread mutex destroy(mutex) (doit être déverrouillé)
I (pas d’attributs POSIX, mais il y en a dans certaines implémentations)
Interface d’usage
I pthread mutex lock(mutex) Bloque jusqu’à obtention du verrou
I pthread mutex trylock(mutex) Obtient le verrou ou renvoie EBUSY
I pthread mutex unlock(mutex) Libère le verrou
(204/210)
Usage des mutex
I Thread 3 crée une condition de compétition même si les autres sont disciplinés
(cf. les verrous consultatifs sur les fichiers)
I Pas d’équivalent des verrous impératifs sur la mémoire
I Pas de moniteurs dans PThreads (durs à implémenter en C)
(205/210)
Sémaphores POSIX
I On parle ici de l’interface POSIX, pas de celle IPC Système V
I #include <semaphore.h>
I Type : sem t
Interface de gestion
I int sem init(sem t *sem, int pshared, unsigned int valeur)
pshared !=0 ⇒ sémaphore partagée entre plusieurs processus (pas sous linux)
I int sem destroy(sem t * sem) (personne ne doit être bloqué dessus)
Interface d’usage
I int sem wait(sem t * sem) réalise un P()
I int sem post(sem t * sem) réalise un V()
I int sem trywait(sem t * sem) P() ou renvoie EAGAIN pour éviter blocage
I int sem getvalue(sem t * sem, int * sval)
(206/210)
Variables de condition
Interface de gestion
I Type de donnée : pthread cond t
I Création :
I statique : pthread cond t cond = PTHREAD COND INITIALIZER ;
I dynamique : int pthread cond init(pthread cond t *, pthread condattr t *)
(207/210)
Exemple d’usage des conditions
Thread principal
(1) Déclare et initialise une globale requérant une synchronisation (ex : compteur)
(1) Déclare et initialise une variable de condition et un mutex associé
(1) Crée deux threads (A et B) pour faire le travail
Thread A Thread B
(2) Travaille jusqu’au rendez-vous (2) Fait des choses
(point où B doit remplir une (3) Verrouille le mutex
condition, compteur > 0 par ex)
(3) Modifie la globale attendue par A
(2) Verrouille mutex et consulte variable
(3) Si la condition est atteinte, signale A
(2) Attend la condition de B
(3) Déverrouille le mutex
ça libère le mutex (pour B) et gèle A
(4) Au signal, réveil
mutex automagiquement repris
(4) Déverrouille explicitement
Thread principal : (5) Rejoint les deux threads puis continue
(208/210)
Les conditions POSIX
Interface d’usage
I pthread cond signal(pthread cond t *) Débloque un éventuel thread bloqué
I pthread cond wait(pthread cond t *, pthread mutex t *) Bloque tant que la
condition n’est pas réalisée
I pthread cond timedwait(pthread cond t*, pthread mutex t*,struct timespec*)
Bloque au plus tard jusqu’à la date spécifiée
I pthread cond broadcast(pthread cond t *) Réveille tous les bloqués (sont
encore bloqués par mutex)
Pièges des conditions
I Risque d’interblocage ou de pas de blocage si protocole du mutex pas suivi
I Différence sémantique avec sémaphore :
I Si sem==0, alors V(sem) ⇒ sem := 1
I Signaler une condition que personne n’attend : noop (info perdue)
(209/210)
Résumé du sixième chapitre
I Définition de threads (vs. processus) : proc=espace
d’adressage+meta-données OS ; thread=fil d’exécution
I Avantages et inconvénients : Ca va plus vite, mais c’est plus dangereux
I Schémas d’implémentations :
I Modèle M:1 : M threads par processus (portable et rapide, pas parallèle)
I Modèle 1:1 : Threads gérés par l’OS directement (plus dur à implémenter, plus
efficace en pratique)
I Modèle M:N : Gestion conjointe (modèle théorique meilleur, difficile à faire
efficacement)
I Les fonctions principales de l’interface POSIX :
I pthread create : crée un nouveau thread
I pthread exit : termine le thread courant
I pthread join : attend la fin du thread et récupère son errcode
I pthread detach : indique que personne ne rejoindra ce thread
I mutex {lock,unlock,*}: usage des mutex
I sem {wait,post,*}: usage des sémaphore
I cond {wait,signal,*}: usage des conditions
(210/210)