Introduction · appels système
Le système d'exploitation comme machine étendue. Mode noyau, mode utilisateur, et la grande famille POSIX : open, read, write, lseek, close, dup, dup2, fork, wait, waitpid, exec*, system.
📄 Ressources du cours
🎥 Vidéos recommandées
-
🇫🇷
fonction fork programmation c linux en darija
programmer0MAROC · 12:29 · 43k vues — link for download exemple.txt http://www.mediafire.com/?iu6giktc7dgb2uo
-
🇫🇷
GNU/Linux #22 - processus
FormationVidéo · 48:17 · 34k vues — Cette vidéo aborde les processus du système, de quoi sont ils composés et comment le processeur (via son ordonnanceur) partage les ressources entre chacun d'...
-
🇬🇧
Linux fork() Introduction
Brian Fraser · 29:40 · 248k vues — Demo the use of the fork() function in Linux/Unix to spawn a new process. Cover the basics of what it does, how to do work in parent and child processes, and...
0. Plan du cours
• Décrire le rôle et le fonctionnement d'un système d'exploitation monoprocesseur et multi-tâches.
• Apprendre à manipuler certains concepts de base.
Ces concepts seront essentiellement illustrés au travers du système UNIX / Linux qui est très homogène, très riche, très souple.
1. Systèmes d'exploitation · définitions
Architecture en couches d'un ordinateur
Un ordinateur est constitué :
- du matériel — dispositifs physiques + langage machine ;
- d'un système d'exploitation ;
- de programmes — programmes système + programmes d'applications.
Les quatre couches d'un système : du matériel jusqu'aux utilisateurs, en passant par le SE et les applications.
Composition du SE
Partie la plus critique d'un OS. Il permet aux éléments matériel et logiciel de communiquer entre eux, de fonctionner ensemble et de former un tout. C'est le premier logiciel chargé en mémoire.
Partie permettant à l'utilisateur de tirer profit de l'OS, de gérer les périphériques, les configurer. Bref, ils fournissent une interface d'accès au système.
Le matériel (hardware)
Les dispositifs physiques constituent la couche la plus basse :
- le processeur,
- la mémoire principale,
- des disques,
- des imprimantes,
- des interfaces de connexion réseau,
- …
Les programmes (software, applications)
Ils sont écrits par les utilisateurs ou par les éditeurs de logiciels. But : résoudre des problèmes spécifiques (traitement des problèmes commerciaux, calculs scientifiques, etc.).
2. La double fonction du SE
Un SE répond à deux besoins qui ne sont pas forcément liés :
- le SE en tant que machine étendue (ou « machine virtuelle »),
- le SE en tant que gestionnaire de ressources.
Le SE en tant que machine étendue
Un programmeur va utiliser le SE par l'intermédiaire des « appels système ».
Un utilisateur peut lui aussi — dans une certaine mesure — manipuler un SE, sans pour autant avoir à créer un programme : via les commandes shell.
Le SE en tant que gestionnaire de ressources
Les différents composants d'un ordinateur doivent coopérer et partager des ressources. Le travail du SE consiste à :
- ordonnancer,
- contrôler l'allocation des ressources : processeurs, mémoires, périphériques d'E/S, entre les différents programmes qui y font appel.
Pour chacune des ressources, le SE doit :
- connaître à tout moment l'utilisateur de la ressource,
- en accorder l'usage de manière équitable,
- éviter les conflits d'accès entre les différents programmes ou utilisateurs.
① Le partage des ressources.
② La protection de l'accès aux ressources.
3. Fonctions de base d'un SE
Un SE, à quoi ça sert ? Six grandes fonctions :
| # | Fonction | Rôle |
|---|---|---|
| 1 | Gestion des processus | Correspond à l'exécution des programmes. |
| 2 | Gestion de la mémoire | Gérer les transferts entre la mémoire principale et secondaire. |
| 3 | Système de fichiers | Offre à l'utilisateur une vision homogène et structurée des données et des ressources (disques, périphériques). |
| 4 | Entrées-sorties | Mécanismes qu'utilisent les processus pour communiquer avec l'extérieur. |
| 5 | Réseaux d'ordinateurs | Protocoles de communication, d'interconnexion et d'application. |
| 6 | Systèmes répartis | Protocoles d'appels de procédures à distance (RPC) ou objets distribués. |
4. Les appels système · principe
① Basculer en mode noyau
② Invoquer le SE
③ Revenir en mode utilisateur
④ Retourner le contrôle au programme utilisateur.
Exemple : lecture ou écriture sur le disque dur.
Un exemple détaillé avec le compilateur C
Soit l'appel cpt = read(df, tampon, nboctets). Voici les 10 étapes du déroulement :
// Côté utilisateur (mode user)
cpt = read(df, tampon, nboctets);
- Empiler
nboctets - Empiler
&tampon - Empiler
df - Appeler
read() - Placement du code du
readdans un registre - Déroutement vers le noyau — basculer en mode noyau
- Le code noyau examine le numéro d'appel système, utilise une table de pointeurs indexée sur les numéros d'appels système
- Exécution de l'appel système
- Rendre le contrôle à la fonction
readde bibliothèque - Rendre le contrôle au programme utilisateur
Anatomie d'un appel système : on quitte le mode utilisateur, on transite par la table indexée du noyau, on exécute, et on revient.
5. Les appels système POSIX · panorama
À quoi ça sert ? Trois grandes familles :
| Famille | Appels système |
|---|---|
| Gestion des fichiers | open, close, read, write, dup, dup2, stat, … |
| Gestion des processus | fork, pipe, exec, execve, execlp, exit, … |
| Communications inter-machines | socket(), bind(), listen(), connect(), accept(), send(), recv(), write(), read(), sendto(), recvfrom(), close(), gethostbyname(), gethostbyaddr(), … |
6. Gestion des fichiers
Ouvrir un fichier · open
Ouvre un fichier, qui restera ouvert jusqu'à la fin du processus ou jusqu'à close :
int open(const char *pathname, int typeOpen);
int open(const char *pathname, int typeOpen, mode_t droitsFic);
pathname(chemin) = chaîne de caractères correspondant au chemin d'accès du fichier à ouvrir.typeOpen= mode d'ouverture.
| Mode | Signification |
|---|---|
O_RDONLY | lecture seule |
O_WRONLY | écriture seule |
O_RDWR | lecture et écriture |
Ces modes peuvent se combiner avec un OU binaire avec un attribut de création :
| Attribut | Effet |
|---|---|
O_CREAT | créer si nécessaire le fichier. |
O_APPEND | ouvrir en mode ajout. |
O_TRUNC | si le fichier existe, est un fichier régulier, et est ouvert en écriture (O_RDWR ou O_WRONLY), il sera tronqué à une longueur nulle (vider avant de pouvoir y écrire de nouvelles données). Si le fichier n'existe pas, il ne sera pas créé sauf si O_CREAT est spécifié aussi. |
O_EXCL | s'assurer que cet appel crée le fichier : si cet attribut est spécifié en conjonction avec O_CREAT et si le fichier existe déjà, open() échouera. |
droitsFicDroits d'accès à utiliser si le fichier doit être créé grâce à
O_CREAT.• Mêmes valeurs octales que la commande
chmod (0777 = tous les droits pour tous).• Se combinent avec le
umask.• Si on oublie,
open donnera des résultats bizarres.
open
open retourne -1 si le fichier n'a pas pu être ouvert, et un entier positif ou nul sinon. Ce nombre correspond à un index dans le tableau des fichiers ouverts, où chaque case contient le descripteur d'un fichier ouvert.
Les étapes d'un open
- Trouver l'inode du fichier (dans la table des inodes) — charger l'inode en mémoire si elle n'y est pas.
- Vérifier les droits d'accès — la vérification n'est faite qu'à l'ouverture ; si on enlève les droits sur un fichier ouvert, ça ne bloque pas le processus qui utilise le fichier !
- Allouer une entrée dans la table des fichiers ouverts.
- Positionner l'offset courant (0 ou fin de fichier en cas d'ouverture en ajout).
- Allouer une place dans la table des descripteurs de fichiers du processus.
- Renvoyer au processus appelant le descripteur obtenu.
Les 3 tables liées à open
Trois niveaux d'indirection : fd processus → entrée globale (offset, mode) → inode.
Fermer un fichier ouvert · close
Ferme le fichier ouvert et libère le descripteur :
int close(int fd);
fd était le dernier descripteur sur le fichier (à l'intérieur du processus)
• le compteur associé à l'inode en mémoire est décrémenté ; s'il vaut 0, l'inode est déchargée.• les ressources associées à l'ouverture du fichier sont libérées.
Lire depuis un fichier ouvert · read
int read(int fd, void *buf, size_t count);
• fin de fichier
• lecture sur un tube ou un terminal
• interruption par un signal
• Vérification de la validité du descripteur et du bon mode d'ouverture.
• Les droits d'accès de l'utilisateur ne sont PAS testés.
Écrire dans un fichier ouvert · write
ssize_t write(int fd, const void *buf, size_t count);
• plus de place sur le périphérique,
• quota disque du processus dépassé,
• interruption par un signal.
Se positionner dans un fichier ouvert · lseek
off_t lseek(int fd, off_t offset, int whence);
whence | Origine du déplacement |
|---|---|
SEEK_SET | début du fichier |
SEEK_CUR | position courante |
SEEK_END | fin du fichier |
lseek peut aller au-delà de la taille du fichier (sans la modifier) ; une écriture modifiera alors la taille réelle.
Dupliquer un descripteur · dup
int dup(int oldfd);
- Duplique un descripteur de fichier valide.
- La copie reçoit le plus petit numéro de descripteur disponible.
- Pénible quand on veut un numéro précis.
Dupliquer avec numéro choisi · dup2
int dup2(int oldfd, int newfd);
- Duplique un descripteur de fichier valide.
- La copie reçoit le numéro
newfd. - Si
newfdétait ouvert, il est fermé automatiquement. - Très utile pour les redirections.
dup, dup2 et lseek
lseek.→ Risque de concurrence.
→ Après une duplication, il faut fermer deux fois le descripteur pour fermer complètement.
Descripteurs standards
Par défaut, un processus dispose de :
| Nom | Rôle | fd |
|---|---|---|
stdin | entrée standard | 0 |
stdout | sortie standard | 1 |
stderr | sortie d'erreur | 2 |
7. Gestion des processus
Création d'un processus
- Lorsqu'on entre une commande, le shell lance un processus pour l'exécuter.
- Le shell attend la fin du processus, puis attend la commande suivante.
- Un programme qui s'exécute avec un jeu de données particulier est un processus.
Arborescence de processus
• Un processus a un numéro permettant son identification :
PID.• Le numéro du processus du père est le
PPID.• Un processus père peut avoir plusieurs processus fils.
• Sous UNIX, un premier processus
init (ou systemd) est créé avec un PID égal à 1. C'est l'ancêtre de tous les processus.
Duplication d'un processus · fork
pid_t fork(void);
L'appel système fork est le seul moyen de créer un processus à bas niveau :
- Crée un processus fils de ce processus, et le lance.
- L'exécution continue dans les deux processus, à l'instruction qui suit le
fork. - Les
PIDetPPIDsont les seules informations différentes entre les deux processus.
fork()
• Dans le processus père : retourne le PID du fils.• Dans le processus fils : retourne 0.
• Si le
fork échoue : retourne -1 (seul le père existe alors).
La manière la plus courante d'utiliser fork
L'appel système fork duplique / clone le processus appelant :
void main() {
pid_t n = fork();
if (n != 0) {
if (n < 0) {
// gestion des erreurs
} else {
// programme du papa
}
} else {
// programme du fils
}
}
Récupération du PID
pid_t getpid(void);
pid_t getppid(void);
pid_t getpgrp(void);
getpidretourne le PID du processus courant.getppidretourne le PID du processus père.getpgrpretourne le PID du groupe du processus courant.
Exemple — clonage, indépendance
Le clonage est une recopie complète et indépendante. Exemple de sortie :
je suis le fils, mon PID est 10271 ; mon pere est 10270
pour le pid 10271, i = 12
je suis le pere, mon PID est 10270
Pour le pid 10270, i = 7
Le branchement classique post-fork : n > 0 côté père, n == 0 côté fils, n < 0 en cas d'erreur.
Attendre la mort d'un fils · wait
pid_t wait(int *status);
La primitive wait permet à un processus d'attendre la fin d'un de ses fils :
- S'il n'y a pas de fils ou erreur, retourne
-1. - Sinon, retourne le PID du fils qui s'est terminé.
wait suspend l'exécution du processus courant jusqu'à ce que l'un de ses fils se termine (ou jusqu'à la réception d'un signal).• Si un processus fils est déjà mort, la fonction revient immédiatement.
• Toutes les ressources utilisées par le fils sont libérées.
Inspecter status
Si status n'est pas NULL, les informations sur la terminaison du fils y sont stockées.
| Macro | Signification |
|---|---|
WIFEXITED(status) | vrai si le fils s'est terminé normalement (exit) ; sinon 0. |
WEXITSTATUS(status) | code de retour du fils (si normal). |
WIFSIGNALED(status) | vrai si le fils a été tué par un signal. |
WTERMSIG(status) | numéro du signal qui a tué le fils. |
Terminer un processus · exit
void exit(int status);
statuspermet d'indiquer au processus père qu'une erreur s'est produite.status = 0si pas d'erreur.
Attendre un fils particulier · waitpid
pid_t waitpid(pid_t pid, int *status, int options);
La fonction waitpid suspend l'exécution du processus courant jusqu'à ce que le processus fils de numéro pid se termine (ou jusqu'à réception d'un signal).
Valeur de pid | Comportement |
|---|---|
< -1 | attendre la fin de n'importe quel processus fils appartenant au groupe d'ID |pid| (n'importe quel fils dont GID = |pid|). |
-1 | attendre n'importe quel fils — identique à wait. |
0 | attendre la fin de n'importe quel fils du même groupe que l'appelant. |
> 0 | attendre la fin du processus de numéro pid. |
Il existe différentes options ; la plus utile :
WNOHANG qui permet de ne pas bloquer si aucun fils n'est mort.
Temporisation · sleep
int sleep(int seconds);
Similaire à la commande shell sleep — le processus qui appelle sleep est bloqué pendant le nombre de secondes spécifiées.
wait()
wait bloque le processus jusqu'à la fin d'un fils, alors que sleep() bloque pour un temps spécifié.
8. Lancer un autre programme · exec* & system
La famille exec*
exec* qui permettent à un processus de remplacer son code exécutable par un autre programme spécifié par path ou file, sans changer de numéro de processus (PID).
exec* n'existe plus.En cas d'échec :
-1.
Les 6 prototypes
int execl (char *path, char *arg0, char *arg1, ..., NULL);
int execv (char *path, char *arg[]);
int execle(char *path, char *arg0, ..., NULL, char *envp[]);
int execve(char *path, char *arg[], char *envp[]);
int execlp(char *file, char *arg0, char *arg1, ..., NULL);
int execvp(char *file, char *arg[]);
execl — liste d'arguments
La fonction execl prend en paramètre une liste des arguments à passer au programme (liste terminée par NULL). Le premier paramètre est une chaîne qui doit contenir le chemin d'accès complet au fichier exécutable ou au script shell à exécuter.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
/* dernier élément NULL, OBLIGATOIRE */
execl("/usr/bin/emacs", "emacs", "fichier.c", "fichier.h", NULL);
perror("Problème : cette partie du code ne doit jamais être exécutée");
return 0;
}
execv — tableau d'arguments
La différence avec execl est que l'on n'a pas besoin de connaître la liste des arguments à l'avance (ni même leur nombre). Prototype :
int execv(const char *application, const char *argv[]);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char *argv[] = {"emacs", "fichier.c", "fichier.h", NULL};
/* dernier élément NULL, obligatoire */
execv("/usr/bin/emacs", argv);
puts("Problème : cette partie du code ne doit jamais être exécutée");
return 0;
}
Récapitulatif des 6 variantes
| Primitive | Format d'argument | Passage d'environnement | Recherche avec PATH |
|---|---|---|---|
execl | Liste | Automatique | Non |
execv | Tableau | Automatique | Non |
execle | Liste | Manuel | Non |
execve | Tableau | Manuel | Non |
execlp | Liste | Automatique | Oui |
execvp | Tableau | Automatique | Oui |
•
l : liste d'arguments•
v : arguments sous forme de vecteur•
p : recherche du fichier avec la variable d'environnement PATH•
e : transmission d'un environnement en dernier paramètre, en remplacement de l'environnement courant
Lancement d'une commande · system
int system(const char *commande);
- La fonction
systemde la bibliothèquestdlib.hpermet directement de lancer un programme dans un programme C sans utiliserforketexec. - Crée un nouveau processus
/bin/shqui exécute la commande (/bin/sh -c commande). - Le processus appelant cette fonction reste bloqué jusqu'à la fin de l'exécution du processus.
system("clear");
★ Réviser le cours
❓ Q/R
Que sont les deux fonctions principales d'un système d'exploitation ?
① Le SE en tant que machine étendue (ou « machine virtuelle ») : interface entre les applications et le matériel, plus facile à programmer/utiliser que le matériel. ② Le SE en tant que gestionnaire de ressources : ordonnance et contrôle l'allocation des ressources (processeurs, mémoires, périphériques d'E/S). Les deux tâches essentielles ici sont le partage et la protection.
Qu'est-ce qu'un appel système et quelles sont ses 4 étapes ?
C'est le mécanisme par lequel un programme utilisateur accède aux services du SE. Les 4 étapes : ① basculer en mode noyau, ② invoquer le SE, ③ revenir en mode utilisateur, ④ retourner le contrôle au programme utilisateur. Le noyau utilise une table de pointeurs indexée sur les numéros d'appels système pour dispatcher.
Que retourne open ?
open retourne -1 en cas d'échec, et un entier positif ou nul sinon, correspondant à un index dans le tableau des fichiers ouverts (le descripteur).
Pourquoi read peut-il retourner moins d'octets que demandé ?
Trois cas : ① fin de fichier, ② lecture sur un tube ou un terminal, ③ interruption par un signal. read vérifie la validité du descripteur et le mode d'ouverture, mais ne teste pas les droits d'accès de l'utilisateur (cela est fait à l'open).
Différence entre dup et dup2 ?
dup(oldfd) duplique oldfd et renvoie le plus petit numéro de descripteur disponible. dup2(oldfd, newfd) oblige la copie à recevoir le numéro newfd (si newfd était ouvert, il est fermé automatiquement) — très utile pour les redirections. Dans les deux cas, l'original et la copie partagent la position modifiée par lseek.
Que retourne fork() ?
• Dans le père : le PID du fils (entier positif).
• Dans le fils : 0.
• En cas d'échec : -1 (seul le père existe alors). Les deux processus continuent à l'instruction qui suit le fork ; seules les valeurs de PID et PPID diffèrent.
Quelle est la différence entre wait et waitpid ?
wait attend la fin de n'importe lequel des fils. waitpid(pid, status, options) permet de cibler un fils particulier (pid > 0), un groupe (pid < -1), n'importe quel fils du même groupe (pid == 0) ou n'importe quel fils (pid == -1, équivalent à wait). Option utile : WNOHANG pour ne pas bloquer.
Que retourne un appel exec* en cas de succès ?
RIEN. Le code ayant fait l'appel exec* n'existe plus — il a été remplacé par le nouveau programme. En cas d'échec, retourne -1. Le PID, lui, est conservé.
Que signifient les lettres l, v, p, e dans les variantes d'exec ?
l : arguments en liste (terminée par NULL). v : arguments en vecteur (tableau). p : recherche du fichier via la variable d'environnement PATH. e : environnement passé manuellement en dernier paramètre.
À quoi sert system() ? Différence avec fork + exec ?
system(commande) de stdlib.h lance directement un programme dans un programme C sans avoir à écrire le couple fork/exec*. Elle crée un nouveau processus /bin/sh qui exécute /bin/sh -c commande. Particularité : le processus appelant reste bloqué jusqu'à la fin de l'exécution.
🃏 Flashcards
Clique pour retourner.
Machine étendue
Quel est le rôle du SE vu comme machine étendue ?
tourne →4 étapes d'un appel système
Ordre exact.
tourne →② Invoquer le SE
③ Revenir en mode utilisateur
④ Retourner le contrôle au programme
Prototype de open
Les deux signatures.
tourne →int open(const char *pathname,
int typeOpen);
int open(const char *pathname,
int typeOpen,
mode_t droitsFic);Valeur de retour de open
Échec et succès.
tourne →Les 3 tables d'open
Lesquelles ?
tourne →② Table des fichiers ouverts (globale)
③ Table des inodes
read — retour partiel
3 cas.
tourne →② Lecture sur tube ou terminal
③ Interruption par un signal
lseek · whence
3 valeurs possibles.
tourne →SEEK_SET (début) · SEEK_CUR (courant) · SEEK_END (fin). Peut aller au-delà de la taille du fichier.dup vs dup2
Différence principale.
tourne →dup → plus petit fd disponible.dup2(old, new) → fd imposé = new (ferme new s'il était ouvert). Utile pour les redirections.stdin / stdout / stderr
Quels fd ?
stdin = 0 · stdout = 1 · stderr = 2Valeurs de retour de fork
3 cas.
tourne →Fils : 0
Erreur : -1 (seul le père existe)
PID 1
Quel est-ce ?
tourne →init (ou systemd) — ancêtre de tous les processus.Macros de wait
4 macros sur status.
WIFEXITED · terminé normalementWEXITSTATUS · code de retourWIFSIGNALED · tué par signalWTERMSIG · n° du signalwaitpid · valeurs de pid
4 cas.
tourne →< -1 : fils du groupe |pid|-1 : n'importe quel fils (= wait)0 : fils du même groupe> 0 : le fils de PID pidFamille exec* — lettres
Que veulent dire l v p e ?
p = PATH · e = environnement manuel
Retour d'exec*
Succès et échec.
tourne →Échec : -1. Le PID est conservé.
✎ Quiz éclair
open en cas d'échec ?open retourne -1 en cas d'échec, sinon un entier positif ou nul (le descripteur).O_EXCL avec O_CREAT garantit que l'appel crée le fichier ; si le fichier existe déjà, open échoue.dup et dup2 :fork() retourne :waitpid, on utilise :WNOHANG est l'option qui rend waitpid non-bloquant.execl retourne :exec* remplace le code du processus. En cas de succès, il n'y a aucun retour. En cas d'échec : -1.exec*, lesquelles cherchent dans la variable PATH ?PATH : execlp et execvp.📌 À retenir
- SE = liaison matériel ↔ applications, points d'entrée génériques pour les périphériques
- Double fonction : machine étendue + gestionnaire de ressources
- Composition : noyau (kernel, 1er chargé en mémoire) + outils système
- 6 fonctions de base : processus · mémoire · système de fichiers · E/S · réseaux · systèmes répartis
- Appel système : 4 étapes → basculer noyau → invoquer SE → revenir user → rendre la main
- POSIX : fichiers (
open/close/read/write/dup/dup2/stat), processus (fork/pipe/exec*/exit), réseaux (socket/bind/…) open: retourne -1 si échec, sinon un fd ≥ 0openmanipule 3 tables : descripteurs (par proc) → fichiers ouverts (global) → inodesread/writepeuvent renvoyer moins que demandé (EOF, tube/terminal, signal)lseek(fd, offset, whence)avecSEEK_SET/SEEK_CUR/SEEK_END— peut dépasser la tailledup= plus petit fd libre ;dup2= fd imposé (ferme l'ancien) → redirectionsfork: père reçoit PID(fils), fils reçoit 0, erreur = -1 ; seuls PID/PPID diffèrentwaitbloque jusqu'à mort d'un fils ;waitpid+WNOHANG= non-bloquantexec*: remplace le code, PID conservé ; succès = aucun retour, échec = -1 ;l/v/p/e= liste/vecteur/PATH/envsystem("cmd")=fork+execde/bin/sh -c cmd, bloque jusqu'à la fin