Programmation Système
Merci
• Alexandre Dang : Programmation système sous Linux et
Windows (électif 2A, Rennes)
• Frédéric Tronel et Pierre Wilke : Systèmes d'exploitation
(électif 2A, Rennes)
• Idir Ait Sadoune : Systèmes d'exploitation et virtualisation
(Mastère spécialisé ASI, Saclay)
2 ©2024 CentraleSupélec - Dominique Marcadet
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
3 ©2024 CentraleSupélec - Dominique Marcadet
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
4 ©2024 CentraleSupélec - Dominique Marcadet
Objectifs
• La programmation système est un type de programmation qui
vise au développement de programmes qui font partie du
système d’exploitation d’un ordinateur ou qui en réalisent les
fonctions [...]
Wikipédia
• Dans le cadre de ce cours, l'objectif est de découvrir et
d'utiliser quelques services de bas niveau fournis pas un
système d'exploitation.
• Linux est pris comme exemple pour les services retenus.
• Connaissances supposées : les mêmes que celles du cours
« Concepts des langages de programmation - Mise en œuvre
en C/C++ ».
5 ©2024 CentraleSupélec - Dominique Marcadet
Système d'exploitation
• Un système d’exploitation (ou OS, Operating System) est
une couche logicielle conceptuellement située entre le
matériel et les applications.
• Il o re aux programmeurs et aux utilisateurs un ensemble
d’abstractions qui permettent de ne pas avoir à gérer les
spéci cités du matériel.
• Les applications font des appels systèmes à des services
proposés par le système d’exploitation qui, seul, gère les
implications matérielles et retourne le résultat de l’appel.
6 ©2024 CentraleSupélec - Dominique Marcadet
ff
fi
Services
• Lancement et gestion des applications.
• Gestion de la mémoire.
• Gestion des E/S et des chiers.
• Gestion des périphériques.
• Coopération inter-tâches.
• Communications via le réseau.
7 ©2024 CentraleSupélec - Dominique Marcadet
fi
Espaces noyau et utilisateur
• Dans les systèmes d’exploitation modernes, il existe une distinction
nette entre l’espace noyau et l’espace utilisateur.
• Le noyau s’exécute dans une zone mémoire particulière et avec des
privilèges élevés.
• Il peut accéder à toute la mémoire, à tous les périphériques, et à
toutes les instructions du microprocesseur.
• Les applications s’exécutent pour leur part dans l’espace utilisateur.
• Elles ne peuvent accéder qu’à la partie de la mémoire que le noyau
leur a attribuée et ne peuvent utiliser qu’un sous-ensemble des
instructions du microprocesseur.
• Le noyau contrôle leurs accès aux périphériques via les pilotes.
8 ©2024 CentraleSupélec - Dominique Marcadet
Liens entre couches
• Appels syst mes : demandes
de services
• Noyau (kernel) : partie du
Appels Applications système d'exploitation
systèmes rendant des services en lien
Noyau
Pilotes avec le matériel
Matériel • Interruptions : v nements
produits par le mat riel
Exceptions Interruptions
• Exceptions : v nements
g n r s par le processeur
• Pilotes (drivers) : applications
contr lant les p riph riques
9 ©2024 CentraleSupélec - Dominique Marcadet
é
é
ô
é
è
é
é
é
é
è
é
é
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
10 ©2024 CentraleSupélec - Dominique Marcadet
Multics
• Multiplexed Information and Computing Service
(Wikipédia)
• Projet commun entre le MIT, les Bell Labs et General
Electric (GE), démarré en 1964.
• Les Bell Labs se sont retirés en 1969.
• C’est un système d’exploitation extrêmement novateur
mais qui a connu un succès commercial en demi-teinte.
• Les chercheurs des Bell Labs impliqués dans Multics
s’inspireront de certaines des idées présentes dans
Multics pour concevoir un système plus simple : Unix.
11 ©2024 CentraleSupélec - Dominique Marcadet
Unix : l'origine (1969)
• Les Bell Labs trouvent le projet Multics trop complexe et
ambitieux.
• Ils se retirent progressivement du projet.
• Les derniers chercheurs à s’être retirés, reconsidèrent la
construction d’un système d’exploitation multitâche de
conception plus simple que Multics.
• Les concepteurs : Ken Thompson, Brian Kernighan,
Dennis Ritchie
• Une première version est développée sur un PDP-7, dans
l'optique de faire tourner le jeu vidéo Space Travel.
12 ©2024 CentraleSupélec - Dominique Marcadet
Unix : l'expansion (1)
• Les Bell Labs fournissent un soutien aux trois chercheurs, à la
condition qu’ils écrivent un éditeur de texte et un système de mise en
page pour le nouvel OS.
• Celui-ci est porté sur PDP-11.
• Brian Kernighan suggère le nom d’Unics (Uniplexed Information and
Computing Service) qui est un jeu de mot à propos de Multics.
• Le terme UNIX remplacera rapidement celui d’Unics.
• Le logiciel de mise en page TROFF est développé et sera utilisé par
la suite pour la rédaction de l’ensemble des brevets de la compagnie.
• En 1972, UNIX est réécrit en C. Ceci permit de le porter plus
facilement vers d’autres processeurs.
13 ©2024 CentraleSupélec - Dominique Marcadet
Unix : l'expansion (2)
• En 1956, une condamnation dans un procès antitrust interdit à AT&T
de se lancer dans le commerce lié aux ordinateurs.
• Les Bell Labs qui sont une division de AT&T ne peuvent donc pas
commercialiser UNIX.
• Celui-ci est donc distribué de manière gratuite (avec le code source).
• UNIX devient de plus en plus utilisé et populaire dans les universités
américaines notamment pour l’enseignement des systèmes
d’exploitation.
• La septième version d’UNIX est produite en 1979.
• Cette version marque un changement de licence : il n’est plus
possible d’étudier le code source du système d’exploitation.
14 ©2024 CentraleSupélec - Dominique Marcadet
Unix : le début de la division
• En 1983, le département de la Justice américain (DOJ) entame
un second procès antitrust contre AT&T.
• Ce procès a pour résultat le découpage de la compagnie, mais
la relève de son interdiction de se lancer dans le commerce des
ordinateurs.
• AT&T en pro te pour commercialiser UNIX.
• Comme les conditions de distribution sont devenues moins
favorables pour les universités, l’université de Berkeley (UCB)
continue à développer sa propre version (Berkeley Software
Distribution).
• La branche BSD continue d'exister de nos jours, elle s’est
subdivisée en de nombreuses variantes.
15 ©2024 CentraleSupélec - Dominique Marcadet
fi
Unix : la division
• C’est aussi durant les années 80 que de nombreuses
compagnies privées se mettent à produire leur propres
versions d’UNIX, c’est ce qu’on a appelé la guerre des
UNIX.
• Malgré des tentatives de normalisation (dont POSIX), les
stratégies commerciales des di érentes compagnies ont
nalement nuit au succès commercial d’UNIX.
• Timeline
16 ©2024 CentraleSupélec - Dominique Marcadet
fi
ff
Unix : familles
17 ©2024 CentraleSupélec - Dominique Marcadet
GNU/Hurd
• Richard Stallman : projet de développer un système
d’exploitation libre et compatible Unix (1983).
• GNU « GNU’s Not UNIX ».
• Au début des années 1990, le projet GNU possède une
version utilisable de tous les éléments nécessaires à la
construction d’un système d’exploitation à l’exception du
plus central : le noyau.
• Projet GNU/Hurd : micro-noyau
18 ©2024 CentraleSupélec - Dominique Marcadet
Linux
• Linus Torvalds
• [Link]
(message, minix)
• Noyau monolithique
• Historique des versions
•
Crédit : [Link]
Les composants du noyau
• Nombreuses distributions
19 ©2024 CentraleSupélec - Dominique Marcadet
PDP-11 et shell
• Interface utilisateur de type
« ligne de commande »
• shell qui entoure le noyau
(kernel)
• Le premier (sh) pour les
versions 1 à 6 d'Unix
• De nombreuses variantes
existent maintenant
Crédit : Stefan_Kögl / CC BY-SA
20 ©2024 CentraleSupélec - Dominique Marcadet
De MS-DOS à MS-Windows 3
• MS-DOS 1.0, sorti en 1981, était un système d’exploitation 16
bits mono-utilisateur en ligne de commande. Il occupait 8 Ko
en mémoire.
• Deux versions de MS-Windows sortent respectivement en
1985 et 1987. Elles ne rencontrent pas un grand succès.
• Windows 3, sorti en 1990 pour l’Intel 386, se vend à plus d’un
million d’exemplaires en 6 mois. Il s’agit avant tout d’une
surcouche graphique à MS-DOS, pas d’un vrai système
d’exploitation.
• Même s’ils apportent de nombreuses innovations
technologiques (Multiprogrammation, gestion des processus,
mémoire virtuelle), MS-Windows 95, 98 et Me sont en fait
construits en interne sur la base MS-DOS.
21 ©2024 CentraleSupélec - Dominique Marcadet
De Windows NT 3.1 à Windows XP
• Parallèlement à Windows 3, Microsoft (après l'arrêt de sa
collaboration avec IBM sur OS/2) conçoit un système d’exploitation
entièrement 32 bits dédié aux serveurs, Windows NT, qui introduit
l’API Win32. La première version, Windows NT 3.1, sort en 1993.
• Windows NT 4, sorti en 1996, propose la même API Win32 mais
exhibe la même interface graphique que MS-Windows 95. 4
familles de processeurs sont supportés.
• Son successeur, Windows 2000, ne supportera plus de manière
active que les processeurs Intel x86.
• L’arrivée de Windows XP (2001) marque un tournant important dans
l’évolution des gammes d’OS Microsoft : pour la première fois, les
systèmes orientés professionnels et les systèmes orientés grand-
public sont basés sur le même noyau et l’API Win32.
22 ©2024 CentraleSupélec - Dominique Marcadet
De Windows Vista à Windows 11
• Windows Vista (2007), plus de 5 ans après Windows XP, a
reçu beaucoup de critiques.
• Windows 7 (2009) a été lui très bien accueilli
• Windows RT (2012) à destination des processeurs ARM
(Surface RT)
• Windows 10 en 2015, Windows 11 en 2021
• Timeline
23 ©2024 CentraleSupélec - Dominique Marcadet
macOS
• Mac OS Classic de 1984 à 2001(Système 1 à Mac OS 9)
• macOS (Mac OS X jusqu'en 2012, OS X jusqu'en 2015)
• Basé sur un noyau XNU et l'implémentation BSD d'Unix
(Darwin)
• Interface graphique propriétaire issue de Next
• OS dérivés : iOS, iPadOS, watchOS, tvOS, visionOS,
audioOS
24 ©2024 CentraleSupélec - Dominique Marcadet
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
25 ©2024 CentraleSupélec - Dominique Marcadet
Unix : tout est chier
• chier normal
• r pertoire
• lien symbolique : une r f rence vers un autre chier
• named pipe ou FIFO : un outil de communication
unidirectionnel entre processus locaux
• socket : un outil de communication bidirectionnel entre
processus distants
• p riph riques ( cran, imprimante, clavier, disque, clef USB...)
• processus (Linux)
26 ©2024 CentraleSupélec - Dominique Marcadet
fi
é
é
é
é
é
é
fi
fi
Descripteur de chier
• Tout ces chiers sont manipulables par des descripteurs de
chier
• En C ils sont repr sent s par des entiers non n gatifs (int)
→ STDIN_FILENO (0) : entrée standard
→ STDOUT_FILENO (1) : sortie standard
→ STDERR_FILENO (2) : sortie d'erreurs standard
• Avantage/d savantage:
+ API commune pour pouvoir g rer toutes les ressources
avec des descripteurs de chiers
→ open(), creat(), read(), write(), fcntl() et close()
- Fonctions g n riques et de bas niveau, ne permettant pas
une utilisation simple de ressources sp ci ques
27 ©2024 CentraleSupélec - Dominique Marcadet
fi
fi
é
é
é
é
é
fi
é
é
fi
fi
é
Flux de donn es
• Surcouche aux descripteurs de chiers pour les chiers
normaux
• Dans la bibliothèque standard C (#include <stdio.h>)
→ Repr sentation : FILE *
→ fopen(), fclose(), fread(), fwrite(),
fscanf(), fprintf()...
• Avantages
+ Portabilit , appartient au standard C
+ Performance, gestion plus optimis e des lectures/ critures
+ Simplicit , fonctions sp cialis es dans la gestion de chiers
normaux
• Trois ux standards pour chaque processus
→ stdin, stdout et stderr
28 ©2024 CentraleSupélec - Dominique Marcadet
fl
é
é
é
é
é
fi
é
é
fi
é
fi
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
29 ©2024 CentraleSupélec - Dominique Marcadet
Programme et processus
• Un programme est l’ensemble des instructions et données
décrivant l’apparence et le comportement d’une application
• Un programme est stocké dans un chier
• Un programme est statique
• Un programme est exécutable
• Un processus est l’ensemble des informations décrivant l’état d’un
programme en cours d’exécution
• Un processus n’existe qu’en mémoire
• Un processus est dynamique
• Plusieurs processus peuvent être associés à un même
programme
30 ©2024 CentraleSupélec - Dominique Marcadet
fi
Analogie culinaire classique
• Recette = programme
• Cuisinier = processeur
• Plat en cours de préparation = processus
• Un cuisinier peut préparer simultanément plusieurs plats
(certains pouvant correspondre à la même recette)
31 ©2024 CentraleSupélec - Dominique Marcadet
Gestion des processus
• Le système d’exploitation a en charge :
• La création de processus
• La répartition de l’utilisation du processeur par les
di érents processus (ordonnanceur, scheduler)
• La communication entre processus (copier/coller,
mémoire partagée…)
• La gestion de l’utilisation des ressources par les
processus
• mémoire
• entrées/sorties
32 ©2024 CentraleSupélec - Dominique Marcadet
ff
États d'un processus
Terminaison
Actif
Fin du quantum Demande d’entrée/sortie
ou de ressource
Suppression
Élection
Prêt Bloqué
Naissance Fin d’entrée/sortie
ou ressource disponible
33 ©2024 CentraleSupélec - Dominique Marcadet
Mémoire vue d'un processus
• Chaque processus pense
Pile être le seul à s'exécuter sur
l'ordinateur (hors aspects
liés aux communications).
• Un processus voit un
Tas espace mémoire illimité (de
Données globales
l'adresse 0 à l'adresse ...).
Code • Les adresses manipulées
par un processus sont dites
logiques.
34 ©2024 CentraleSupélec - Dominique Marcadet
Mémoire vue du SE
• Le système d'exploitation a accès à la mémoire réelle, le
noyau utilise des adresses physiques.
• Il alloue à chaque processus une partie de cette mémoire
réelle.
• Il con gure le composant matériel (MMU) chargé de
convertir les adresses logiques du processus actif en
adresses physiques.
• Les ordinateurs actuels (processeur et système
d'exploitation) utilisent le mécanisme de pagination pour
cela (ils o rent aussi la mémoire virtuelle).
35 ©2024 CentraleSupélec - Dominique Marcadet
fi
ff
Bloc de contexte
• Lors du changement de processus actif, l'ordonnateur doit
sauvegarder l'état du processus courant :
• registres du processeur,
• ressources utilisées :
• espace mémoire utilisé (con guration de la
pagination),
• chiers ouverts,
• canaux de communications utilisés,
• ...
• et restaurer celui du processus qui va (re)devenir actif
36 ©2024 CentraleSupélec - Dominique Marcadet
fi
fi
Types de processus
• Di érents types de processus
• Processus contrôlé par le shell
./[Link]
• Processus en tâche de fond
gedit &
• Services (daemon)
httpd, sshd…
37 ©2024 CentraleSupélec - Dominique Marcadet
ff
Création d'un processus
• La seule fa on de cr er un processus est de dupliquer le
processus courant avec fork()
pid_t fork();
• retourne 0 si on se trouve dans le processus ls
• retourne le PID du ls si on se trouve dans le processus
p re
• Un processus possède un identi ant unique appelé PID
pid_t getpid();
• Tous les processus ont un anc tre commun : le processus
lanc au d marrage (PID = 1).
38 ©2024 CentraleSupélec - Dominique Marcadet
è
é
é
ç
fi
é
ê
fi
fi
Changement de programme
• Pour changer le programme d'un processus (en g n ral le
ls), il faut appeler une fonction de la famille exec() :
int execl( const char * path,
const char * arg, ..., NULL );
int execv( const char * path,
char * const argv[] );
39 ©2024 CentraleSupélec - Dominique Marcadet
fi
é
é
Démo
GitLab
cours/progsyst/Fork.c
5- 40 ©2024 CentraleSupélec - Dominique Marcadet
Terminaison d'un processus
• Terminaison normale
return depuis la fonction main()
void exit( int status );
• Terminaison anormale
void abort();
void assert( expression );
• Attente de la terminaison d'un processus ls
pid_t wait( int * status );
41 ©2024 CentraleSupélec - Dominique Marcadet
fi
Gestion de la mémoire
• Gestion de bas niveau
void * mmap(
void * addr, size_t length, int prot,
int flags, int fd, off_t offset );
int munmap( void * addr, size_t length );
• Gestion via libc
void * malloc( size_t size );
void free( void * ptr );
void * calloc( size_t nmemb, size_t size );
void * realloc( void * ptr, size_t size );
42 ©2024 CentraleSupélec - Dominique Marcadet
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
43 ©2024 CentraleSupélec - Dominique Marcadet
IPC
• Douglas McIlroy (1978) et Peter H. Salus (1994) :
• Philosophie Unix
• Write programs that do one thing and do it well.
• Write programs to work together.
• Write programs to handle text streams, because that
is a universal interface.
• Les outils de communication entre processus sont
commun ment appel s IPC (Inter-Process
Communication)
44 ©2024 CentraleSupélec - Dominique Marcadet
é
é
Différents IPC
• Signaux
• Pipes ou tubes
• Sockets
• Fichiers partag s
• M moire partag e
• (Files d’attente de message)
45 ©2024 CentraleSupélec - Dominique Marcadet
é
é
é
Signaux
• Message asynchrone d’un processus un autre
• Envoi des signaux avec
int kill( pid_t pid, int sig );
• Action sur réception d'un signal :
int sigaction( int signum,
const struct sigaction * act,
struct sigaction * oldact);
• Signaux standards
SIGINT (Ctrl+C), SIGQUIT (Ctrl+D) …
46 ©2024 CentraleSupélec - Dominique Marcadet
à
Table des descripteurs
• Le noyau alloue des ressources I/O pour chaque
processus sous forme de descripteurs de chiers. La
plupart des IPC sont repr sent s par des descripteurs de
chiers.
47 ©2024 CentraleSupélec - Dominique Marcadet
fi
é
é
fi
Pipes
• Les pipes sont des IPC unidirectionnels
48 ©2024 CentraleSupélec - Dominique Marcadet
Exemple pipe (1)
• On veut un pipe dans lequel un processus ls peut crire
des messages au processus p re
// Tableau pour 2 descripteurs de fichier
int fds[2];
// Cr ation d'un pipe, les bouts sont stock s dans fd
pipe( fds );
pid_t pid = fork();
if( pid == 0 ) { // Processus fils
close( fds[0] ); // Fermeture du c t lecture
// ...
} else { // Processus père
close( fds[1] ); // Fermeture du c t écriture
// On raccroche stdin au c t lecture du pipe
dup2( fds[0], STDIN_FILENO );
// ...
}
49 ©2024 CentraleSupélec - Dominique Marcadet
é
è
ô
é
fi
ô
ô
é
é
é
é
Exemple pipe (2)
dup2( close(
fd[0], fd[1])
stdin ) État initial
fork()
pipe( fds ) close( fd[0])
stdin = 0 stdin = 0
stdout = 1 stdout = 1
stderr = 2 stderr = 2
fd[0] fd[0]
fd[1] fd[1]
50 ©2024 CentraleSupélec - Dominique Marcadet
Démo
GitLab
cours/progsyst/Pipe.c
5- 51 ©2024 CentraleSupélec - Dominique Marcadet
Sockets
• Les sockets sont des IPC bidirectionnels pouvant utiliser
les protocoles réseaux (UDP, TCP…)
52 ©2024 CentraleSupélec - Dominique Marcadet
Fonctions sockets (1)
• Création d'un socket
int socket( int domain, int type, int protocol );
• Liaison de la socket une adresse IP et un port
int bind( int fd, const struct sockaddr * addr,
socklen_t addrlen );
• Description de l'adresse
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; //localhost
address.sin_port = htons( PORT );
53 ©2024 CentraleSupélec - Dominique Marcadet
à
Fonctions sockets (2)
• Attente de connexion par le serveur
int listen( int sockfd, int backlog );
• Ouverture de la connexion par le client
int connect( int fd, const struct sockaddr * addr,
socklen_t addrlen );
• Acceptation par le serveur de la connexion
int accept( int fd, const struct sockaddr * addr,
socklen_t addrlen );
54 ©2024 CentraleSupélec - Dominique Marcadet
Ordre des octets
• Le nombre sur 32 bits [Link]
int i = 0x12345678; [Link]
• peut être stocké en gros-boutiste (big-endian)
12 34 56 78
&i &i+1
• ou en petit-boutiste (little-endian)
78 56 34 12
&i &i+1
• Des ordinateurs qui échangent des données doivent se
mettre d'accord sur l'ordre des octets utilisé : fonctions
htonl(), htons(), ntohl(), ntohs()
55 ©2024 CentraleSupélec - Dominique Marcadet
Fichiers partagés
• Ouverture du même chier par plusieurs processus
int open( const char * name, int flags, mode_t mode );
• Mappage possible en mémoire
void * mmap(
void * addr, size_t length, int prot,
int flags, int fd, off_t offset );
56 ©2024 CentraleSupélec - Dominique Marcadet
fi
Mémoire partagée
• Une mémoire partagée est nommée, comme un chier
int shm_open( const char * name, int oflag, mode_t mode );
• Mappage en mémoire
57 ©2024 CentraleSupélec - Dominique Marcadet
fi
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
58 ©2024 CentraleSupélec - Dominique Marcadet
Processus légers (threads)
• Le temps de création (et de changement) d’un processus
est élevé (… mémoire virtuelle … mémoire cache …).
• Un thread a son propre compteur ordinal, sa propre pile ...
mais n’existe qu’à l’intérieur d’un processus et utilise ses
ressources (espace mémoire en particulier).
• Un thread a aussi un état (actif, prêt, bloqué), un bloc de
contexte (plus petit !).
• C'est aussi le système d'exploitation qui gère les threads.
59 ©2024 CentraleSupélec - Dominique Marcadet
Threads POSIX (1)
• Creation
int pthread_create(
pthread_t * thread_handle,
const pthread_attr_t * attribute,
void * (*thread_function)( void * ),
void * arg
);
• Terminaison
void pthread_exit(
void * ptr
);
60 ©2024 CentraleSupélec - Dominique Marcadet
Threads POSIX (2)
• Obtenir son identi ant de thread
pthread_t pthread_self();
• Attente de terminaison (sinon le thread continue d'exister)
int pthread_join(
pthread_t thread,
void ** ptr
);
• Détachement (il n'est plus possible de l'attendre)
int pthread_detach(
pthread_t thread
);
61 ©2024 CentraleSupélec - Dominique Marcadet
fi
C++ : std::thread
class thread {
public:
template< class F > explicit thread( F f );
void join();
void detach();
id get_id() const;
// ... C++20 : std::jthread
};
namespace this_thread {
id get_id();
void sleep_until( /* ... */ );
void sleep_for( /* ... */ );
// ...
}
62 ©2024 CentraleSupélec - Dominique Marcadet
Utilisation std::thread
void f1( int n ) { /* ... */ }
void f2( int & n ) { /* ... */ }
struct F3 {
void operator()() { /* ... */ }
// ...
};
int main() {
int n1{ 5 }, n2{ -2 };
std::thread t1{ f1, n1 };
std::thread t2{ f2, std::ref( n2 )};
F3 f3;
std::thread t3{ f3 };
[Link](); [Link](); [Link]();
}
63 ©2024 CentraleSupélec - Dominique Marcadet
Démo
GitLab
cours/progsyst/[Link]
5- 64 ©2024 CentraleSupélec - Dominique Marcadet
std::async et std::future
int foo()
{
std::this_thread::sleep_for( 10ms );
return 42;
}
int main()
{
std::future< int > f{ std::async( foo )};
while( f.wait_for( 1ms ) ==
std::future_status::timeout ) {
std::cout << "waiting\n";
}
std::cout << "got " << [Link]() << "\n";
}
65 ©2024 CentraleSupélec - Dominique Marcadet
Démo
GitLab
cours/progsyst/[Link]
5- 66 ©2024 CentraleSupélec - Dominique Marcadet
Plan
• Présentation
• Introduction
• Historique
• Programmation sous Unix
• Fichier
• Processus
• Communication
• Threads
• Synchronisation
67 ©2024 CentraleSupélec - Dominique Marcadet
Plan
• Présentation
• Programmation sous Unix
• Fichier
• Processus et threads
• Communication
• Synchronisation
• Problématique
• Synchronisation de threads
• Synchronisation de processus
68 ©2024 CentraleSupélec - Dominique Marcadet
Problème de la variable partagée
DAB Compte Employeur
solde
credit()
retrait() debit() paiement()
[Link](...); [Link](...);
69 ©2024 CentraleSupélec - Dominique Marcadet
Version incorrecte
1. Chargement de
class Compte {
public: l'attribut solde (en
void credit( double montant ) {mémoire) dans un
solde += montant; registre du processeur
}
2. Ajout de montant au
void debit( double montant ) { registre
solde -= montant;
} 3. Transfert du contenu
private:
du registre en
double solde; mémoire (dans
} l'attribut solde)
70 ©2024 CentraleSupélec - Dominique Marcadet
Exécution correcte
Thread Thread
DAB Employeur
‣ LDR solde ‣ LDR solde
‣ SUB 20 ‣ ADD 50
‣ STR solde ‣ STR solde
100
80 100
130
80 130
80
Registre Registre
71 ©2024 CentraleSupélec - Dominique Marcadet
Exécution incorrecte
Thread Go
Stop Go
Stop
Thread
DAB Employeur
‣ LDR solde ‣ LDR solde
‣ SUB 20 ‣ ADD 50
‣ STR solde ‣ STR solde
100
80 150
100
80 150
100
Registre Registre
72 ©2024 CentraleSupélec - Dominique Marcadet
Le dîner des philosophes
• Interblocage
• Famine
73 ©2024 CentraleSupélec - Dominique Marcadet
Dé nitions
• Ressource critique : ressource qui ne doit pas être
accédée par plusieurs processus simultanément
• Section critique : partie(s) de code accédant à une
ressource critique
• Exclusion mutuelle : mécanisme permettant l’accès à une
ressource critique
74 ©2024 CentraleSupélec - Dominique Marcadet
fi
Propriétés
• Sûreté : au plus un seul processus utilise la ressource à un
moment donné
• Vivacité :
• Un processus obtient l’accès immédiat à une ressource
disponible
• Un processus obtient l’accès à la ressource au bout
d’un temps ni
• E cacité : un processus en attente d’une ressource
n’utilise pas le processeur (il est bloqué)
75 ©2024 CentraleSupélec - Dominique Marcadet
ffi
fi
Solutions
• Il existe des algorithmes (algorithme de Peterson,
algorithme de Dekker), mais ceux-ci imposent une attente
active (e cacité non respectée)
• C'est le système d'exploitation qui peut mettre un thread
ou un processus à l'état bloqué, donc c'est lui qui doit
fournir le service
• Une implémentation e cace impose l'existence d'une
transaction de lecture/écriture au niveau du bus et
l'existence d'une instruction correspondante sur le
processeur (test-and-set, compare-and-swap…)
76 ©2024 CentraleSupélec - Dominique Marcadet
ffi
ffi
Plan
• Présentation
• Programmation sous Unix
• Fichier
• Processus et threads
• Communication
• Synchronisation
• Problématique
• Synchronisation de threads
• Synchronisation de processus
77 ©2024 CentraleSupélec - Dominique Marcadet
Mutex POSIX
• Création
int pthread_mutex_init(
pthread_mutex_t * mutex_lock,
const pthread_mutexattr_t * lock_attr
);
• Acquisition du mutex
int pthread_mutex_lock(
pthread_mutex_t * mutex_lock
);
• Libération du mutex
int pthread_mutex_unlock(
pthread_mutex_t * mutex_lock
);
78 ©2024 CentraleSupélec - Dominique Marcadet
Compte avec mutex
class Compte {
public:
Compte() { pthread_mutex_init( &lock, NULL ); }
void credit( double montant ) {
pthread_mutex_lock( &lock );
solde += montant;
pthread_mutex_unlock( &lock );
}
void debit( double montant ) {
pthread_mutex_lock( &lock );
solde -= montant;
pthread_mutex_unlock( &lock );
}
private:
double solde;
pthread_mutex_t lock;
}
79 ©2024 CentraleSupélec - Dominique Marcadet
Producteur - Consommateur
• Le producteur dépose des données dans une boîte à
lettres de taille xe.
• Le consommateur les retire.
• Synchronisation :
• le consommateur doit attendre qu'une donnée soit
présente dans la boîte à lettres ;
• le producteur doit attendre d'avoir de la place dans la
boîte à lettres.
• Variantes : plusieurs producteurs et consommateurs.
80 ©2024 CentraleSupélec - Dominique Marcadet
fi
Moniteur
• Hansen (1973), Hoare (1974)
• Un moniteur est un module constitué de :
• fonctions d'accès à la ressource critique en exclusion
mutuelle (un verrou)
• condition booléenne et services permettant le blocage
d'un processus/thread (avec libération du verrou) si la
condition n'est pas remplie et réactivation par un autre
processus/thread quand la condition peut être remplie
(l'accès à la ressource critique reste soumise au verrou)
• Mécanisme de base de synchronisation en Java (classe
Object)
81 ©2024 CentraleSupélec - Dominique Marcadet
Variable de condition POSIX (1)
• Création
int pthread_cond_init(
pthread_cond_t * cond,
const pthread_condattr_t * attr
);
• Destruction
int pthread_cond_destroy(
pthread_cond_t * cond
);
82 ©2024 CentraleSupélec - Dominique Marcadet
Variable de condition POSIX (2)
• Attente
int pthread_cond_wait(
pthread_cond_t * cond,
pthread_mutex_t * mutex
);
• Signalisation
int pthread_cond_signal(
pthread_cond_t * cond
);
83 ©2024 CentraleSupélec - Dominique Marcadet
Exemple POSIX (1)
pthread_cond_t data_place_cond;
pthread_mutex_t data_place_mutex;
volatile int data_available = 0;
int main() {
pthread_t c, p;
pthread_cond_init( &data_place_cond, NULL );
pthread_mutex_init( &data_place_mutex, NULL );
pthread_create( &c, NULL, &consumer, NULL );
pthread_create( &p, NULL, &producer, NULL );
pthread_join( c, NULL );
pthread_join( p, NULL );
}
84 ©2024 CentraleSupélec - Dominique Marcadet
Exemple POSIX (2)
void * producer( void * producer_thread_data ) {
while( !done() ) {
create_data();
pthread_mutex_lock( &data_place_mutex );
if( data_available == 1 ) {
pthread_cond_wait(
&data_place_cond,
&data_place_mutex );
}
put_data_into_place();
data_available = 1;
pthread_cond_signal( &data_place_cond );
pthread_mutex_unlock( &data_place_mutex );
}
}
85 ©2024 CentraleSupélec - Dominique Marcadet
Exemple POSIX (3)
void * consumer( void * consumer_thread_data ) {
while( !done() ) {
pthread_mutex_lock( &data_place_mutex );
if( data_available == 0 ) {
pthread_cond_wait(
&data_place_cond,
&data_place_mutex );
}
get_data_from_place();
data_available = 0;
pthread_cond_signal( &data_place_cond );
pthread_mutex_unlock( &data_place_mutex );
process_data();
}
}
86 ©2024 CentraleSupélec - Dominique Marcadet
C++ : std::mutex
class mutex {
public:
void lock();
bool try_lock();
void unlock();
// ...
};
class recursive_mutex { /* ... */ };
class timed_mutex {
public:
bool try_lock_for( /* ... */ );
bool try_lock_until( /* ... */ );
// ...
};
class recursive_timed_mutex { /* ... */ };
87 ©2024 CentraleSupélec - Dominique Marcadet
C++ : std::lock_guard
template< typename Mutex >
class lock_guard {
public:
explicit lock_guard( Mutex & m );
~lock_guard();
// ...
};
template< typename Mutex >
class unique_lock {
public:
void lock();
bool try_lock();
void unlock();
bool try_lock_for( /* ... */ );
bool try_lock_until( /* ... */ );
// ...
};
88 ©2024 CentraleSupélec - Dominique Marcadet
C++ : std::condition_variable
class condition_variable {
public:
void notify_one();
void notify_all();
void wait( unique_lock< mutex > & lock );
bool wait_until( /* ... */ );
bool wait_for( /* ... */ );
// ...
};
class condition_variable_any { /* ... */ };
89 ©2024 CentraleSupélec - Dominique Marcadet
C++ : exemple (1)
std::condition_variable data_place_cond;
std::mutex data_place_mutex;
volatile bool data_available{ false };
int main() {
std::thread c{ consumer{} }, p{ producer{} };
[Link]();
[Link]();
}
90 ©2024 CentraleSupélec - Dominique Marcadet
C++ : exemple (2)
struct producer {
void operator()() {
while( !done() ) {
create_data();
{
std::unique_lock lock{ data_place_mutex };
if( data_available ) {
cond_place_empty.wait( lock );
}
put_data_into_place();
data_available = true;
cond_place_full.notify_one();
}
}
}
};
91 ©2024 CentraleSupélec - Dominique Marcadet
C++ : exemple (3)
struct consumer {
void operator()() {
while( !done() ) {
{
std::unique_lock lock{ data_place_mutex };
if( ! data_available ) {
cond_place_full.wait( lock );
}
get_data_from_place();
data_available = false;
cond_place_empty.notify_one();
}
process_data();
}
}
};
92 ©2024 CentraleSupélec - Dominique Marcadet
Démo
GitLab
cours/progsyst/[Link]
5- 93 ©2024 CentraleSupélec - Dominique Marcadet
Variable partagée (KO)
void f( int & x ) { /*...*/ x += 3; }
int main() {
int n{ 2 };
std::thread t1{ f, std::ref( n )};
std::thread t2{ f, std::ref( n )};
[Link]();
[Link]();
}
94 ©2024 CentraleSupélec - Dominique Marcadet
Variable partagée (OK)
class ProtectedInt {
public:
ProtectedInt( int n ) : n_{ n } {}
void operator+=( int i ) {
std::lock_guard< std::mutex > l( mutex_ );
n_ += i;
}
private:
int n_; std::mutex mutex_;
};
void f( ProtectedInt & x ) { /*...*/ x += 3; }
int main() {
ProtectedInt n{ 2 };
std::thread t1{ f, std::ref( n )};
std::thread t2{ f, std::ref( n )};
[Link](); [Link]();
}
95 ©2024 CentraleSupélec - Dominique Marcadet
std::atomic
void f( std::atomic_int & x ) {
/*...*/
x += 3;
}
int main() {
std::atomic_int n{ 2 };
std::thread t1{ f, std::ref( n )};
std::thread t2{ f, std::ref( n )};
[Link]();
[Link]();
}
96 ©2024 CentraleSupélec - Dominique Marcadet
Plan
• Présentation
• Programmation sous Unix
• Fichier
• Processus et threads
• Communication
• Synchronisation
• Problématique
• Synchronisation de threads
• Synchronisation de processus
97 ©2024 CentraleSupélec - Dominique Marcadet
Différent des threads ?
• La synchronisation entre processus ne peut être que gérée
par le système d'exploitation
• La frontière d'un processus (espace mémoire) est aussi
une frontière pour les langages
• C'est aussi le cas pour la communication entre
processus !
• Les systèmes d'exploitation sont trop variés (parce que les
besoins sont multiples) pour imposer une solution unique
98 ©2024 CentraleSupélec - Dominique Marcadet
Solutions déjà vues
• Certains mécanismes de communication intègrent un
mécanisme de synchronisation
• pipe
• socket
• D'autres mécanismes de communication o rent des
solutions spéci ques
• chiers partagés : un blocage (avec fcntl()) peut être
utilisé pour interdire l'accès à un chier
99 ©2024 CentraleSupélec - Dominique Marcadet
fi
fi
fi
ff
Solutions pour les autres cas
• Par exemple, comment gérer la synchronisation dans le cas
d'une communication par mémoire partagée ?
• Plusieurs solutions
• Se limiter à un seul système d'exploitation
• il faut quand même gérer les di érentes variantes et
versions
• Écrire du code spéci que à chaque système d'exploitation
• di culté de maintenance
• Utiliser une bibliothèque qui o re une interface uni ée
• l'ensemble des possibilités ne sera pas disponible
100 ©2024 CentraleSupélec - Dominique Marcadet
ffi
fi
ff
ff
fi
Sémaphore (1)
• Djikstra (1965)
• Un sémaphore est constitué :
• D’un compteur
• D’une file d’attente de processus (de blocs de
contexte)
• De 3 opérations atomiques (non interruptibles)
101 ©2024 CentraleSupélec - Dominique Marcadet
Sémaphore (2)
• init( val )
• initialise le compteur à val et la file à vide
• wait()
• décrémente le compteur ; si celui-ci est strictement
négatif, bloque le processus et le met dans la file
d’attente
• signal()
• incrémente le compteur ; si celui-ci est négatif ou nul,
libère un processus de la file d’attente
102 ©2024 CentraleSupélec - Dominique Marcadet
Linux : sémaphore anonyme
• Les sémaphores anonymes sont accessibles uniquement
entre processus père- ls et les threads
• Création
int sem_init(
sem_t * sem,
// pshared == 0 ? threads : processus
int pshared,
unsigned int value
);
• Destruction
int sem_destroy( sem_t * sem );
103 ©2024 CentraleSupélec - Dominique Marcadet
fi
Linux : sémaphore nommé
• Les sémaphores nommés sont implémentés par un chier
et sont accessibles par tout les processus/threads
• Création
sem_t * sem_open(
char * name,
int oflag,
mode_t mode,
int val
);
• Destruction
int sem_destroy( sem_t * sem );
104 ©2024 CentraleSupélec - Dominique Marcadet
fi
Linux : opérations sur sémaphores
• Ces deux types de sémaphores o rent les services
suivants :
• Demande d'un jeton
int sem_wait( sem_t * sem );
• Libération d'un jeton
int sem_post( sem_t * sem );
105 ©2024 CentraleSupélec - Dominique Marcadet
ff