💻 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.
💻 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:
| Layer | Description |
|---|---|
| Applications | User programs |
| Libraries | Standard functions (libc) |
| System calls | Kernel-user interface |
| Kernel | Core of the operating system |
| Hardware | Processor, 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:
| Aspect | System call | Library function |
|---|---|---|
| Execution | Kernel mode | User mode |
| Cost | High (mode switch) | Low |
| Examples | open(), 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:
| State | Description |
|---|---|
| New | Process being created |
| Ready | Waiting for CPU allocation |
| Running | Using the CPU |
| Blocked | Waiting for a resource (I/O) |
| Terminated | Execution 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:
| Aspect | Process | Thread |
|---|---|---|
| Memory | Isolated space | Shared with other threads |
| Creation | Expensive (fork) | Lightweight |
| Communication | IPC required | Shared variables |
| Context | Full PCB | Stack 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:
- Read counter from memory
- Increment
- 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:
- Mutual exclusion: non-shareable resources
- Hold and wait: process holds resources while waiting for others
- No preemption: cannot force resource release
- 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:
- Interrupt to kernel
- Load page from disk
- Update page table
- 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:
- Convert array to linked list
- Display list elements
- Search for negative accounts
- Filter list (keep only negative accounts)
- 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
| Course | Link |
|---|---|
| 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