Nous avons vu dans l’épisode 1 l’architecture générale de l’environnement du noyau Linux, ainsi que ce que celui-ci réalise en matière de gestion de processus, et dans l’épisode 2 ce qu’il réalise en termes de gestion mémoire.
Pour continuer notre exploration des fonctionnalités du noyau, nous allons, dans cet épisode, aborder la gestion des périphériques.
Sur un PC les périphériques sont nombreux : clavier, souris, écran, carte graphique, carte réseau, clé USB, disque dur, SSD, etc.
Dans le monde Windows, les périphériques ont été historiquement livrés par leur constructeur accompagnés par le pilote de périphérique adapté à Windows. Pendant des décennies les constructeurs n’ont pas daigné fournir les pilotes de périphériques (drivers) adaptés à Linux. Les programmeurs du noyau Linux ont donc dû s’adapter en les développant eux-mêmes.
Une technique qui a beaucoup été utilisée est la retro-ingénierie, qui consiste à décortiquer le driver pour Windows jusqu’à ce qu’on ait compris ce qu’il fait et comment il fonctionne.
Dans les années 90, lorsque les programmeurs du noyau étaient encore peu nombreux (l’époque où le contributeur numéro deux après Linus Torvalds était le légendaire Alan Cox), il n’y avait généralement presqu’une seule personne derrière chaque famille de drivers.

A cette époque, les drivers pour les cartes réseau Ethernet étaient par exemple pratiquement tous codés par un programmeur de la NASA (dont j’ai oublié le nom, on pourrait le retrouver en allant farfouiller dans le code source Linux de ces drivers).
Le monde Linux a donc appris au fil des décennies à coder seul ses drivers. Le noyau est donc devenu de facto très bon pour auto-détecter le matériel.
Jusqu’à la version 0.99.14 du noyau Linux, les drivers étaient en dur dans le noyau. C’était une époque où les utilisateurs de Linux compilaient eux-mêmes un noyau aux petits oignons pour leur machine. Lors de la configuration pré-compilation, on indiquait quels drivers on voulait, et le noyau était ensuite compilé avec ces drivers. Quand on bootait dessus, on avait le support des périphériques dont on avait retenu le driver dans le noyau. Cette action était assez sportive car le driver n’était pas lié à un modèle commercial de périphérique, mais généralement à la puce électronique située au cœur de ce périphérique. Il n’était donc pas rare de devoir consulter Windows pour savoir quelle puce Windows disait détecter.
A partir de la version 0.99.15 du noyau Linux en 1994, celui-ci s’est vu ajouter la fonction de modules chargeables. On pouvait désormais monter ou démonter dynamiquement un driver dans le noyau, ce qui fut une avancée notable.
Au cours des années 2000, des distributions comme Ubuntu ont poussé très loin les mécanismes d’auto-détection du matériel, ces mécanismes se terminant par la montée dynamique du bon driver dans le noyau.
Entre temps, pour la petite histoire, Windows s’est inspiré de Linux en ayant de moins en moins la nécessité d’utiliser des drivers externes fournis par les constructeurs. Les drivers sont intégrés dans le code du noyau Windows.
La gestion des périphériques dans le noyau Linux

Les périphériques étant du matériel, seul le noyau a les droits pour y accéder. Ces accès se font par des pilotes de périphériques en dur dans le noyau ou montés sous forme de modules comme nous l’avons vu (commande lsmod pour voir quels modules sont montés, insmod pour monter un module, rmmod pour descendre un module).
Les processus ayant besoin d’accéder aux périphériques doivent demander au noyau de servir d’intermédiaire. Pour cela, deux mécanismes sont prévus pour que l’accès fonctionne.
Le premier mécanisme est l’association entre des drivers dans le noyau et des fichiers devices particuliers sur le disque dur, situés généralement dans le répertoire /dev (mais en réalité on peut les mettre n’importe où).
Ainsi le fichier /dev/null par exemple est associé à un driver qui ne fait rien du tout avec tout ce qu’on lui envoie. Ou le fichier /dev/random est associé à un driver qui génère des nombres pseudo-aléatoires si l’on le lit (la commande dd if=/dev/random of=fichier bs=1024 count=1 crée un fichier d’1 ko rempli de données aléatoires). De même, le fichier /dev/sda est relié au driver noyau qui gère les disques SCSI. Une écriture sur ce fichier device écrit directement sur le disque dur correspondant.
Si l’on regarde /dev/null et /dev/random, voici ce que l’on voit :


