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

CoucheDescription
ApplicationsProgrammes utilisateur
BibliothèquesFonctions standards (libc)
Appels systèmeInterface noyau-utilisateur
NoyauCœur du système d'exploitation
MatérielProcesseur, 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:

AspectAppel systèmeFonction bibliothèque
ExécutionMode noyauMode utilisateur
CoûtÉlevé (changement de mode)Faible
Exemplesopen(), 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:

ÉtatDescription
NouveauProcessus en création
PrêtEn attente d'allocation CPU
En exécutionUtilise 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:

AspectProcessusThread
MémoireEspace isoléPartagé avec autres threads
CréationCoûteuse (fork)Légère
CommunicationIPC nécessaireVariables partagées
ContextePCB completPile 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:

  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:

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

  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.

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

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

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

  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:

  • 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

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

💻 Operating Systems - S5

Year: 2022-2023 (Semester 5)
Credits: 3 ECTS
Type: System Computing


PART A: GENERAL OVERVIEW

Course objectives

This course explores the fundamental principles of operating systems, focusing on process management, memory, files, and concurrent programming. Emphasis is placed on practical implementation under Unix/Linux with system programming in C.

Target skills

  • Understand the architecture and role of an operating system
  • Master process and thread management
  • Program inter-process communication (IPC)
  • Understand virtual memory management
  • Implement synchronization mechanisms
  • Solve deadlock problems
  • Use Unix/Linux system calls
  • Develop multithreaded applications
  • Analyze and optimize system performance

Organization

  • Hours: Lectures, tutorials, and C programming labs
  • Assessment: Written exam + graded labs
  • Semester: 5 (2022-2023)
  • Prerequisites: C programming, computer architecture

PART B: EXPERIENCE, CONTEXT AND FUNCTION

Course content

The course covers the internal mechanisms of modern operating systems.

1. Introduction to operating systems

Role of the operating system:

Interface between hardware and user applications.

Main functions:

  • Resource management (CPU, memory, peripherals)
  • Hardware abstraction
  • Protection and security
  • Application services

System architecture:

LayerDescription
ApplicationsUser programs
LibrariesStandard functions (libc)
System callsKernel-user interface
KernelCore of the operating system
HardwareProcessor, memory, peripherals

Execution modes:

User mode:

  • Normal applications
  • Restricted hardware access
  • No privileged instructions

Kernel mode:

  • Operating system code
  • Full hardware access
  • Privileged instructions allowed

Transition via system calls (syscalls).

System calls vs library functions:

AspectSystem callLibrary function
ExecutionKernel modeUser mode
CostHigh (mode switch)Low
Examplesopen(), fork(), read()printf(), malloc(), strlen()

2. Process management

Process concept:

A process is a program in execution.

Contents:

  • Program code
  • Data (variables)
  • Execution stack
  • Heap for dynamic allocations
  • PCB (Process Control Block): management information

Process states:

StateDescription
NewProcess being created
ReadyWaiting for CPU allocation
RunningUsing the CPU
BlockedWaiting for a resource (I/O)
TerminatedExecution finished

Transitions:

  • Ready → Running: scheduler selects the process
  • Running → Ready: time quantum expired
  • Running → Blocked: waiting for I/O
  • Blocked → Ready: I/O completed

Process creation under Unix:

#include <unistd.h>
#include <sys/types.h>

pid_t pid = fork();

if (pid == 0) {
    // Child process code
    printf("I am the child, PID=%d\n", getpid());
} else if (pid > 0) {
    // Parent process code
    printf("I am the parent, PID=%d, child=%d\n", getpid(), pid);
    wait(NULL);  // Wait for child to finish
} else {
    // Error
    perror("fork");
}

fork(): creates a child process identical to the parent.

Returns:

  • 0 in the child
  • Child's PID in the parent
  • -1 on error

exec(): replaces the process code with a new program.

execl("/bin/ls", "ls", "-l", NULL);
// If exec succeeds, the following code is never executed
perror("exec");

wait(): the parent waits for the child to finish.

