Révision Express — Systèmes d'Exploitation II

00:00:00
CHAPITRE 01 // SYNCHRONISATION DES PROCESSUS

Process Synchronization

Gestion des accès concurrents aux ressources partagées — Section critique, Sémaphores, Moniteurs, Problèmes classiques

Concepts Fondamentaux
🔴 Race Condition

Résultat dépend de l'ordre d'exécution des opérations. Le résultat est non déterministe.

🔒 Ressource Critique

Ressource dont les accès concurrents peuvent mener à un état incohérent (ex: variable partagée).

⚡ Section Critique (SC)

Portion de code manipulant une ressource critique. Un seul processus à la fois doit l'exécuter.

🤝 Exclusion Mutuelle

Garantit qu'un seul processus est dans sa SC à un instant donné — mécanisme d'atomicité.

⚠️
Exemple bancaire : P1 lit compte=1000, P2 lit compte=1000. P1 ajoute +1000 → 2000. P2 retire -500 → 500. Le résultat final (500) est faux ! Le correct serait 1500.
Structure d'un Processus avec SC
// STRUCTURE GÉNÉRALE
/* Structure typique d'un processus Pi */
do {
    entry_section() ;    // Demande d'accès à la SC
    /* ═══ SECTION CRITIQUE ═══ */
    // Modifier variables communes, écrire fichier...
    exit_section() ;     // Libération de la SC
    /* Remainder section */
} while (true) ;
💡
Une solution au CS problem doit satisfaire 3 conditions : Mutual Exclusion (un seul dans SC) + Progress (si SC libre, un processus qui attend peut y entrer) + Bounded Waiting (aucun processus n'attend indéfiniment).
Solutions au Problème de Section Critique
SolutionTypeLimite
Peterson / DekkerLogicielle2 processus seulement, pas garanti sur toutes architectures
Masquage interruptionsMatérielleDangereux en multiprocesseur, perturbe l'horloge
Test & Set / SwapMatérielleDifficile en multiprocesseur, complexe à utiliser
Verrou (Mutex)OSAttente active → gaspille CPU
SémaphoreOS ✓Solution complète, sans attente active
MoniteurOS ✓✓Niveau supérieur, géré par compilateur (Java, C#)
⚠️
Défaut de l'attente active (busy-wait / spin-lock) : Le processus qui attend consomme du temps CPU inutilement. Le verrou est lui-même une ressource critique → n'assure pas l'exclusion seul.
Sémaphores (Dijkstra, 1965)
// DÉFINITION

Un sémaphore S est constitué de : un compteur entier + une file d'attente F de processus bloqués.

WAIT / P / down(S)

wait(S):
  S.value = S.value - 1
  if S.value < 0:
    // ajouter process à S.L
    block()  // mise en sommeil

→ Demande d'autorisation. Si S ≤ 0, le processus est bloqué.

SIGNAL / V / up(S)

signal(S):
  S.value = S.value + 1
  if S.value <= 0:
    // retirer process P de S.L
    wakeup(P) // réveil

→ Libération. Si des processus attendent, un est réveillé.

Sémaphore Binaire (Mutex)

Valeur ∈ {0,1}. Init à 1 → exclusion mutuelle. Init à 0 → bloquant. Contrôle accès à une ressource unique.

Sémaphore de Comptage

Valeur peut être > 1. Init = nombre de ressources disponibles. Utile pour plusieurs instances identiques.

// EXCLUSION MUTUELLE AVEC SÉMAPHORE
/* n processus partagent un sémaphore mutex initialisé à 1 */
Semaphore mutex = 1;

repeat
    wait(mutex);          // Entrée SC
    /* section critique */
    signal(mutex);        // Sortie SC
    /* remainder section */
until false;
Sémaphores POSIX — API C
// semaphore.h
// Déclaration
sem_t mutex;

// Initialisation : pshared=0 (thread local), value=valeur init
sem_init(&mutex, 0, 1);

// Attente (décrémente) — BLOQUANT
sem_wait(&mutex);

// Attente (décrémente) — NON BLOQUANT (retourne -1 si occupé)
sem_trywait(&mutex);

// Libération (incrémente + réveille un processus si file non vide)
sem_post(&mutex);

// Lire valeur courante
sem_getvalue(&mutex, &sval);

// Destruction
sem_destroy(&mutex);
📌
File d'attente : FIFO → sémaphore fort (pas de famine). LIFO → sémaphore faible (famine possible).
🎮 Simulateur de Sémaphore Interactif
▸ SIMULATION — Sémaphore Binaire (Mutex)
S (valeur)
1
LIBRE (unlocked)
P1
prêt
P2
prêt
P3
prêt
FILE D'ATTENTE
→ Simulateur prêt. Cliquez sur les boutons.
Problèmes Classiques de Synchronisation
1. PRODUCTEUR / CONSOMMATEUR (Bounded Buffer)

Un producteur génère des données placées dans un tampon de N cases, qu'un consommateur retire. Gestion : Overflow Underflow

empty = N (cases vides) full = 0 (cases pleines) mutex = 1 (excl. mutuelle)
/* Producteur */                    /* Consommateur */
repeat                               repeat
  produire nextp                         wait(full)
  wait(empty)   // case disponible?    wait(mutex)
  wait(mutex)   // accès exclusif     retirer item
  ajouter nextp au buffer              signal(mutex)
  signal(mutex)                        signal(empty)  // libère case
  signal(full)  // case remplie       consommer item
until false;                         until false;
2. LECTEURS / RÉDACTEURS

Accès concurrent à une base de données. Plusieurs lecteurs simultanément OK. Un rédacteur → accès exclusif total.

  • mutex = 1 : protège la variable NbL (compteur de lecteurs)
  • redact = 1 : exclusion mutuelle pour les rédacteurs
  • NbL = 0 : nombre de lecteurs actifs
  • 1er lecteur → wait(redact) ; Dernier lecteur → signal(redact)
⚠️
Problème 1 (priorité lecteurs) : famine possible pour les rédacteurs.
Problème 2 (priorité rédacteurs) : famine possible pour les lecteurs.
3. DÎNER DES PHILOSOPHES (Dijkstra, 1965)

5 philosophes autour d'une table, 5 fourchettes. Pour manger : besoin des 2 fourchettes adjacentes.

💀
Si chaque philosophe prend sa fourchette gauche → Deadlock ! Aucun ne peut prendre la droite.

Solutions : limiter à 4 philosophes à table, prendre 2 fourchettes atomiquement, asymétrie (philosophe pair/impair). C'est aussi un modèle du problème d'inter-blocage.

Moniteurs (Hoare, 1973)
🤔
Problème des sémaphores : variables globales, pas de lien linguistique, accès de n'importe où, risque d'inversion wait/signal → blocage total.
Moniteur = abstraction haut niveau

Encapsule variables partagées + sections critiques d'un même problème. Le compilateur gère l'exclusion mutuelle automatiquement.

Propriété fondamentale

Un seul thread actif dans le moniteur à tout moment. Le compilateur insère le code de verrouillage (Java: synchronized).

// MONITEUR EN JAVA
class CompteBancaire {
    private int solde = 0;

    // synchronized → une seule méthode active à la fois
    public synchronized void deposer(int montant) {
        solde = solde + montant;
    }

    public synchronized void retirer(int montant) {
        if (solde >= montant)
            solde = solde - montant;
    }
}
📌
Les variables de condition permettent aux threads de s'endormir à l'intérieur du moniteur (wait()) en libérant le verrou, et d'être réveillés par un autre thread (notify()).

🧪 QCM — Synchronisation

Score: 0 / 0
0/0
CHAPITRE 02 // INTER-PROCESS COMMUNICATION

IPC — Communication Interprocessus

Mécanismes permettant aux processus de communiquer et synchroniser leurs actions : mémoire partagée, tubes, signaux, sockets, messages.

Processus Indépendants vs Coopératifs

Independent Process

  • N'affecte pas les autres processus
  • Ne partage aucune donnée
  • Pas besoin de synchronisation

Cooperating Process

  • Affecte ou est affecté par les autres
  • Partage des données (fichier, mémoire)
  • Nécessite IPC pour communiquer
🔗 Pourquoi coopérer ?

Partage d'info (fichier commun) · Speedup (parallélisme) · Modularité · Commodité (multitâche)

Les 2 Modèles Fondamentaux d'IPC

📦 Message Passing

  • send(message) / receive(message)
  • Un seul processus a les données à la fois
  • Communication directe : send(P, msg) / receive(Q, msg)
  • Communication indirecte : via boîte aux lettres (port)
  • Utile pour petites quantités de données
  • Pas de problème de conflit (pas de vars partagées)

💾 Shared Memory

  • Région de mémoire commune entre processus
  • Accès en lecture/écriture non exclusifs par défaut
  • Plus rapide (pas d'appels système pendant l'accès)
  • Nécessite synchronisation ! (sémaphores)
  • Exemple POSIX : shm_open()
💡
Ces deux modèles ne sont pas exclusifs et peuvent être utilisés simultanément dans un même OS ou processus.
Tubes Anonymes — pipe()
// CARACTÉRISTIQUES
  • Unidirectionnel : un côté lecture, un côté écriture
  • Réservé aux processus ayant un ancêtre commun (après fork())
  • Pas de nom dans le système de fichiers → anonyme
  • Stocké dans le kernel, capacité fixe (PIPE_BUF)
  • Opérations : read(), write(), close() — comme un fichier
// Création du tube
int pipe_fds[2];
pipe(pipe_fds);      // retourne 0 si OK, -1 si erreur

int fr = pipe_fds[0];   // descripteur en LECTURE
int fw = pipe_fds[1];   // descripteur en ÉCRITURE

// Dans le shell Linux : ls | less → crée un tube entre ls et less
SituationComportement read()
Tube non videLit min(taille, TAILLE_BUF) caractères
Tube vide, pas d'écrivainsRetourne 0 (EOF)
Tube vide, des écrivains (bloquant)Processus mis en sommeil jusqu'à données
Tube vide, des écrivains (non bloquant)Retour immédiat: -1, errno = EAGAIN
Écriture, pas de lecteursSIGPIPE envoyé → terminaison processus
▸ SIMULATEUR — Pipe Anonyme
Producteurfw = pipe_fds[1]
Consommateurfr = pipe_fds[0]
Tube vide (0/5)
Tubes Nommés — FIFO (mkfifo())
Différence clé vs pipe()

Le tube nommé a une entrée dans le système de fichiers → accessible par des processus sans ancêtre commun (via chemin).

Visible avec ls -l

Identifiable par le type de fichier spécial p dans ls -l. Premier octet des droits = 'p'.

// Création du FIFO
mkfifo("/tmp/fifo1", 0666);

// Processus 1 (écrivain)
int d_ecriture = open("fifo1", O_WRONLY);

// Processus 2 (lecteur) — peuvent ne pas se connaître!
int d_lecture  = open("fifo1", O_RDONLY);

// O_NONBLOCK → ouverture et I/O non bloquants
int fd = open("somefifo", O_RDONLY | O_NONBLOCK);
Signaux (Interruptions Logicielles)

Mécanisme de communication minimale entre processus. Utilisés par l'OS pour notifier les processus d'événements importants.

SignalNumTypeDescription
SIGKILL9AEFTermine le processus (non interceptable)
SIGSTOP19DEFStoppe le processus (non interceptable)
SIGCONT18Continue l'exécution du processus
SIGCHLD17BUn processus fils est terminé
SIGPIPE13AÉcriture dans un tube sans lecteur
SIGUSR1/210/12ASignaux utilisateur (définis par l'app)
SIGALRM14AAlarme (alarm(n) → SIGALRM dans n sec)
// API SIGNAUX
// Envoyer signal signum au processus pid
kill(pid, SIGTERM);

// Envoyer signal à soi-même
raise(signum);  // ≡ kill(getpid(), signum)

// Programmer envoi de SIGALRM dans n secondes
alarm(5);     // alarm(0) annule

// Réactions possibles d'un processus à un signal:
// 1. Ignorer le signal
// 2. Action par défaut (terminaison, stop, core...)
// 3. Handler personnalisé via signal() ou sigaction()
Sockets
Communication bidirectionnelle

Peut communiquer sur la même machine ou sur des machines distantes. Base de Telnet, FTP, HTTP, WWW.

Paramètres de création

Style de communication (STREAM/DGRAM), espace de nommage (AF_UNIX, AF_INET), protocole.

// socketpair() — COMMUNICATION LOCALE
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

// SOCK_STREAM : flux continu (comme les tubes)
write(sv[0], "hello", 5); read(sv[1], buf, 5);

// SOCK_DGRAM : mode paquet (messages indivisibles)
write(in, "ABC", 3);  // file contient "ABC"
write(in, "123", 3);  // file contient "ABC","123"
read(out, bf, 10);   // retourne 3, bf="ABC" seulement!
Système de Messages — Buffering
Capacité de la fileComportement
Capacité zéroAucun message en attente → synchronisation rendez-vous. Émetteur bloqué jusqu'à récepteur prêt.
Capacité limitée (n)File peut contenir n messages. Si pleine, émetteur se bloque.
Capacité illimitéeL'émetteur ne se bloque jamais. Non réaliste.
⚠️
Messages perdus : Détectés par temporisateur (timeout). Solutions : OS retransmet, émetteur retransmet, OS notifie l'émetteur.
Messages embrouillés : Détectés par CRC (somme de contrôle). L'OS retransmet le message original.

🧪 QCM — IPC

Score: 0 / 0
0/0
CHAPITRE 03 // DEADLOCKS (INTERBLOCAGES)

Deadlocks

Un ensemble de processus est en deadlock si chaque processus attend un événement que seul un autre processus de l'ensemble peut provoquer → blocage total et définitif.

Définition & Cycle de Vie d'une Ressource
💀
Deadlock : Tous les processus attendent → aucun événement n'est produit → tous bloqués indéfiniment. Aucun processus n'est réveillé.
// SÉQUENCE D'UTILISATION D'UNE RESSOURCE
REQUÊTE
Demande
UTILISATION
Exploitation
LIBÉRATION
Release

Si la requête ne peut être satisfaite immédiatement → processus bloque jusqu'à disponibilité

🔑 4 Conditions Nécessaires (Coffman, Elphick et Shoshani)
📌
Les 4 conditions doivent être simultanément satisfaites pour qu'un deadlock se produise. Briser l'une d'elles → prévention.
1. Mutual Exclusion

Une ressource est soit allouée à un seul processus, soit disponible. Impossible d'être partagée.

2. Hold & Wait (Détention & Attente)

Un processus qui détient des ressources peut en demander d'autres, sans libérer celles qu'il possède.

3. No Preemption (Pas de préemption)

Les ressources allouées ne peuvent être retirées de force. Elles sont libérées uniquement par le processus lui-même.

4. Circular Wait (Attente Circulaire)

P0 attend une ressource de P1, P1 attend de P2, ..., Pn attend de P0 → cycle fermé.

🍴
Exemple des Philosophes : Chaque philosophe a pris sa fourchette gauche (Hold), attend la droite (Wait) tenue par son voisin (Circular Wait) → Deadlock !
Graphe d'Allocation de Ressources (Holt, 1972)
Nœuds processus

Représentés par des cercles (P1, P2...)

Nœuds ressources

Représentés par des carrés (R1, R2...). Les points à l'intérieur = instances.

Arc Ressource → Processus

La ressource est allouée au processus.

Arc Processus → Ressource

Le processus attend la ressource (bloqué).

▸ GRAPHE INTERACTIF — Détection de Deadlock
💀 DEADLOCK DÉTECTÉ — cycle circulaire
🛡️ 4 Stratégies Face aux Deadlocks
#StratégiePrincipeUsage pratique
1 Ignorer (Ostrich) Prétendre qu'il n'y a pas de problème UNIX/Windows : deadlocks rares, coût trop élevé pour les traiter
2 Détecter & Récupérer Laisser arriver, détecter, puis corriger Systèmes où les deadlocks sont tolérables temporairement
3 Éviter (Avoidance) Allocation prudente (état sûr/non sûr) Algorithme du Banquier de Dijkstra
4 Prévenir (Prevention) Empêcher une des 4 conditions Conception du système dès le départ
Algorithme de Détection
// MATRICES UTILISÉES
  • C[n×m] : Matrice allocations courantes. C[i,j] = nb ressources de type j tenues par Pi
  • R[n×m] : Matrice demandes. R[i,j] = nb ressources de type j manquant à Pi
  • A[m] : Vecteur disponibilités. A[j] = ressources de type j non attribuées
  • E[m] : Vecteur existences. E[j] = total ressources de type j dans le système
// ÉTAPES DE L'ALGORITHME
  1. Chercher un processus Pi non marqué dont la rangée R[i] ≤ A (peut être satisfait)
  2. Si trouvé → ajouter C[i] à A (Pi va terminer et libérer ses ressources), marquer Pi, retour étape 1
  3. Si pas de tel processus → les processus non marqués sont en deadlock. Fin.
▸ SIMULATEUR — Algorithme du Banquier (Banker's Algorithm)
Matrice C (Allocation)
Matrice R (Demande)
Vecteur A (Dispo)
Vecteur E (Total)
→ Cliquez "Exécuter" pour analyser l'état
Récupération après Deadlock
1. Préemption

Retirer provisoirement une ressource à son propriétaire et l'attribuer à un autre. Dépend de la nature de la ressource.

2. Rollback

Restaurer un état antérieur (point de reprise = checkpoint). Sauvegarde mémoire + état ressources.

3. Kill de processus

Supprimer un ou plusieurs processus. Choisir un processus pouvant être redémarré sans conséquence (plus simple).

Évitement — Algorithme du Banquier (Dijkstra, 1965)
🏦
Principe : Avant chaque allocation, le système vérifie si l'état résultant est sûr. Si non sûr → requête mise en attente.
État Sûr

Il existe un ordre d'exécution (séquence sûre) dans lequel tous les processus peuvent terminer. L'état actuel ne mène pas à un deadlock.

État Non Sûr

Aucune séquence d'exécution garantissant la terminaison de tous. Ne signifie pas nécessairement un deadlock, mais à risque.

⚠️
Limite pratique : L'algorithme du Banquier est peu utilisé en pratique car il faut connaître à l'avance les besoins maximaux en ressources de chaque processus — généralement impossible.
Prévention — Briser une des 4 Conditions
Condition à briserMéthodeProblème
Mutual Exclusion Sérialiser les requêtes (ex: spooling imprimante → démon d'impression) Pas toujours possible (certaines ressources sont intrinsèquement exclusives)
Hold & Wait Demander toutes les ressources à l'avance avant de démarrer Très difficile en pratique (allocation dynamique) + gaspillage de ressources
No Preemption Permettre à l'OS de retirer des ressources de force Dégrade le système. Possible seulement pour certaines ressources (CPU, mémoire)
Circular Wait Numéroter les ressources. Demande uniquement par ordre croissant, ou une ressource à la fois. Difficile de trouver une bonne numérotation pour toutes les ressources

🧪 QCM — Deadlocks

Score: 0 / 0
0/0
BONUS // RÉVISION RAPIDE

Flashcards — Cliquez pour retourner

Testez votre mémorisation des définitions clés. Chaque carte montre un terme, cliquez pour voir la définition.

🔒 Synchronisation
📨 IPC
💀 Deadlocks
📊 Résumé Comparatif Final
ConceptDéfinition courteMot-clé
Race ConditionRésultat non déterministe selon l'ordre d'exécutionDANGER
Section CritiqueCode manipulant une ressource critiqueATOMIQUE
Sémaphore binaireMutex : valeur 0 ou 1, exclusion mutuelleINIT=1
Sémaphore comptageGère N ressources identiquesINIT=N
wait(S)S-- ; si S<0, bloquerP / down
signal(S)S++ ; si S≤0, réveiller un processusV / up
MoniteurEncapsulation SC + mutex géré par compilateursynchronized
pipe()Tube anonyme, unidirectionnel, processus liésFIFO
mkfifo()Tube nommé, visible dans FS, processus quelconquesPATH
SIGKILLSignal 9, terminaison forcée, non interceptablekill -9
DeadlockTous les processus attendent indéfinimentBLOCAGE
Hold & WaitCondition 2 du deadlock : tenir + demander d'autresGREEDY
Banker's AlgorithmÉvitement deadlock via état sûr/non sûrSAFE
Ostrich AlgorithmIgnorer les deadlocks (solution UNIX/Windows)🦦