Comme on peut le voir, la commande ls sur ces fichiers spéciaux montre deux chiffres distincts (1 et 3 pour /dev/null, et 1 et 8 pour /dev/random). Ces chiffres sont appelés le majeur et le mineur. Chaque driver dans le noyau a un majeur et un mineur, et le simple fait de créer un fichier device sur le disque dur avec le même numéro de majeur, et le même numéro de mineur suffit au noyau pour savoir à quel driver associer le fichier device (et donc à quel driver envoyer les données reçues sur ce fichier device).
Comme on peut le voir, la première lettre à gauche est un c, ce qui signifie que ce fichier device est associé à un driver de type caractère. Les autres types qu’on peut voir sont b (pour driver de type bloc, c’est le cas de /dev/sda par exemple), p pour les pipes nommés (qui sont hors sujets car ils pointent dans le noyau vers des mécanismes de communication inter-processus et non vers un driver de périphérique, et s pour les sockets nommés (hors sujet de notre épisode également).
Pour créer un fichier device comme cela, la commande à utiliser est mknod, avec le droit root (sudo) avec la syntaxe : sudo mknod –m droits fichier_device type majeur mineur.
Par exemple, imaginons que /dev/null n’existe pas sur votre machine, vous pouvez le créer par la commande :
sudo mknod –m 666 /dev/null c 1 3
Notez que vous n’êtes pas obligé de créer ce fichier dans /dev. La commande ci-dessous crée un fichier device tout aussi valable mais dans /opt par exemple :
sudo mknod –m 666 /opt/toto c 1 3
/dev/null et /opt/toto sont des points d’entrée sur le disque menant exactement au même driver de périphérique dans le noyau, puisque les numéros de majeur et mineur sont les mêmes.
Vous avez déjà compris que le fichier device /dev/random peut être créé par la commande :
sudo mknod –m 666 /dev/random c 1 8
Appels système pour les fichiers device
Une fois que le fichier device existe sur le disque et donc sert de point d’entrée aux programmes pour communiquer avec le driver associé dans le noyau, ce dernier met à disposition des programmes des appels système pour réaliser cette communication. C’est le deuxième mécanisme.
Un programme donné, le vôtre par exemple, pourra ouvrir le fichier device cible avec l’appel système open, lire et écrire dedans avec les appels système read et write, et fermer la connexion au périphérique avec l’appel système close.
L’appel système ioctl est également mis à disposition par le noyau pour lancer des commandes de paramétrage aux drivers. Exemple : imaginons que nous ayons une carte son dont le driver noyau est associé au fichier device /dev/mon_son, et imaginons qu’on veuille la paramétrer pour sortir du son en 44 Khz sur 16 bits. Il va alors falloir lui faire ce paramétrage 44 Khz sur 16 bits via l’appel système ioctl avant de lui envoyer du son dans ce format par l’appel système write.
Cette philosophie de drivers associés à des fichiers device sur le disque a contribué à la réputation que, sous Unix (et donc aussi sous Linux), tout est fichier. Du point de vue du programmeur, cette philosophie est géniale car elle dispense d’avoir à apprendre comment fonctionne un périphérique donné pour pouvoir l’utiliser dans ses programmes. Ce qui se passe en réalité c’est que, pour chaque périphérique existant, un programmeur doit se pencher sérieusement sur le fonctionnement de ce périphérique et écrire le driver noyau correspondant. Tous les autres programmeurs du monde n’auront alors plus qu’à utiliser le fichier device pour faire ce qu’il veut du périphérique, sans avoir à en creuser son fonctionnement.
Drivers sous forme de modules
Sauf à être un développeur du noyau Linux, si demain vous tombez sur un matériel inconnu dont le driver Linux n’existe pas, et que vous souhaitez écrire ce driver, vous devrez écrire un driver sous forme de module chargeable dans le noyau. Du moins si vous êtes sous Linux, car chaque Unix a ses spécificités dans ce domaine.
Ecrire un module chargeable n’est pas une mince affaire et nécessite des connaissances, notamment parce que votre programme est du code noyau, qui va être chargé en mémoire et tourner avec de très hauts privilèges. C’est donc du code par définition dangereux, surtout si votre code est bugué.
Mais c’est aussi du code qui est difficile à écrire car, comme il va tourner dans l’espace mémoire noyau, il doit respecter les règles en vigueur dans cet espace. Beaucoup de choses qu’on écrit en langage C dans l’espace mémoire utilisateur ne fonctionne pas à l’identique dans l’espace noyau.
Un printf par exemple, si facile à utiliser dans un programme en C, n’existe pas dans le noyau. A la place il faut utiliser printk, qui écrit des choses dans les logs du noyau (consultables avec la commande dmesg).
De même malloc n’existe pas dans l’espace mémoire du noyau, il faut utiliser kmalloc et kfree qui sont des fonctions spécifiques au noyau.
Bref, vous l’avez compris, écrire un driver chargeable sous Linux est une tâche qui certes ne nécessite pas de connaître la structure entière du noyau (40 millions de lignes de code, bon courage), mais nécessite tout de même une connaissance minimale de règles propres au noyau.
Votre module, si tant est que vous sachiez l’écrire (ce qui implique que vous avez également une connaissance fine du fonctionnement physique du matériel pour lequel vous écrivez un driver), est destiné à être monté sous forme de module chargeable via la commande insmod et déchargé via la commande rmmod.
Cet ajout / retrait de code dynamiquement dans le noyau nécessite aussi, vous vous en doutez, de respecter des règles strictes. Notamment, il faudra que votre code ait une fonction module_init(fonction_init) appelée au chargement par insmod, une fonction module_exit(fonction_cleanup) appelée au déchargement par rmmod, mais aussi des informations comme MODULE_LICENCE(« GPL »), ou l’implémentation des open, read, write, et close via la structure file_operations, etc.
Devenir programmeur de modules chargeables ne s’improvise pas. Il faut prendre un bon bouquin qui explique les règles de codage en C (eh oui, pas de python ou de langages à la con dès qu’on rentre dans le sérieux) à respecter.
Voici une suggestion de livre (malheureusement il n’existe pas de livre en français dans ce domaine pointu) :
Nous voici donc arrivés au terme de ce survol de la gestion des périphériques dans le noyau Linux. Dans l’épisode suivant nous verrons comment le noyau Linux traite les appels système.
Stay tuned !