int status;
pid_t child_pid = wait(&status);

Process scheduling:

Scheduling criteria:

  • CPU utilization: maximize
  • Throughput: number of processes completed per unit of time
  • Turnaround time: total execution time
  • Waiting time: time spent in the ready queue
  • Response time: time before first response

Scheduling algorithms:

FCFS (First-Come, First-Served):

  • Simple FIFO queue
  • No preemption
  • Problem: convoy effect (short processes stuck behind long ones)

SJF (Shortest Job First):

  • Executes the shortest process first
  • Optimal for average waiting time
  • Problem: predicting required time

Round Robin:

  • Fixed time quantum (10-100 ms)
  • Circular preemption
  • Fair but frequent context switches

Priorities:

  • Each process has a priority
  • Risk of starvation for low priorities
  • Solution: aging

3. Threads and multithreading

Thread vs Process:

AspectProcessThread
MemoryIsolated spaceShared with other threads
CreationExpensive (fork)Lightweight
CommunicationIPC requiredShared variables
ContextFull PCBStack and registers only

Advantages of multithreading:

  • Responsiveness (a blocked task does not stop everything)
  • Easier resource sharing
  • Efficiency (less overhead than fork)
  • Parallelism exploitation (multi-core)

Programming with pthreads:

#include <pthread.h>

void* thread_function(void* arg) {
    int id = *(int*)arg;
    printf("Thread %d running\n", id);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    int id1 = 1, id2 = 2;

    // Create threads
    pthread_create(&thread1, NULL, thread_function, &id1);
    pthread_create(&thread2, NULL, thread_function, &id2);

    // Wait for threads to finish
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

Compilation: gcc -pthread program.c

4. Process synchronization

Critical section problem:

When multiple threads access shared data, race conditions can occur.

Classic example: shared counter.

int counter = 0;  // Shared variable

// Thread 1 and Thread 2 execute:
counter = counter + 1;

// Unpredictable result without synchronization!

Breakdown:

  1. Read counter from memory
  2. Increment
  3. Write back to memory

If threads interleave, the result is incorrect.

Mutual exclusion:

Guarantee that only one thread accesses the critical section at a time.

Semaphores:

Synchronization tool with a counter.

Types:

  • Binary semaphore: 0 or 1 (mutex)
  • Counting semaphore: any value

Operations:

  • wait() or P(): decrements, blocks if negative
  • signal() or V(): increments, wakes blocked thread
#include <semaphore.h>

sem_t semaphore;
sem_init(&semaphore, 0, 1);  // Initial value 1

// Critical section
sem_wait(&semaphore);
// ... protected code ...
sem_post(&semaphore);

sem_destroy(&semaphore);

Mutex (Mutual Exclusion):

Lock to protect critical sections.

#include <pthread.h>

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

// Protection
pthread_mutex_lock(&mutex);
// Critical section
counter++;
pthread_mutex_unlock(&mutex);

pthread_mutex_destroy(&mutex);

Condition variables:

Allow waiting until a condition becomes true.

pthread_cond_t condition;
pthread_mutex_t mutex;

// Waiting thread
pthread_mutex_lock(&mutex);
while (!condition_true) {
    pthread_cond_wait(&condition, &mutex);
}
pthread_mutex_unlock(&mutex);

// Signaling thread
pthread_mutex_lock(&mutex);
condition_true = 1;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);

Classic problems:

Producer-Consumer:

  • Shared buffer of limited size
  • Producer adds elements
  • Consumer removes elements
  • Synchronization with semaphores

Readers-Writers:

  • Multiple simultaneous readers OK
  • Exclusive writer
  • Priority to readers or writers

Dining Philosophers:

  • 5 philosophers, 5 forks
  • Need 2 forks to eat
  • Risk of deadlock

5. Deadlocks

Necessary conditions for a deadlock:

All 4 conditions must be simultaneously true:

  1. Mutual exclusion: non-shareable resources
  2. Hold and wait: process holds resources while waiting for others
  3. No preemption: cannot force resource release
  4. Circular wait: dependency cycle

