💻 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

Organisation


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:

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:

Mode noyau (kernel):

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:

É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:

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:

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:

Algorithmes d’ordonnancement:

FCFS (First-Come, First-Served):

SJF (Shortest Job First):

Round Robin:

Priorités:

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:

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:

  1. Lire compteur depuis mémoire
  2. Incrémenter
  3. É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:

Opérations:

#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:

Lecteurs-Rédacteurs:

Philosophes dîneurs:

5. Interblocages (Deadlocks)

Conditions nécessaires pour un deadlock:

Les 4 conditions doivent être simultanément vraies:

  1. Exclusion mutuelle: ressources non partageables
  2. Détention et attente: processus garde ressources en attendant d’autres
  3. Pas de préemption: impossible de forcer libération ressource
  4. 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.

Évitement: Algorithme du banquier:

Détection et récupération:

Ignorance (Ostrich algorithm):

6. Gestion de la mémoire

Mémoire virtuelle:

Abstraction donnant l’illusion de mémoire illimitée à chaque processus.

Avantages:

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:

Chaque processus a sa table des pages.

Avantages:

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:

  1. Interruption vers noyau
  2. Charger page depuis disque
  3. Mettre à jour table des pages
  4. 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):

Optimal (MIN):

LRU (Least Recently Used):

Algorithme de l’horloge (seconde chance):

É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:

7. Systèmes de fichiers

Notion de fichier:

Abstraction pour stocker données de façon persistante.

Attributs:

Organisation hiérarchique:

Structure d’arborescence avec répertoires et fichiers.

Chemins:

Méthodes d’allocation:

Allocation contiguë:

Allocation chaînée:

Allocation indexée:

Inode (Unix):

Structure de données contenant métadonnées du fichier:

Séparation nom (dans répertoire) et contenu (dans inode).

Gestion de l’espace libre:

Bitmap:

Liste chaînée:

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:

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:

  1. Convertir tableau en liste chaînée
  2. Afficher éléments d’une liste
  3. Rechercher comptes négatifs
  4. Filtrer liste (ne garder que comptes négatifs)
  5. 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:

Concepts théoriques:

Résolution de problèmes:

Applications pratiques

Les systèmes d’exploitation sont au cœur de toute l’informatique:

Développement logiciel:

Systèmes embarqués:

Cloud et virtualisation:

Performances:

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:

Parallélisme massif:

Sécurité renforcée:

Systèmes distribués:

Optimisation énergétique:

Défis et problèmes complexes

Concurrence et parallélisme:

Programmer correctement avec threads est difficile:

Solutions:

Sécurité:

Les OS sont cibles d’attaques:

Réponses:

Mon opinion

Ce cours est fondamental pour comprendre ce qui se passe “sous le capot” des ordinateurs.

Points forts:

Importance professionnelle:

Ces connaissances sont essentielles pour:

Défis rencontrés:

La programmation concurrente est conceptuellement difficile:

Complémentarité:

Ce cours se combine parfaitement avec:

Applications concrètes:

Les notions apprises s’appliquent quotidiennement:

Vision pour l’avenir:

L’évolution vers:

…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.