Nous avons vu dans l’épisode 1 l’architecture générale de l’environnement du noyau Linux et la manière dont il gère les processus. L’épisode 2 nous a permis d’entrer dans le détail de la gestion de la mémoire. Et l’épisode 3 nous a permis de creuser comment le noyau Linux gère les périphériques.
Dans cet épisode 4 nous allons voir comment le noyau Linux offre aux applications un service d’appels système.
La notion même d’appels système est intimement liée au fait que le noyau est installé dans une zone mémoire à hauts privilèges (qu’on appelle par commodité l’espace noyau), alors que les applications tournent dans le reste de la mémoire, sans privilèges particuliers (qu’on appelle par commodité l’espace utilisateur). De facto, le noyau est le seul programme ayant accès au matériel.
Comme les applications ont besoin d’accéder au matériel (pour détecter un clic de souris sur un bouton par exemple, et déclencher le traitement associé), et que le noyau est le seul à avoir l’accès au matériel, de facto on n’y coupe pas : il faut impérativement que le noyau propose aux applications un service d’accès au matériel en passant par lui. Ce service ce sont les appels système.

Le noyau Linux propose un nombre variable d’appels système en fonction de l’architecture cible. Néanmoins, il y a en moyenne un peu plus de 300 appels système implémentés, couvrant tous les besoins possibles des applications : accéder aux fichiers, aux périphériques, aux couches réseaux, à la gestion des processus, etc.
Le principe d’un appel système vu d’un programme écrit en langage C est simple : il appelle un (faux) appel système implémenté dans la librairie C lequel déclenche un (vrai) appel système du noyau. Ce deuxième déclenchement fait basculer la poursuite de l’exécution dans le code de l’appel système dans le noyau, donc en espace noyau, donc avec les privilèges d’accès au matériel dont bénéficie le noyau.
Le code de l’appel système tel que programmé dans le noyau ne fait pas qu’exécuter ce qui est demandé par le programme. Au préalable il vérifie que le programme appelant a bien les droits pour exécuter ce qu’il demande. Si ce n’est pas le cas, il ramène un code d’erreur, sinon il exécute ce qui est demandé (ouvrir un fichier par exemple).
Ainsi, à chaque fois qu’un programme exécute une fonction qui nécessite un accès matériel (comme un simple printf par exemple), un ou plusieurs appels système sont exécutés dans l’espace noyau, en exécutant du code noyau écrit par les meilleurs programmeurs du monde. Donc du code sûr.
Déclenchement d’un appel système
La manière dont la librairie C déclenche l’appel système noyau dépend de l’architecture sur laquelle on est.
Sur un processeur de type x86_64, elle met dans les registres appropriés du micro-processeur le numéro de l’appel système ainsi que les paramètres passés, puis elle déclenche l’instruction syscall du micro-processeur.
Sur un processeur de type x86 (32 bits), elle prépare pareil mais au lieu d’exécuter l’instruction syscall elle effectue une interruption par l’instruction int 0x80.
Sur les architectures à micro-processeur ARM (32 ou 64 bits), la préparation est du même type mais la librairie C finit par déclencher l’appel système en exécutant l’instruction svc du micro-processeur.
Dans tous les cas, vu du noyau, un appel système ce sont des valeurs lues dans des registres du micro-processeur (numéro de l’appel système et paramètres), et une prise d’exécution qui intervient après déclenchement d’un trap (syscall, int 0x80, ou svc).
Les appels système pour des programmes non écrits en langage C
Les appels système proposés par le noyau sont écrits en C dans le noyau (et peut-être de plus en plus en rust à l’avenir), mais ce n’est pas pour autant qu’il ne peuvent être appelés que par des programmes écrits en C.
Dans les faits, actuellement, pratiquement tous les langages utilisent la librairie C (libc). C’est le cas des langages interprétés comme Python, dont l’interpréteur est écrit en C donc fait ses appels système via la libc, mais c’est aussi le cas des langages n’ayant rien à voir avec le C comme Rust ou Go qui, sauf demande explicite, produisent des binaires utilisant la librairie standardisée, fiable et rassurante libc.
Quid de la compatibilité POSIX ?
POSIX est une norme visant à définir ce qu’est un Unix. Si demain je programme un système d’exploitation moi-même et que je respecte la liste des appels système définis par cette norme, alors mon OS peut être qualifié d’Unix.
Alors, Linux est-il compatible POSIX ? Linux est-il un Unix ?
Oui, et non. Non, car le noyau propose ses appels systèmes (les vrais) dans son format à lui, sans se soucier de POSIX. Oui, car la librairie C libc respecte bien ce standard POSIX, qui s’applique à la liste des (faux) appels système proposés par la libc et non aux (vrais) appels système proposés par le noyau.
La libc est donc POSIX, ce qui garantit que tout programme dont le code source est en langage C compilera et s’exécutera sans problème sous tout OS compatible POSIX, donc compatible Unix, dont le système d’exploitation Linux (compris dans le sens noyau Linux + libc).
Programmation système sous Linux
Vous l’avez compris, lorsqu’on parle de programmation système sous Linux on ne parle que d’utiliser l’interface des (faux) appels système proposés par la libc POSIX et non directement l’interface des (vrais) appels système du noyau.
Nous verrons dans les épisodes ultérieurs comment programmer avec beaucoup d’appels système proposés par la libc.
En attendant, le lecteur qui souhaitera se familiariser avec le sujet pourra utilement acquérir la bible qui explique ce sujet :

