💻 Systèmes d’Exploitation - S5
Année: 2022-2023 (Semestre 5)
Crédits: 3 ECTS
Type: Informatique Système
PART A: PRÉSENTATION GÉNÉRALE
Objectifs du cours
Ce cours explore les principes fondamentaux des systèmes d’exploitation en se concentrant sur la gestion des processus, la mémoire, les fichiers et la programmation concurrente. L’accent est mis sur l’implémentation pratique sous Unix/Linux avec la programmation système en C.
Compétences visées
- Comprendre l’architecture et le rôle d’un système d’exploitation
- Maîtriser la gestion des processus et threads
- Programmer la communication inter-processus (IPC)
- Comprendre la gestion de la mémoire virtuelle
- Implémenter des mécanismes de synchronisation
- Résoudre les problèmes d’interblocage (deadlock)
- Utiliser les appels système Unix/Linux
- Développer des applications multithreads
- Analyser et optimiser les performances système
Organisation
- Volume horaire: Cours magistraux, TD et TPs de programmation C
- Évaluation: Examen écrit + TPs notés
- Semestre: 5 (2022-2023)
- Prérequis: Programmation C, architecture des ordinateurs
PART B: EXPÉRIENCE, CONTEXTE ET FONCTION
Contenu pédagogique
Le cours couvre les mécanismes internes des systèmes d’exploitation modernes.
1. Introduction aux systèmes d’exploitation
Rôle du système d’exploitation:
Interface entre le matériel et les applications utilisateur.
Fonctions principales:
- Gestion des ressources (CPU, mémoire, périphériques)
- Abstraction du matériel
- Protection et sécurité
- Services aux applications
Architecture du système:
| Couche | Description |
|---|---|
| Applications | Programmes utilisateur |
| Bibliothèques | Fonctions standards (libc) |
| Appels système | Interface noyau-utilisateur |
| Noyau | Cœur du système d’exploitation |
| Matériel | Processeur, mémoire, périphériques |
Modes d’exécution:
Mode utilisateur:
- Applications normales
- Accès restreint au matériel
- Pas d’instructions privilégiées
Mode noyau (kernel):
- Code du système d’exploitation
- Accès complet au matériel
- Instructions privilégiées autorisées
Transition via appels système (syscalls).
Appels système vs fonctions bibliothèque:
| Aspect | Appel système | Fonction bibliothèque |
|---|---|---|
| Exécution | Mode noyau | Mode utilisateur |
| Coût | Élevé (changement de mode) | Faible |
| Exemples | open(), fork(), read() | printf(), malloc(), strlen() |
2. Gestion des processus
Notion de processus:
Un processus est un programme en cours d’exécution.
Contenu:
- Code du programme
- Données (variables)
- Pile d’exécution (stack)
- Tas (heap) pour allocations dynamiques
- PCB (Process Control Block): informations de gestion
États d’un processus:
| État | Description |
|---|---|
| Nouveau | Processus en création |
| Prêt | En attente d’allocation CPU |
| En exécution | Utilise le CPU |
| Bloqué | Attend une ressource (I/O) |
| Terminé | Fin d’exécution |
Transitions:
- Prêt → Exécution: ordonnanceur sélectionne le processus
- Exécution → Prêt: quantum de temps écoulé
- Exécution → Bloqué: attente I/O
- Bloqué → Prêt: I/O terminée
Création de processus sous Unix:
#include <unistd.h>
#include <sys/types.h>
pid_t pid = fork();
if (pid == 0) {
// Code du processus fils
printf("Je suis le fils, PID=%d\n", getpid());
} else if (pid > 0) {
// Code du processus père
printf("Je suis le père, PID=%d, fils=%d\n", getpid(), pid);
wait(NULL); // Attendre la fin du fils
} else {
// Erreur
perror("fork");
}
fork(): crée un processus fils identique au père.
Retourne:
- 0 dans le fils
- PID du fils dans le père
- -1 en cas d’erreur
exec(): remplace le code du processus par un nouveau programme.
execl("/bin/ls", "ls", "-l", NULL);
// Si exec réussit, le code suivant n'est jamais exécuté
perror("exec");
wait(): le père attend la fin du fils.
int status;
pid_t child_pid = wait(&status);
Ordonnancement des processus:
Critères d’ordonnancement:
- Utilisation CPU: maximiser
- Débit (throughput): nombre de processus terminés par unité de temps
- Temps de rotation: temps total d’exécution
- Temps d’attente: temps passé en file d’attente
- Temps de réponse: temps avant première réponse
Algorithmes d’ordonnancement:
FCFS (First-Come, First-Served):
- File FIFO simple
- Pas de préemption
- Problème: effet convoi (processus courts bloqués derrière longs)
SJF (Shortest Job First):
- Exécute d’abord le processus le plus court
- Optimal pour temps d’attente moyen
- Problème: prédiction du temps nécessaire
Round Robin:
- Quantum de temps fixe (10-100 ms)
- Préemption circulaire
- Équitable mais changements de contexte fréquents
Priorités:
- Chaque processus a une priorité
- Risque de famine (starvation) pour basses priorités
- Solution: vieillissement (aging)
3. Threads et multithreading
Thread vs Processus:
| Aspect | Processus | Thread |
|---|---|---|
| Mémoire | Espace isolé | Partagé avec autres threads |
| Création | Coûteuse (fork) | Légère |
| Communication | IPC nécessaire | Variables partagées |
| Contexte | PCB complet | Pile et registres uniquement |
Avantages du multithreading:
- Réactivité (une tâche bloquée n’arrête pas tout)
- Partage de ressources facilité
- Efficacité (moins de coût que fork)
- Exploitation du parallélisme (multi-cœurs)
Programmation avec pthreads:
#include <pthread.h>
void* fonction_thread(void* arg) {
int id = *(int*)arg;
printf("Thread %d en cours\n", id);
return NULL;
}
int main() {
pthread_t thread1, thread2;
int id1 = 1, id2 = 2;
// Créer threads
pthread_create(&thread1, NULL, fonction_thread, &id1);
pthread_create(&thread2, NULL, fonction_thread, &id2);
// Attendre fin des threads
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
Compilation: gcc -pthread programme.c
4. Synchronisation des processus
Problème de la section critique:
Quand plusieurs threads accèdent à des données partagées, des conditions de course (race conditions) peuvent survenir.
Exemple classique: compteur partagé.
int compteur = 0; // Variable partagée
// Thread 1 et Thread 2 exécutent:
compteur = compteur + 1;
// Résultat imprévisible sans synchronisation!
Décomposition:
- Lire compteur depuis mémoire
- Incrémenter
- Écrire en mémoire
Si les threads s’entrelacent, le résultat est incorrect.
Exclusion mutuelle:
Garantir qu’un seul thread accède à la section critique à la fois.
Sémaphores:
Outil de synchronisation avec compteur.
Types:
- Sémaphore binaire: 0 ou 1 (mutex)
- Sémaphore compteur: valeur quelconque
Opérations:
- wait() ou P(): décrémente, bloque si négatif
- signal() ou V(): incrémente, réveille thread bloqué
#include <semaphore.h>
sem_t semaphore;
sem_init(&semaphore, 0, 1); // Valeur initiale 1
// Section critique
sem_wait(&semaphore);
// ... code protégé ...
sem_post(&semaphore);
sem_destroy(&semaphore);
Mutex (Mutual Exclusion):
Verrou pour protéger sections critiques.
#include <pthread.h>
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// Protection
pthread_mutex_lock(&mutex);
// Section critique
compteur++;
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
Variables conditionnelles:
Permettent d’attendre qu’une condition soit vraie.
pthread_cond_t condition;
pthread_mutex_t mutex;
// Thread en attente
pthread_mutex_lock(&mutex);
while (!condition_vraie) {
pthread_cond_wait(&condition, &mutex);
}
pthread_mutex_unlock(&mutex);
// Thread qui signale
pthread_mutex_lock(&mutex);
condition_vraie = 1;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);
Problèmes classiques:
Producteur-Consommateur:
- Buffer partagé de taille limitée
- Producteur ajoute éléments
- Consommateur retire éléments
- Synchronisation avec sémaphores
Lecteurs-Rédacteurs:
- Plusieurs lecteurs simultanés OK
- Rédacteur exclusif
- Priorité aux lecteurs ou rédacteurs
Philosophes dîneurs:
- 5 philosophes, 5 fourchettes
- Besoin de 2 fourchettes pour manger
- Risque d’interblocage
5. Interblocages (Deadlocks)
Conditions nécessaires pour un deadlock:
Les 4 conditions doivent être simultanément vraies:
- Exclusion mutuelle: ressources non partageables
- Détention et attente: processus garde ressources en attendant d’autres
- Pas de préemption: impossible de forcer libération ressource
- Attente circulaire: cycle de dépendances
Exemple simple:
Thread A possède ressource 1, veut ressource 2. Thread B possède ressource 2, veut ressource 1. → Interblocage!
Stratégies de gestion:
Prévention: Casser une des 4 conditions nécessaires.
- Éliminer attente circulaire: ordre d’acquisition des ressources
- Allouer toutes ressources d’un coup
Évitement: Algorithme du banquier:
- Vérifie si allocation laisse système en état sûr
- N’accorde que si état sûr garanti
- Nécessite connaissance à l’avance des besoins
Détection et récupération:
- Détecter cycles dans graphe d’allocation
- Récupération: tuer processus ou reprendre ressources
Ignorance (Ostrich algorithm):
- Ne rien faire, supposer que ça n’arrive pas
- Solution de nombreux OS (Linux, Windows)
- Coût de détection > coût des rares deadlocks
6. Gestion de la mémoire
Mémoire virtuelle:
Abstraction donnant l’illusion de mémoire illimitée à chaque processus.
Avantages:
- Isolation des processus
- Protection mémoire
- Utilisation efficace de la RAM
- Permet programmes > taille RAM physique
Adressage:
Adresse logique (virtuelle): vue du processus.
Adresse physique: adresse réelle en RAM.
MMU (Memory Management Unit): traduit logique → physique.
Pagination:
Découpage de la mémoire en pages de taille fixe (4 KB typiquement).
Composants:
- Page: bloc en mémoire virtuelle
- Cadre (frame): bloc en mémoire physique
- Table des pages: traduction page → cadre
Chaque processus a sa table des pages.
Avantages:
- Pas de fragmentation externe
- Allocation simple
TLB (Translation Lookaside Buffer): Cache matériel pour accélérer la traduction d’adresses.
Défaut de page (page fault):
Quand page demandée n’est pas en RAM:
- Interruption vers noyau
- Charger page depuis disque
- Mettre à jour table des pages
- Reprendre exécution
Très coûteux (accès disque lent).
Algorithmes de remplacement de pages:
Quand RAM pleine, quelle page évincer?
FIFO (First-In, First-Out):
- Évincer page la plus ancienne
- Simple mais sous-optimal
Optimal (MIN):
- Évincer page qui sera utilisée le plus tard
- Optimal mais irréalisable (nécessite futur)
- Sert de référence théorique
LRU (Least Recently Used):
- Évincer page non utilisée depuis le plus longtemps
- Bon compromis performance/complexité
- Difficile à implémenter exactement
Algorithme de l’horloge (seconde chance):
- Approximation de LRU
- Bit de référence par page
- Parcours circulaire
Écroulement (thrashing):
Situation où le système passe son temps à gérer des défauts de page au lieu d’exécuter du code utile.
Cause: trop de processus actifs, pas assez de RAM.
Solutions:
- Augmenter RAM
- Réduire multiprogrammation
- Algorithmes d’ensemble de travail
7. Systèmes de fichiers
Notion de fichier:
Abstraction pour stocker données de façon persistante.
Attributs:
- Nom
- Type
- Taille
- Propriétaire
- Permissions
- Dates (création, modification, accès)
Organisation hiérarchique:
Structure d’arborescence avec répertoires et fichiers.
Chemins:
- Absolu: depuis la racine (/)
- Relatif: depuis répertoire courant
Méthodes d’allocation:
Allocation contiguë:
- Fichier occupe blocs consécutifs
- Accès rapide
- Fragmentation externe
Allocation chaînée:
- Chaque bloc pointe vers le suivant
- Pas de fragmentation externe
- Accès séquentiel lent
Allocation indexée:
- Bloc d’index contient pointeurs vers blocs de données
- Accès direct
- Unix: inodes avec blocs indirects
Inode (Unix):
Structure de données contenant métadonnées du fichier:
- Permissions
- Propriétaire
- Taille
- Horodatages
- Pointeurs vers blocs de données
Séparation nom (dans répertoire) et contenu (dans inode).
Gestion de l’espace libre:
Bitmap:
- 1 bit par bloc (0=libre, 1=occupé)
- Rapide pour trouver blocs libres
Liste chaînée:
- Blocs libres chaînés entre eux
- Économe en mémoire
8. Communication inter-processus (IPC)
Tubes (pipes):
Canal de communication unidirectionnel.
int fd[2];
pipe(fd); // fd[0]=lecture, fd[1]=écriture
if (fork() == 0) {
// Fils écrit
close(fd[0]);
write(fd[1], "message", 7);
close(fd[1]);
} else {
// Père lit
close(fd[1]);
char buffer[100];
read(fd[0], buffer, 100);
close(fd[0]);
}
Tubes nommés (named pipes, FIFO):
Persistent dans système de fichiers, accessible par nom.
mkfifo("/tmp/mon_pipe", 0666);
Mémoire partagée:
Zone mémoire accessible par plusieurs processus.
#include <sys/shm.h>
int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
char* shared = (char*)shmat(shmid, NULL, 0);
// Utiliser shared...
shmdt(shared);
Files de messages:
Boîtes aux lettres pour échanger messages structurés.
#include <sys/msg.h>
int msgid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
msgsnd(msgid, &message, sizeof(message), 0);
msgrcv(msgid, &message, sizeof(message), 0, 0);
Sockets:
Communication réseau ou locale (Unix domain sockets).
PART C: ASPECTS TECHNIQUES
Travaux Pratiques
TP1: Programmation système
Le TP se décompose en 3 sujets progressifs:
Sujet 1: Manipulation de structures de données
Objectif: travailler avec collections, tableaux et structures.
Exercices:
- Affichage d’informations produit
- Parcours de tableaux
- Recherche d’éléments (prix maximum)
- Filtrage selon critères
Exemple de code:
// Afficher tous les produits
for(i=0; i<nb_produits; i++) {
affiche_nom(i);
printf(" Prix: %f Taille: %c Stock: %d\n",
prix(i), taille(i), stock(i));
}
// Rechercher le produit le plus cher
float plus_cher = 0;
for(i=0; i<nb_produits; i++) {
if(prix(i) > plus_cher) {
plus_cher = prix(i);
}
}
Sujet 2: Listes chaînées
Objectif: manipulation de listes dynamiques.
Structure:
typedef struct {
int num_client;
float solde;
} compte_client;
Exercices:
- Convertir tableau en liste chaînée
- Afficher éléments d’une liste
- Rechercher comptes négatifs
- Filtrer liste (ne garder que comptes négatifs)
- Inverser une liste (miroir)
Implémentation:
// Conversion tableau -> liste
Liste tab_lst(compte_client tableau[], int len) {
Liste new_list = cons(tableau[len-1], nil);
for(int i=len-2; i>=0; i--) {
new_list = cons(tableau[i], new_list);
}
return new_list;
}
// Affichage récursif
void aff_lst(Liste liste) {
while(!est_vide(liste)) {
printf("%d %f ", head(liste).num_client, head(liste).solde);
liste = tail(liste);
}
}
// Recherche comptes négatifs
void negative_compte(Liste liste) {
while(!est_vide(liste)) {
if(head(liste).solde < 0) {
printf("Client %d: solde négatif %f\n",
head(liste).num_client, head(liste).solde);
}
liste = tail(liste);
}
}
Sujet 3: Arbres binaires
Objectif: manipulation d’arbres.
Extension du sujet 2 avec structures arborescentes pour organisation hiérarchique des données.
Outils de développement
Compilation:
gcc -o programme programme.c
gcc -pthread -o multi multithread.c # Avec threads
gcc -g -o debug programme.c # Avec symboles debug
Makefile fourni:
CC=gcc
CFLAGS=-Wall -g
all: sujet1 sujet2 sujet3
sujet1: sujet1.c
$(CC) $(CFLAGS) -o sujet1 sujet1.c
clean:
rm -f sujet1 sujet2 sujet3
Débogage avec GDB:
gdb ./programme
break main
run
next
print variable
continue
quit
Outils de monitoring:
ps aux # Processus en cours
top # Monitoring temps réel
strace ./programme # Tracer appels système
valgrind ./programme # Détecter fuites mémoire
Programmation système en C
Appels système essentiels:
Processus:
fork() // Créer processus
exec() // Remplacer programme
wait() // Attendre fils
exit() // Terminer processus
getpid() // Obtenir PID
getppid() // Obtenir PID parent
Fichiers:
open() // Ouvrir fichier
read() // Lire
write() // Écrire
close() // Fermer
lseek() // Déplacer curseur
Signaux:
signal() // Installer gestionnaire
kill() // Envoyer signal
alarm() // Programmer alarme
Gestion des erreurs:
if (fork() < 0) {
perror("fork"); // Affiche message d'erreur
exit(1);
}
// Ou avec errno
if (open("fichier", O_RDONLY) < 0) {
fprintf(stderr, "Erreur: %s\n", strerror(errno));
}
PART D: ANALYSE ET RÉFLEXION
Compétences acquises
Programmation système:
- Maîtrise des appels système Unix/Linux
- Programmation multithread avec pthreads
- Communication inter-processus
- Gestion des signaux et erreurs
- Débogage d’applications concurrentes
Concepts théoriques:
- Fonctionnement interne d’un OS
- Gestion des ressources (CPU, mémoire, I/O)
- Problèmes de synchronisation et solutions
- Algorithmes d’ordonnancement et pagination
- Architecture des systèmes de fichiers
Résolution de problèmes:
- Détection et résolution de deadlocks
- Optimisation de performances
- Débogage de race conditions
- Conception d’applications concurrentes robustes
Applications pratiques
Les systèmes d’exploitation sont au cœur de toute l’informatique:
Développement logiciel:
- Applications multithreads (serveurs, jeux)
- Programmes temps réel
- Traitement parallèle de données
- Gestion de ressources partagées
Systèmes embarqués:
- Linux embarqué (Raspberry Pi, BeagleBone)
- RTOS (Real-Time Operating Systems)
- Drivers de périphériques
- Optimisation mémoire et énergie
Cloud et virtualisation:
- Hyperviseurs (KVM, Xen)
- Conteneurs (Docker, Kubernetes)
- Orchestration de ressources
- Isolation et sécurité
Performances:
- Optimisation d’applications
- Profilage et analyse
- Réglage de paramètres système
- Scalabilité multi-cœurs
Liens avec autres cours
| Cours | Lien |
|---|---|
| Architecture Matérielle (S5) | MMU, interruptions, DMA |
| Langage C (S5) | Programmation système |
| Système Unix (S5) | Commandes, shell, scripts |
| Réseau (S5) | Sockets, communication |
| Temps Réel (S8) | Ordonnancement temps réel |
| Cloud Computing (S9) | Virtualisation, conteneurs |
Évolution des systèmes d’exploitation
Tendances actuelles:
Virtualisation omniprésente:
- Machines virtuelles
- Conteneurs légers (Docker)
- Unikernels pour cloud
Parallélisme massif:
- Processeurs multi-cœurs (64+ cœurs)
- GPU pour calcul général
- Architectures hétérogènes
Sécurité renforcée:
- Isolation par conteneurs
- Secure boot
- Protection mémoire avancée (ASLR, DEP)
- Sandboxing d’applications
Systèmes distribués:
- Systèmes d’exploitation pour clusters
- Coordination de milliers de machines
- Résilience et tolérance aux pannes
Optimisation énergétique:
- Gestion fine de la consommation
- États de veille avancés
- Ordonnancement conscient de l’énergie
Défis et problèmes complexes
Concurrence et parallélisme:
Programmer correctement avec threads est difficile:
- Race conditions subtiles
- Deadlocks difficiles à reproduire
- Problèmes de performance (contention)
Solutions:
- Modèles de programmation simplifiés (actors, CSP)
- Langages avec sécurité intégrée (Rust)
- Outils de détection (ThreadSanitizer)
Sécurité:
Les OS sont cibles d’attaques:
- Exploits de vulnérabilités noyau
- Élévation de privilèges
- Fuites d’information (Spectre, Meltdown)
Réponses:
- Principes de moindre privilège
- Isolation renforcée
- Mises à jour de sécurité régulières
Mon opinion
Ce cours est fondamental pour comprendre ce qui se passe “sous le capot” des ordinateurs.
Points forts:
- Vision complète du fonctionnement d’un OS
- Programmation système pratique en C
- Concepts applicables à de nombreux domaines
- Compréhension des problèmes de concurrence
Importance professionnelle:
Ces connaissances sont essentielles pour:
- Développement de logiciels performants
- Systèmes embarqués et IoT
- Débogage de problèmes complexes
- Architecture de systèmes distribués
- DevOps et administration système
Défis rencontrés:
La programmation concurrente est conceptuellement difficile:
- Bugs non déterministes
- Difficiles à reproduire et déboguer
- Nécessite rigueur et tests exhaustifs
Complémentarité:
Ce cours se combine parfaitement avec:
- Système Unix (utilisation pratique)
- Architecture matérielle (fonctionnement bas niveau)
- Réseau (communication distribuée)
- Temps réel (contraintes temporelles strictes)
Applications concrètes:
Les notions apprises s’appliquent quotidiennement:
- Navigateurs web (multithreads)
- Serveurs (gestion de connexions)
- Applications mobiles (gestion ressources)
- Jeux vidéo (parallélisme)
Vision pour l’avenir:
L’évolution vers:
- Processeurs massivement parallèles
- Systèmes distribués
- Edge computing
- IA embarquée
…rend ces connaissances encore plus cruciales.
Bilan personnel: Ce cours a fourni une compréhension profonde des mécanismes fondamentaux des systèmes d’exploitation. La programmation pratique en C avec fork, threads et synchronisation a permis de confronter théorie et réalité. Ces compétences sont directement applicables en développement de systèmes embarqués, applications temps réel, et optimisation de performances. La maîtrise de la concurrence et des problèmes associés (deadlocks, race conditions) est particulièrement précieuse dans un monde où le parallélisme devient omniprésent.