Simple example:

Thread A holds resource 1, wants resource 2.
Thread B holds resource 2, wants resource 1.
→ Deadlock!

Management strategies:

Prevention:
Break one of the 4 necessary conditions.

  • Eliminate circular wait: resource acquisition ordering
  • Allocate all resources at once

Avoidance:
Banker's algorithm:

  • Checks if allocation leaves system in safe state
  • Grants only if safe state guaranteed
  • Requires advance knowledge of needs

Detection and recovery:

  • Detect cycles in allocation graph
  • Recovery: kill processes or reclaim resources

Ignorance (Ostrich algorithm):

  • Do nothing, assume it does not happen
  • Approach of many OSes (Linux, Windows)
  • Detection cost > cost of rare deadlocks

6. Memory management

Virtual memory:

Abstraction giving the illusion of unlimited memory to each process.

Advantages:

  • Process isolation
  • Memory protection
  • Efficient use of RAM
  • Allows programs > physical RAM size

Addressing:

Logical address (virtual): process's view.

Physical address: actual address in RAM.

MMU (Memory Management Unit): translates logical → physical.

Paging:

Dividing memory into fixed-size pages (typically 4 KB).

Components:

  • Page: block in virtual memory
  • Frame: block in physical memory
  • Page table: page → frame translation

Each process has its own page table.

Advantages:

  • No external fragmentation
  • Simple allocation

TLB (Translation Lookaside Buffer):
Hardware cache to speed up address translation.

Page fault:

When a requested page is not in RAM:

  1. Interrupt to kernel
  2. Load page from disk
  3. Update page table
  4. Resume execution

Very costly (slow disk access).

Page replacement algorithms:

When RAM is full, which page to evict?

FIFO (First-In, First-Out):

  • Evict the oldest page
  • Simple but suboptimal

Optimal (MIN):

  • Evict the page that will be used furthest in the future
  • Optimal but unrealizable (requires future knowledge)
  • Serves as a theoretical benchmark

LRU (Least Recently Used):

  • Evict the page not used for the longest time
  • Good performance/complexity trade-off
  • Difficult to implement exactly

Clock algorithm (second chance):

  • Approximation of LRU
  • Reference bit per page
  • Circular scan

Thrashing:

A situation where the system spends its time handling page faults instead of executing useful code.

Cause: too many active processes, not enough RAM.

Solutions:

  • Add more RAM
  • Reduce multiprogramming
  • Working set algorithms

7. File systems

File concept:

Abstraction for persistently storing data.

Attributes:

  • Name
  • Type
  • Size
  • Owner
  • Permissions
  • Dates (creation, modification, access)

Hierarchical organization:

Tree structure with directories and files.

Paths:

  • Absolute: from the root (/)
  • Relative: from the current directory

Allocation methods:

Contiguous allocation:

  • File occupies consecutive blocks
  • Fast access
  • External fragmentation

Linked allocation:

  • Each block points to the next
  • No external fragmentation
  • Slow sequential access

Indexed allocation:

  • Index block contains pointers to data blocks
  • Direct access
  • Unix: inodes with indirect blocks

Inode (Unix):

Data structure containing file metadata:

  • Permissions
  • Owner
  • Size
  • Timestamps
  • Pointers to data blocks

Separation of name (in directory) and content (in inode).

Free space management:

Bitmap:

  • 1 bit per block (0=free, 1=occupied)
  • Fast for finding free blocks

Linked list:

  • Free blocks linked together
  • Memory-efficient

8. Inter-process communication (IPC)

Pipes:

Unidirectional communication channel.

int fd[2];
pipe(fd);  // fd[0]=read, fd[1]=write

if (fork() == 0) {
    // Child writes
    close(fd[0]);
    write(fd[1], "message", 7);
    close(fd[1]);
} else {
    // Parent reads
    close(fd[1]);
    char buffer[100];
    read(fd[0], buffer, 100);
    close(fd[0]);
}

Named pipes (FIFO):

Persistent in the file system, accessible by name.

mkfifo("/tmp/my_pipe", 0666);

Shared memory:

Memory region accessible by multiple processes.

#include <sys/shm.h>

int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
char* shared = (char*)shmat(shmid, NULL, 0);
// Use shared...
shmdt(shared);

Message queues:

Mailboxes for exchanging structured messages.

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

Network or local communication (Unix domain sockets).


PART C: TECHNICAL ASPECTS

Lab Work

Lab 1: System programming

The lab is divided into 3 progressive exercises:

Exercise 1: Data structure manipulation

Objective: work with collections, arrays, and structures.

Exercises:

  • Displaying product information
  • Array traversal
  • Element search (maximum price)
  • Filtering by criteria

Code example:

// Display all products
for(i=0; i<nb_products; i++) {
    display_name(i);
    printf(" Price: %f Size: %c Stock: %d\n",
           price(i), size(i), stock(i));
}

// Search for the most expensive product
float most_expensive = 0;
for(i=0; i<nb_products; i++) {
    if(price(i) > most_expensive) {
        most_expensive = price(i);
    }
}

Exercise 2: Linked lists

Objective: dynamic list manipulation.

Structure:

typedef struct {
    int client_num;
    float balance;
} client_account;

Exercises:

  1. Convert array to linked list
  2. Display list elements
  3. Search for negative accounts
  4. Filter list (keep only negative accounts)
  5. Reverse a list (mirror)

Implementation:

// Array -> list conversion
List arr_to_lst(client_account array[], int len) {
    List new_list = cons(array[len-1], nil);
    for(int i=len-2; i>=0; i--) {
        new_list = cons(array[i], new_list);
    }
    return new_list;
}

// Recursive display
void display_lst(List list) {
    while(!is_empty(list)) {
        printf("%d %f ", head(list).client_num, head(list).balance);
        list = tail(list);
    }
}

// Search for negative accounts
void negative_account(List list) {
    while(!is_empty(list)) {
        if(head(list).balance < 0) {
            printf("Client %d: negative balance %f\n",
                   head(list).client_num, head(list).balance);
        }
        list = tail(list);
    }
}

Exercise 3: Binary trees

Objective: tree manipulation.

Extension of exercise 2 with tree structures for hierarchical data organization.

Development tools

Compilation:

gcc -o program program.c
gcc -pthread -o multi multithread.c  # With threads
gcc -g -o debug program.c            # With debug symbols

Provided Makefile:

CC=gcc
CFLAGS=-Wall -g

all: exercise1 exercise2 exercise3

exercise1: exercise1.c
	$(CC) $(CFLAGS) -o exercise1 exercise1.c

clean:
	rm -f exercise1 exercise2 exercise3

Debugging with GDB:

gdb ./program
break main
run
next
print variable
continue
quit

Monitoring tools:

ps aux                # Running processes
top                   # Real-time monitoring
strace ./program      # Trace system calls
valgrind ./program    # Detect memory leaks

System programming in C

Essential system calls:

Processes:

fork()      // Create process
exec()      // Replace program
wait()      // Wait for child
exit()      // Terminate process
getpid()    // Get PID
getppid()   // Get parent PID

Files:

open()      // Open file
read()      // Read
write()     // Write
close()     // Close
lseek()     // Move cursor

Signals:

signal()    // Install handler
kill()      // Send signal
alarm()     // Schedule alarm

Error handling:

if (fork() < 0) {
    perror("fork");  // Display error message
    exit(1);
}

// Or with errno
if (open("file", O_RDONLY) < 0) {
    fprintf(stderr, "Error: %s\n", strerror(errno));
}

PART D: ANALYSIS AND REFLECTION

Skills acquired

System programming:

  • Mastery of Unix/Linux system calls
  • Multithreaded programming with pthreads
  • Inter-process communication
  • Signal and error handling
  • Debugging concurrent applications

Theoretical concepts:

  • Internal workings of an OS
  • Resource management (CPU, memory, I/O)
  • Synchronization problems and solutions
  • Scheduling and paging algorithms
  • File system architecture

Problem solving:

  • Deadlock detection and resolution
  • Performance optimization
  • Race condition debugging
  • Design of robust concurrent applications

Practical applications

Operating systems are at the core of all computing:

Software development:

  • Multithreaded applications (servers, games)
  • Real-time programs
  • Parallel data processing
  • Shared resource management

Embedded systems:

  • Embedded Linux (Raspberry Pi, BeagleBone)
  • RTOS (Real-Time Operating Systems)
  • Device drivers
  • Memory and energy optimization

Cloud and virtualization:

  • Hypervisors (KVM, Xen)
  • Containers (Docker, Kubernetes)
  • Resource orchestration
  • Isolation and security

Performance:

  • Application optimization
  • Profiling and analysis
  • System parameter tuning
  • Multi-core scalability

Links with other courses

CourseLink
Hardware Architecture (S5)MMU, interrupts, DMA
C Language (S5)System programming
Unix System (S5)Commands, shell, scripts
Networking (S5)Sockets, communication
Real-Time (S8)Real-time scheduling
Cloud Computing (S9)Virtualization, containers

Evolution of operating systems

Current trends:

Ubiquitous virtualization:

  • Virtual machines
  • Lightweight containers (Docker)
  • Unikernels for cloud

Massive parallelism:

  • Multi-core processors (64+ cores)
  • GPUs for general-purpose computing
  • Heterogeneous architectures

Enhanced security:

  • Container isolation
  • Secure boot
  • Advanced memory protection (ASLR, DEP)
  • Application sandboxing

Distributed systems:

  • Cluster operating systems
  • Coordination of thousands of machines
  • Resilience and fault tolerance

Energy optimization:

  • Fine-grained power management
  • Advanced sleep states
  • Energy-aware scheduling

Challenges and complex problems

Concurrency and parallelism:

Programming correctly with threads is difficult:

  • Subtle race conditions
  • Deadlocks difficult to reproduce
  • Performance issues (contention)

Solutions:

  • Simplified programming models (actors, CSP)
  • Languages with built-in safety (Rust)
  • Detection tools (ThreadSanitizer)

Security:

OSes are targets for attacks:

  • Kernel vulnerability exploits
  • Privilege escalation
  • Information leaks (Spectre, Meltdown)

Responses:

  • Principle of least privilege
  • Enhanced isolation
  • Regular security updates

My opinion

This course is fundamental to understanding what happens "under the hood" of computers.

Strengths:

  • Complete view of how an OS works
  • Hands-on system programming in C
  • Concepts applicable to many domains
  • Understanding of concurrency problems

Professional importance:

This knowledge is essential for:

  • High-performance software development
  • Embedded systems and IoT
  • Complex problem debugging
  • Distributed system architecture
  • DevOps and system administration

Challenges encountered:

Concurrent programming is conceptually difficult:

  • Non-deterministic bugs
  • Difficult to reproduce and debug
  • Requires rigor and exhaustive testing

Complementarity:

This course combines perfectly with:

  • Unix System (practical usage)
  • Hardware Architecture (low-level operation)
  • Networking (distributed communication)
  • Real-Time (strict time constraints)

Practical applications:

The concepts learned apply daily:

  • Web browsers (multithreading)
  • Servers (connection management)
  • Mobile applications (resource management)
  • Video games (parallelism)

Vision for the future:

The evolution toward:

  • Massively parallel processors
  • Distributed systems
  • Edge computing
  • Embedded AI

...makes this knowledge even more crucial.


Personal assessment: This course provided a deep understanding of the fundamental mechanisms of operating systems. Hands-on C programming with fork, threads, and synchronization allowed bridging theory and reality. These skills are directly applicable to embedded systems development, real-time applications, and performance optimization. Mastery of concurrency and associated problems (deadlocks, race conditions) is particularly valuable in a world where parallelism is becoming ubiquitous.

Rédigé par Cédric ChanfreauWritten by Cédric Chanfreau