Linux Kernel Programming Setup

Apprentissage du Dev Linux en Kernel Mode

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 148
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Linux Kernel Programming Setup

Post by Hydraxx »

Linux Kernel Programming Setup

1. Introduction

Cette première partie explique comment préparer un environnement pour apprendre à programmer des modules et des drivers logiciels Linux.

Le système choisi est Ubuntu dans une machine virtuelle Hyper-V.

Nous utilisons KDevelop pour écrire le code C et Kbuild pour compiler les modules.

Le cours suppose que le langage C est connu, mais n'exige aucune connaissance préalable des API du noyau Linux.

Chaque notion est expliquée avec un exemple, la raison de son utilisation et la façon de vérifier son fonctionnement.

Les essais de modules se font uniquement dans une VM de test avec un point de contrôle. Une erreur dans un driver peut faire planter le noyau.

Sommaire des thèmes
  • Principes du noyau Linux et comparaison avec Windows.
  • Prérequis matériels et logiciels.
  • Installation et configuration d’Hyper-V.
  • Préparation d’Ubuntu.
  • Environnement KDevelop.
  • GCC, Makefile et Kbuild.
  • Commandes de gestion des modules.
  • Sources et documentation Linux.
  • Analyse statique des drivers.
  • Débogage et traçage du noyau.
  • Compilation croisée et systèmes embarqués.
  • TP : création d’un module .ko.
  • Exercices, questions pièges et récapitulatif.

CHAPITRE 1 - Architecture et fonctionnement du noyau Linux


2. Rôle du noyau Linux

Le noyau Linux gère les processus, mémoires, systèmes de fichiers, réseaux et périphériques. Ubuntu est une DISTRIBUTION : elle assemble le noyau, les programmes et les outils d'administration.

Exemple : un programme C appelle read(fd, buffer, 128). La libc fournit l'interface, puis le noyau traite l'appel système.

Selon le fichier, il utilise un cache, attend des données ou appelle un pilote. La libc ne pilote pas directement le matériel.


3. User mode et kernel mode

Un processus utilisateur dispose de privilèges limités ; une mauvaise adresse provoque souvent SIGSEGV dans ce processus.

Une erreur en espace noyau peut corrompre l'état global ou causer un oops/panic. Sur x86-64, on parle souvent de ring 3 (utilisateur) et ring 0 (noyau).

Adresse virtuelle et adresse physique sont deux notions distinctes.


4. Qu'est-ce qu'un module ? Un driver ?

Un module noyau chargeable est du code pouvant être ajouté à un noyau en fonctionnement, souvent sous forme de fichier .ko.

Un driver est du code qui fournit une fonctionnalité liée à un périphérique réel ou virtuel ; il peut être intégré directement au noyau ou compilé en module.

Tous les modules ne sont pas des drivers, et tous les drivers ne sont pas des modules.

Notre premier module affichera des messages et ne pilotera aucun matériel.

Ensuite, un character device driver pourra exposer /dev/mon_driver, recevoir read/write et éventuellement ioctl.

Cette progression permet de maîtriser le cycle de vie avant les interruptions et DMA.


5. Comparaison raisonnée avec Windows WDM

DriverEntry rappelle approximativement module_init et DriverUnload rappelle module_exit, mais leurs signatures et modèles diffèrent.

Linux n'a ni IRP WDM ni DEVICE_OBJECT universel. Un character driver peut exposer struct file_operations.

DeviceIoControl et ioctl permettent d'envoyer des commandes spécifiques à un driver, mais les codes, buffers et validations sont différents.

En Linux, il n'existe pas de traduction automatique de METHOD_BUFFERED : les transferts user/kernel doivent respecter les API appropriées.


6. Pourquoi travailler dans une machine virtuelle

Un module erroné peut bloquer Ubuntu, rendre des données incohérentes ou empêcher un redémarrage.

Une VM Hyper-V permet d'isoler les tests du système principal et de revenir à un point de contrôle.

Attention : une VM n'est pas une barrière de sécurité absolue contre toutes les vulnérabilités ; elle constitue surtout un excellent laboratoire pour les crashs accidentels.

Évitez les dossiers hôtes montés en écriture si vous testez du code instable.


7. Kernel Oops et Kernel Panic

Un kernel oops signale un problème grave détecté par le noyau, sans arrêt systématique de toute la machine. Un panic signifie qu'il ne peut pas poursuivre en sécurité.

Les logs (printk/pr_info, dmesg, journal) deviennent donc essentiels : on ne peut pas déboguer comme une application console ordinaire.

Un message de log n'est toutefois pas une preuve de sécurité ni de correction.

À retenir

Notre premier objectif n'est pas de modifier Linux mais de comprendre les conditions nécessaires à une insertion, une exécution et un retrait de module propres.


CHAPITRE 2 - Prérequis matériels et logiciels


8. Configuration recommandée

Pour Ubuntu Desktop avec KDevelop : 2 vCPU, 4 Gio RAM, disque VHDX dynamique de 30 à 40 Gio. Avec de la mémoire disponible, 6 à 8 Gio offrent plus de confort pour l'IDE.

Un Ubuntu Server sans bureau est plus léger. Compiler un petit module consomme bien moins de ressources que reconstruire tout le noyau.

Le VHDX dynamique croît avec les données réellement écrites ; les points de contrôle augmentent aussi l'espace nécessaire sur le disque de l'hôte.


9. Architecture x86-64

Le PC hôte et la VM de ce cours utilisent x86-64. Les headers et les objets d'un module sont construits pour l'architecture et la configuration du noyau cible.

Compiler un module x86-64 ne le rend pas utilisable sur ARM64. De même, le module doit être compatible avec la version, la configuration et l'ABI du noyau cible.

Linux ne garantit pas une ABI stable pour les modules externes.


10. Virtualisation matérielle

Intel VT-x et AMD-V offrent des mécanismes matériels employés par les hyperviseurs.

Sur Windows, le Gestionnaire des tâches > Performances > Processeur peut indiquer l'état de la virtualisation.

Dans les systèmes modernes, le rôle d'Hyper-V et les exigences exactes du poste varient : ne modifiez pas des options UEFI au hasard si Hyper-V fonctionne déjà.


11. Mémoire, CPU et stockage : comprendre les compromis

Un vCPU ralentit parfois GCC ; trop de vCPU retire inutilement des ressources à l'hôte. Les checkpoints consomment du disque.

Les réglages mémoire d'Hyper-V sont distincts des allocations du noyau que nous étudierons.


12. Vérifications de base sous Windows

Dans PowerShell administrateur :

Code: Select all

Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All
« State : Enabled » indique l'activation de la fonctionnalité. Si la commande n'est pas reconnue sur votre édition, cela ne prouve pas que le CPU ne sait pas virtualiser.

Si Hyper-V fonctionne déjà, aucune modification UEFI n'est requise pour commencer le cours.


CHAPITRE 3 - Hyper-V et machine virtuelle Ubuntu


13. Le rôle de l'hyperviseur

Hyper-V fait fonctionner Ubuntu comme système invité (« guest ») sur un hôte Windows (« host »).

Il présente des processeurs virtuels, une mémoire, des disques et des périphériques virtuels.

Pour nos modules logiciels, cet environnement suffit ; l'accès direct à un périphérique physique spécifique n'est pas nécessaire.


14. Créer une VM de génération 2

Dans le Gestionnaire Hyper-V : Nouvelle > Machine virtuelle. Choisissez un nom explicite, par exemple Ubuntu-Kernel-Lab.

Choisissez Génération 2 (démarrage UEFI), réservez environ 4096 Mio de RAM, 2 processeurs virtuels dans les paramètres et créez un disque VHDX d'au moins 30 Gio.

Associez un commutateur virtuel offrant Internet si vous devez installer des paquets.

Attachez l'ISO officielle d'Ubuntu 24.04 LTS au lecteur DVD virtuel, puis démarrez la VM.


15. Secure Boot : deux protections différentes

Dans Hyper-V, le démarrage sécurisé UEFI d'une VM génération 2 peut utiliser le modèle « Microsoft UEFI Certificate Authority » pour Linux.

Il vérifie la chaîne de démarrage. Ubuntu peut également appliquer une politique de signature aux modules noyau tiers.

Il est donc possible qu'Ubuntu démarre correctement, que le fichier .ko compile, MAIS que son insertion échoue avec « Required key not available ».

Ne désactivez pas automatiquement Secure Boot sur votre machine principale.

Pour apprendre dans une VM isolée, vous avez deux choix documentés : signer et faire reconnaître le module (MOK), ou désactiver Secure Boot de CETTE VM si vous acceptez la réduction de protection.

Ce cours ne dépend pas d'une désactivation : le TP explique d'abord comment diagnostiquer le problème.


16. Installation d’Ubuntu dans Hyper-V

Utilisez l'installateur graphique de l'ISO.

L'option effaçant le disque concerne le disque VIRTUEL de la VM, pas le disque Windows hôte, à condition d'avoir correctement attaché le VHDX virtuel.

Vérifiez cette distinction. Créez un utilisateur standard, pas une session permanente root. Une fois Ubuntu démarré, retirez l'ISO du lecteur DVD virtuel si besoin.


17. Réseau et système de fichiers

Un commutateur réseau de type Default Switch (selon l'environnement) peut permettre les mises à jour. Vérifiez une connexion depuis Ubuntu avant d'installer GCC.

N'exposez pas inutilement le service SSH de la VM à Internet.

Pour transférer du code, Git ou une copie de fichiers maîtrisée est préférable à un dossier hôte entièrement accessible en écriture.


18. Points de contrôle : la ceinture de sécurité

Éteignez proprement Ubuntu, puis dans Hyper-V créez un point de contrôle appelé « Ubuntu propre + outils kernel ». Recréez un point avant tout TP risqué.

Le point de contrôle capture un état récupérable de la VM, mais ne remplace pas une sauvegarde indépendante de vos fichiers.

Pour les travaux expérimentaux, évitez de conserver vos seuls exemplaires de code source à l'intérieur de la VM.


19. Premier démarrage : contrôles

Dans un terminal Ubuntu :

Code: Select all

cat /etc/os-release
uname -r
uname -m
free -h
lsblk
Le premier identifie la distribution ; uname -r la release du noyau actif ; uname -m l'architecture ; free la mémoire ; lsblk les disques visibles depuis la VM.

Vérifiez notamment x86_64 et notez la release exacte. Ne réutilisez jamais la version écrite dans un livre : les headers devront correspondre à la VM.

Erreur fréquente

un checkpoint n'annule pas nécessairement les écritures effectuées depuis la VM vers un partage hôte externe.


CHAPITRE 4 - Préparation d’Ubuntu


20. Distribution, noyau et en-têtes

Ubuntu livre un noyau construit avec ses propres options, correctifs et conventions de packaging.

Les en-têtes (« headers ») décrivent les interfaces nécessaires à la compilation et s'accompagnent d'autres fichiers générés et de règles Kbuild.

Ils ne représentent pas à eux seuls l'intégralité des sources du noyau.


21. Identifier la version active

Code: Select all

uname -r
ls /lib/modules/$(uname -r)/
readlink -f /lib/modules/$(uname -r)/build
Explications : $(uname -r) exécute la commande entre parenthèses et remplace l'expression par sa sortie.

Le dossier /lib/modules/<version>/ contient les modules liés à la version, et le lien « build » doit pointer vers un arbre de compilation préparé pour cette version.

Si readlink échoue ou si le dossier manque, il faudra installer les en-têtes corrects.


22. Mise à jour d’Ubuntu

Code: Select all

sudo apt update
sudo apt full-upgrade
« apt update » actualise la liste des paquets disponibles ; il n'installe pas en lui-même de nouvelle version de GCC.

« apt full-upgrade » peut ajouter ou retirer des paquets pour achever une mise à niveau, y compris une mise à jour du noyau.

Lisez les changements proposés avant confirmation. Si le noyau a été mis à jour, redémarrez :

Code: Select all

sudo reboot
Pourquoi redémarrer ? « uname -r » affiche le noyau EN MÉMOIRE, pas la dernière image noyau installée sur disque.

Installer des headers pour un noyau récent tout en exécutant l'ancien peut provoquer une confusion évitable.


23. Installation des outils de développement

Après redémarrage :

Code: Select all

sudo apt install build-essential git gdb make pkg-config \
    linux-headers-$(uname -r) kmod perl python3
  • build-essential : ensemble de dépendances de compilation courantes (dont GCC, make et les outils associés).
  • git : récupérer, versionner et comparer du code.
  • gdb : debugger surtout nos programmes utilisateurs ; le noyau demande une configuration supplémentaire.
  • linux-headers-$(uname -r) : éléments pour construire un module contre le noyau démarré.
  • kmod : fournit notamment des commandes de gestion des modules.
  • perl : utilisé par divers scripts de l'arbre Linux.
Si le paquet exact des headers est introuvable, contrôlez vos dépôts et votre noyau ; certains noyaux personnalisés n'ont pas de paquet Ubuntu correspondant.

Il ne faut pas remplacer aveuglément les headers par « n'importe quelle version proche ».


24. Vérifier la chaîne de compilation

Code: Select all

gcc --version
make --version
git --version
test -d /lib/modules/$(uname -r)/build && echo "Headers présents"
La commande test vérifie seulement l'existence d'un répertoire.

Elle ne démontre pas que toutes les données nécessaires à Kbuild sont valides ; seule une vraie compilation de module le confirmera. Pour inspecter la liaison :

Code: Select all

ls -ld /lib/modules/$(uname -r)/build

25. Pourquoi ne pas compiler le kernel complet maintenant ?

Compiler un module externe est rapide, ne remplace pas le noyau démarré et permet d'apprendre le cycle de vie.

Reconstruire un noyau complet exige davantage de temps, d'espace disque, de configuration et une stratégie de démarrage de secours.

Nous différons donc cette étape, sans la confondre avec « installer linux-headers ».


26. Travailler sans être root en permanence

Éditez et compilez depuis votre compte normal. Les commandes d'insertion et de retrait demandent des privilèges : « make » sans sudo, puis « sudo insmod » seulement pour tester.


27. Comprendre les erreurs de paquets

« Unable to locate package » : headers ou dépôts incohérents. « No space left on device » : contrôlez df -h.

« Could not get lock » : un autre gestionnaire de paquets fonctionne peut-être ; n'effacez pas un verrou au hasard.


CHAPITRE 5 - Configuration de KDevelop


28. KDevelop : présentation

KDevelop facilite édition, navigation et analyse du C. Il ne remplace pas Kbuild : l'IDE doit lancer make avec les règles et les headers du noyau cible.


29. Installation

Code: Select all

sudo apt install kdevelop clangd clang-format
Selon la version d'Ubuntu, les fonctionnalités d'indexation et leur intégration dépendent des composants disponibles.

KDevelop n'exige pas forcément tout l'environnement de bureau KDE : il peut être exécuté sous Ubuntu Desktop avec les bibliothèques requises, installées comme dépendances.


30. Ouvrir un projet de module

Créez un dossier personnel dans ~/kernel-lab/hello_module et ouvrez-le dans KDevelop.

Si l'IDE demande un système de build, utilisez l'import d'un dossier ou d'un projet Makefile lorsque disponible.

Les menus exacts varient selon la version ; le point essentiel est de conserver le Makefile Kbuild et de pouvoir exécuter « make » dans le répertoire source.


31. Ne pas confondre deux familles de headers

Les headers de glibc comme <stdio.h> servent aux applications utilisateur. Les headers noyau comme <linux/module.h> et <linux/init.h> servent au code noyau.

Un module ne doit pas appeler printf(), malloc() ou fopen() de la libc. Il emploie des API noyau distinctes (pr_info, kmalloc, etc. selon le besoin).


32. Indexation et faux diagnostics

Un éditeur peut souligner une macro pourtant compilable lorsque ses chemins d'en-têtes ou ses options de compilation ne reflètent pas Kbuild.

Exemple : MODULE_LICENSE() ou __init sont interprétés avec le contexte de build du noyau, pas celui d'un programme C classique.

Ne prenez jamais « tout est vert dans l'IDE » pour une preuve de bonne compilation ; et inversement, un soulignement rouge ne prouve pas une erreur du driver.


33. Configuration de la commande de build

Dans les réglages du projet, définissez une commande externe qui lance make depuis le dossier du module. Le Makefile du chapitre 12 intégrera le chemin du noyau.

Testez toujours la même commande depuis un terminal : vous distinguerez ainsi un problème de l'IDE d'un problème de build réel.


34. GDB et noyau

GDB sur un exécutable utilisateur peut s'attacher à un processus. GDB ne peut pas simplement « s'attacher » à un module noyau pour obtenir le même débogage.

Il faut un noyau configuré pour le débogage, des symboles exploitables et un moyen adapté de contrôler l'exécution (par exemple kgdb, ou un environnement QEMU avec stub GDB).

Dans notre VM Hyper-V, commençons par les logs, ftrace et l'analyse statique.


35. Style de code

Le noyau privilégie une indentation par tabulations, des noms en minuscules avec underscores et une gestion rigoureuse des retours d'erreur. Adopter ces conventions aide à lire les sources existantes.


CHAPITRE 6 - GCC, Makefiles et Kbuild


36. De test.c à test exécutable

Pour un programme utilisateur :

Code: Select all

cat > test.c <<'C'
#include <stdio.h>
int main(void)
{
    printf("Bonjour depuis user mode !\n");
    return 0;
}
C

gcc -Wall -Wextra -o test test.c
./test
« -Wall » et « -Wextra » activent de nombreux avertissements ; « -o test » donne le nom du fichier final.

GCC pilote normalement prétraitement, compilation, assemblage et édition de liens.

Le résultat peut être un exécutable ELF utilisateur, chargé avec l'aide du noyau et de l'éditeur de liens dynamique selon sa construction.


37. Étapes de compilation avec GCC

Code: Select all

gcc -E test.c -o test.i       # Prétraitement : #include, #define...
gcc -S test.c -o test.s       # Génération assembleur
gcc -c test.c -o test.o       # Fichier objet, pas encore exécutable
file test.o
file test
Un fichier .o contient des sections et symboles ; « file » permet d'inspecter le format général. Les fonctions sont ensuite liées avec celles nécessaires au programme.

Ce modèle pédagogique simplifie certains détails internes de GCC, mais évite de confondre source, objet et binaire final.


38. Pourquoi ne pas faire « gcc -shared module.c » ?

Un module noyau dépend des options, des headers générés, du traitement de modpost, des symboles exportés, de l'architecture, de la configuration du noyau et des règles de Kbuild.

Un objet ELF ordinaire ne devient pas un module chargeable parce qu'il porte l'extension .ko.

L'édition de liens et les métadonnées sont organisées par le système de build du noyau.


39. Qu'est-ce que Kbuild ?

Kbuild est l'infrastructure de construction du noyau Linux. Il sait quels fichiers et options utiliser pour compiler un noyau ou un module externe.

« Make » est l'outil qui interprète les Makefiles ; « Kbuild » désigne les règles et conventions de compilation propres au noyau.


40. Déclaration d’un module avec obj-m

Considérons un fichier mon_module.c. Dans un fichier Kbuild ou Makefile, la ligne :

Code: Select all

obj-m += mon_module.o
signifie : construire un module chargeable à partir de mon_module.o. Ici « obj » désigne la liste des objets, « -m » signifie module, « += » ajoute un élément.

Kbuild produit normalement mon_module.ko après ses étapes supplémentaires.

La ligne NE signifie PAS « ajouter mon_module.o à un projet Visual Studio » ni « créer un programme user-mode ».

Pour un module composé de plusieurs fichiers, on peut utiliser :

Code: Select all

obj-m += capteur_virtuel.o
capteur_virtuel-y := coeur.o commandes.o
Cela demande à Kbuild de composer le module capteur_virtuel à partir de coeur.o et commandes.o.

Ne nommez pas aussi un fichier source capteur_virtuel.c dans cette organisation si cela crée un conflit avec l'objet composite.


41. Commande de compilation Kbuild

Code: Select all

make -C /lib/modules/$(uname -r)/build M="$PWD" modules
Décomposition :
  • « make » : moteur qui lit les règles.
  • « -C CHEMIN » : change temporairement le répertoire de travail du processus make.
  • « $(uname -r) » : expansion de commande du shell, donnant la release active.
  • « /build » : chemin préparé pour compiler contre CE noyau.
  • « M="$PWD" » : chemin absolu vers le dossier contenant notre module externe.
  • « modules » : cible de construction des modules, facultative dans de nombreux cas.
Pourquoi M plutôt qu'un simple chemin de fichier C ? Parce que Kbuild doit connaître le répertoire qui contient les règles du module, ses sources et ses résultats.

Après une compilation réussie, les fichiers auxiliaires peuvent inclure .mod.c, .mod.o, Module.symvers, modules.order, ainsi que le .ko.

Leur présence exacte dépend de la version et du type de build.


42. Exemple de Makefile complet

Code: Select all

obj-m += mon_module.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD  := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
IMPORTANT : les deux lignes sous « all » et « clean » commencent par une vraie TABULATION, pas quatre espaces.

Ici « $(shell uname -r) » est évalué par make, tandis que « $(uname -r) » dans une commande shell est évalué par le shell.

La forme « $(MAKE) » transmet les propriétés nécessaires à l'invocation récursive de make.

Les chemins simples ci-dessus conviennent au laboratoire ; des projets complexes doivent gérer les chemins avec espaces et builds séparés.


43. Versioning, vermagic et symboles

Les modules externes ne disposent pas d'une ABI stable entre versions du kernel.

L'information « vermagic » incorporée dans un module aide à vérifier sa compatibilité avec le noyau cible.

Des contrôles de symboles et, selon la configuration, de versions de symboles interviennent aussi.

Un .ko construit pour 6.x.Y ne doit pas être supposé compatible avec n'importe quel autre 6.x.Z.


44. Pourquoi les fichiers .h ne suffisent pas

La construction requiert également configuration et fichiers générés, voire des informations de versions de symboles.

Pour les modules externes, « make modules_prepare » prépare certains éléments d'un arbre source, mais ne crée pas à lui seul Module.symvers nécessaire à certains scénarios avec CONFIG_MODVERSIONS.

Voilà pourquoi installer le paquet Ubuntu de headers adapté est plus sûr pour débuter.


45. Nettoyer les artefacts

Code: Select all

make clean
Une fois le Makefile précédent présent, cette commande retire les fichiers générés pour notre module, pas le code source.

Ne lancez jamais une commande de nettoyage sur un répertoire sans comprendre où elle pointe. Le futur TP la vérifiera concrètement.

Réflexe

Compiler avec Kbuild contre le noyau réellement démarré, pas avec une ligne gcc improvisée.


CHAPITRE 7 - Commandes de gestion des modules Linux


46. uname : identifier le noyau

Code: Select all

uname -r       # Release exacte
uname -m       # Architecture
uname -a       # Plusieurs informations
Attention : une release peut contenir un suffixe de distribution, par exemple « -generic ». Ce suffixe fait partie de l'identité du noyau cible, il n'est pas décoratif.

Ne construisez pas un module contre des headers dépourvus du suffixe attendu.


47. lsmod : modules actuellement chargés

Code: Select all

lsmod
L'affichage contient notamment des colonnes Module, Size et Used by.

« Used by » aide à comprendre les dépendances et références, mais sa valeur ne résume pas tous les états possibles d'un module.

Un module présent ne peut pas nécessairement être retiré immédiatement. Sous le capot, lsmod consulte l'état des modules exposé par le noyau, notamment via /proc/modules.


48. modinfo : examiner les métadonnées d'un module

Code: Select all

modinfo -F filename loop
modinfo -F license loop
Ici « loop » est un exemple de module qui peut être disponible selon la distribution. « -F » extrait un champ. Vous pouvez aussi inspecter directement un fichier :

Code: Select all

modinfo ./mon_module.ko
Cela peut afficher name, license, description, vermagic, etc. « modinfo » analyse des métadonnées ; il ne prouve pas que le code s'exécute sans erreur.

Certains modules sont intégrés statiquement au noyau et n'apparaissent pas comme .ko classiques.


49. insmod : charger un fichier .ko explicite

Code: Select all

sudo insmod ./mon_module.ko
« insmod » demande au noyau d'insérer le fichier indiqué.

Il ne résout généralement pas automatiquement toutes les dépendances comme le fait modprobe à partir des bases de modules installés.

D'où l'intérêt de l'utiliser seulement avec nos modules très simples et de lire les messages en cas d'échec.


50. rmmod : retirer un module

Code: Select all

sudo rmmod mon_module
On fournit ici le nom du module, pas le chemin .ko. Le noyau peut refuser un retrait si le module est occupé.

Un callback de sortie mal conçu peut aussi provoquer un problème lors du retrait.

La fonction de sortie doit libérer seulement les ressources effectivement acquises et encore détenues.


51. modprobe : trouver modules et dépendances

Code: Select all

modprobe --show-depends loop
« modprobe » recherche des modules via la configuration et les index du système, typiquement sous /lib/modules/<release>.

Il est conçu pour prendre en charge les dépendances connues. Un .ko uniquement présent dans ~/kernel-lab/ n'est pas automatiquement indexé comme un module système.

N'installez pas le module expérimental globalement sans nécessité.


52. dmesg : examiner les messages du noyau

Code: Select all

sudo dmesg | tail -n 30
sudo dmesg -w
La première commande montre les trente dernières lignes du tampon de messages noyau. La seconde attend les nouveaux messages en continu : utilisez Ctrl+C pour arrêter.

Un Ubuntu configuré de manière restrictive peut interdire dmesg à un utilisateur non privilégié ; d'où sudo.

Évitez de rendre le journal noyau librement accessible pour résoudre ce seul problème.


53. journalctl -k

Code: Select all

sudo journalctl -k -b --no-pager | tail -n 30
« -k » limite aux messages du noyau ; « -b » limite au démarrage courant. Sur une machine utilisant systemd-journald, cela facilite la lecture des événements.

La disponibilité des anciens journaux dépend des réglages de conservation et de persistance. « dmesg » et « journalctl » ne sont donc pas strictement interchangeables.


54. Commandes de navigation dans les sources

Code: Select all

pwd
ls -la
find . -maxdepth 2 -name '*.c'
grep -R -n 'MODULE_LICENSE' .
  • pwd affiche le répertoire courant.
  • ls -la révèle aussi les fichiers cachés.
  • find parcourt les dossiers répondant au critère.
  • grep -R -n recherche du texte et indique les numéros de ligne.
Évitez « grep -R / » sur toute la machine : vous pouvez tomber sur des fichiers spéciaux de /proc et /sys, ainsi que produire un volume inutile.


55. Identifier un binaire ELF

Code: Select all

file mon_module.ko
readelf -h mon_module.ko
readelf -S mon_module.ko
« file » décrit le type de fichier. « readelf -h » affiche l'en-tête ELF ; « -S » la table des sections. Si readelf n'est pas installé : « sudo apt install binutils ».

Un module Linux reste une forme d'objet ELF, mais sa structure et son mode de chargement ne sont pas ceux d'une application classique.


56. Quand utiliser sudo ?

Préférez « sudo insmod » à un terminal root permanent. « sudo make » est inutile pour compiler dans votre dossier personnel et risque de produire des fichiers appartenant à root.

Exercice rapide

Relevez la version du noyau, trois modules chargés et les métadonnées d'un module. Expliquez la différence entre lsmod et modinfo.


CHAPITRE 8 - Documentation et sources du noyau


57. Pourquoi lire les sources ?

Les API internes du noyau évoluent. Les sources et la documentation de la version cible priment sur un tutoriel ancien. Regardez aussi le contexte autorisé et la durée de vie des objets manipulés.


58. Organisation approximative des sources
  • drivers/ : pilotes ; kernel/ : mécanismes transversaux ; mm/ : mémoire.
  • fs/ : systèmes de fichiers ; net/ : réseau ; arch/ : architectures.
  • include/ : déclarations ; Documentation/ : guides ; scripts/ : outils.
Cette carte simplifiée aide à commencer une recherche dans un arbre très vaste.


59. Téléchargement des sources du noyau

Code: Select all

mkdir -p ~/kernel-lab
cd ~/kernel-lab
git clone --depth 1 https://github.com/torvalds/linux.git linux-mainline
Ce clone récupère une version récente des sources principales, PAS nécessairement les sources exactes du noyau Ubuntu installé. La différence est cruciale.

Utilisez les sources publiques du noyau principal pour explorer des concepts ; pour analyser un bogue spécifique à Ubuntu, vérifiez les sources et correctifs de la version Ubuntu concernée.

Un clone complet peut demander beaucoup de disque ; « --depth 1 » ne conserve qu'un historique minimal.


60. Chercher une définition

Code: Select all

cd ~/kernel-lab/linux-mainline
grep -R -n 'struct file_operations' include/linux | head
Le résultat propose des endroits où apparaît le symbole.

Chercher du texte n'est pas comprendre une définition : vérifiez le bon fichier et le contexte, puis lisez les commentaires et appels associés.

Pour une API, recherchez aussi ses utilisateurs existants. Les exemples du noyau lui-même constituent de bons modèles, sous réserve de versions compatibles.


61. Documentation avec man

Code: Select all

sudo apt install man-db manpages manpages-dev
man 2 open
man 2 ioctl
man 3 printf
La section 2 décrit surtout des interfaces système accessibles aux programmes utilisateurs ; la section 3, des fonctions de bibliothèque.

« man 2 ioctl » ne documente PAS à lui seul le callback unlocked_ioctl d'un module Linux.

La même chaîne de lettres peut apparaître de part et d'autre de la frontière user/kernel, avec des signatures différentes.


62. Documentation officielle en ligne

La documentation versionnée se consulte notamment sur https://docs.kernel.org/ . Pour les modules externes, la section kbuild/modules.html est fondamentale.

Pour vérifier un comportement, sélectionnez si possible la version de documentation correspondant à votre noyau.

Les pages « latest » peuvent décrire des fonctions indisponibles sur Ubuntu 24.04 avec son noyau installé.


63. Documentation générée depuis le code

Les commentaires structurés de certaines fonctions et structures alimentent kernel-doc et Sphinx. La couverture est inégale : lisez aussi les déclarations, le contexte et les appelants.


64. Explorer avec un éditeur et des outils

KDevelop, grep, ripgrep, ctags et cscope permettent de retrouver déclarations et usages. Installer ripgrep : « sudo apt install ripgrep ».

Exemple dans l'arbre des sources : « rg -n 'module_init\(' --glob '*.c' . | head ». Vérifiez ensuite les chemins d'erreur des fonctions trouvées.


65. Un piège fréquent : les exemples datés

Les tutoriels anciens peuvent utiliser des interfaces abandonnées. Le noyau ne promet pas une ABI interne stable aux modules externes.

Vérifiez toujours la release cible, la signature de l'API et les recommandations du sous-système.


66. Exemple : lire une déclaration utile

Code: Select all

#include <linux/module.h>
#include <linux/init.h>
Ces inclusions rendent visibles des déclarations et macros noyau, notamment MODULE_LICENSE, module_init et les annotations d'initialisation.

Inclure un header ne charge pas un module : le chargement intervient lors de l'insertion du .ko par le noyau.


CHAPITRE 9 - Analyse statique des drivers


67. Pourquoi un simple « make » ne suffit-il pas ?

Compiler un .ko ne prouve ni absence de race condition, ni sécurité mémoire. L'analyse statique complète les warnings du compilateur, la relecture et les tests ; elle ne les remplace pas.


68. checkpatch.pl : style et défauts fréquents

Le script du noyau « scripts/checkpatch.pl » signale notamment des problèmes de convention et de nombreux motifs suspects. Dans l'arbre source Linux obtenu au chapitre 8 :

Code: Select all

cd ~/kernel-lab/linux-mainline
./scripts/checkpatch.pl --no-tree --file \
    ~/kernel-lab/hello_module/mon_module.c
La commande est donnée pour le TP à venir : le fichier mon_module.c n'existera pas avant sa création.

« --file » examine le fichier C directement ; « --no-tree » évite l'exigence d'un arbre noyau complet dans certains usages. Lisez les messages un par un.

Un avertissement de style ne signifie pas « faille exploitable » ; une absence d'avertissement ne signifie pas « code correct ».


69. Sparse : vérification supplémentaire des annotations

Sparse analyse du code C du noyau et peut exploiter des annotations comme __user pour distinguer certains espaces d'adressage. Installation :

Code: Select all

sudo apt install sparse
Sur notre module, lorsqu'il existera :

Code: Select all

cd ~/kernel-lab/hello_module
make -C /lib/modules/$(uname -r)/build M="$PWD" C=1 modules
Dans Kbuild, « C=1 » demande l'analyse des fichiers compilés via Sparse selon la configuration ; l'outil sparse doit être disponible.

Pour une réanalyse plus large, C=2 peut être utile dans certains contextes. Les diagnostics peuvent provenir à la fois du compilateur et de Sparse.


70. Exemple d'erreur conceptuelle de mémoire utilisateur

Le code suivant est volontairement FAUX pour un driver :

Code: Select all

/* NE PAS UTILISER : pointeur utilisateur déréférencé directement */
void mauvais_exemple(const char __user *p)
{
    char c = *p;
    (void)c;
}
Pourquoi c'est faux ? L'adresse est fournie par un processus utilisateur.

Elle peut être invalide, non accessible à cet instant, ou provoquer une faute ; un kernel driver doit utiliser les mécanismes appropriés (par exemple copy_from_user dans un contexte autorisé), vérifier les résultats et raisonner sur le temps de vie des données.

Ce code ne sera ni compilé ni inséré dans le TP.


71. Coccinelle et transformations sémantiques

Coccinelle utilise un langage de « semantic patches » (SmPL) pour repérer des formes de code C et parfois aider à les transformer. Installation :

Code: Select all

sudo apt install coccinelle
spatch --version
Coccinelle devient particulièrement utile dans les grosses bases de drivers, par exemple pour trouver des séquences d'appel d'API devant être modernisées.

Ne lancez pas une transformation automatique sur un arbre kernel entier sans versionner les fichiers et lire le patch proposé.


72. Smatch et autres outils

Smatch analyse des chemins de code C, notamment des valeurs et contrôles. Sa disponibilité dépend des paquets. Nous l'approfondirons avec de vrais drivers possédant plusieurs chemins d'erreur.


73. Cas pratique : que faut-il analyser ?

Quand une initialisation acquiert A puis B puis échoue sur C, le retour d'erreur doit libérer B puis A si ces ressources ont été obtenues.

Cherchez les retours ignorés, les validations manquantes et les accès concurrents.


74. Journaux, assertions et preuve

Un message « succès » prouve seulement qu'un chemin a été atteint. Il ne démontre ni l'absence de course critique ni la sécurité mémoire. Combinez analyse, logs, tests et relecture.


CHAPITRE 10 - Débogage et traçage


75. Afficher des messages depuis le noyau

pr_info, pr_warn et pr_err émettent des messages noyau selon un niveau de gravité. Ils ne sont pas des alias de printf et leurs messages peuvent être filtrés ou retardés.


76. Lire un problème plutôt que redémarrer au hasard

En cas d'échec d'insmod, notez d'abord le message d'erreur, puis :

Code: Select all

sudo dmesg | tail -n 60
sudo journalctl -k -b --no-pager | tail -n 60
Exemple : « Invalid module format » peut correspondre à une incompatibilité de format, d'architecture ou de version attendue ; le log précise parfois la cause.

« Unknown symbol » suggère un problème de dépendance ou de résolution de symboles. « Required key not available » renvoie souvent à une politique de signature.

Le diagnostic part des observations réelles, pas d'une solution copiée au hasard.


77. Oops et panic

Un oops peut fournir une pile d'appels et une adresse de faute. Un panic marque un arrêt grave.

La corruption initiale peut précéder le symptôme : il faut lire le contexte, les logs et disposer de symboles adaptés.


78. ftrace : traçage intégré au noyau

ftrace exploite les mécanismes de traçage du kernel. L'interface tracefs est généralement accessible sous /sys/kernel/tracing si le noyau prend en charge la fonctionnalité. Vérifiez :

Code: Select all

ls /sys/kernel/tracing
mount | grep tracefs
Si tracefs n'est pas monté et si le kernel le prend en charge, on peut le monter depuis la VM :

Code: Select all

sudo mount -t tracefs tracefs /sys/kernel/tracing
Cette opération est optionnelle et demande des privilèges élevés.

N'exécutez pas de recettes qui activent « tous les événements » : elles peuvent générer beaucoup de données. Pour commencer, inspectez simplement :

Code: Select all

sudo cat /sys/kernel/tracing/available_tracers
sudo head -n 20 /sys/kernel/tracing/available_events
Pourquoi ces fichiers ? tracefs expose un contrôle runtime et des résultats de traçage ; ce n'est pas un répertoire normal contenant des copies de fichiers.

Lire et écrire ces entrées peut interagir directement avec le comportement du noyau.


79. LTTng : une autre approche du tracing

LTTng fournit une infrastructure de capture et d'analyse d'événements. Il est utile pour des traces longues ou transversales, mais reste facultatif pour notre premier module.


80. Mesurer avant d'optimiser

Avant d'optimiser, mesurez : fréquence, durée, charge et contexte. perf ou ftrace peuvent aider. Une instrumentation très bavarde peut modifier les performances observées.


81. eBPF : observer sans écrire nécessairement un .ko

eBPF permet, sous conditions de sécurité et de support du kernel, de charger de petits programmes vérifiés et attachés à divers points d'observation ou d'action.

C'est une famille de technologies distincte de nos modules C ordinaires. Elle sert notamment à la trace, à l'observabilité et à certains traitements réseau.

Elle ne remplace pas tous les drivers matériels. Comprendre le noyau et ses événements rendra l'apprentissage d'eBPF beaucoup plus facile.


82. Espace d'adressage virtuel

Le noyau et les processus travaillent avec des adresses virtuelles dont les correspondances physiques sont gérées via la MMU et les tables de pages.

Dans un programme utilisateur, /proc/<PID>/maps donne une représentation des régions de mémoire du processus.

Il ne s'agit ni d'un plan complet du noyau ni d'une liste de pages physiques.

Code: Select all

cat /proc/self/maps | head -n 10
« self » désigne le processus qui lit le chemin au moment de l'accès (ici, généralement cat).

Les régions vues varient d'une exécution à l'autre, notamment à cause de l'ASLR.

N'essayez pas de déduire une adresse physique d'une adresse virtuelle en inversant simplement les octets d'un pointeur : l'endianness et la traduction d'adresses sont deux sujets différents.


83. Projets et outils de vérification

Les projets de vérification de drivers, ainsi que KUnit, KASAN ou lockdep selon le noyau et sa configuration, aident à détecter différents défauts.

Vérifiez la maintenance et la compatibilité des outils historiques avant usage.


84. Déboguer avec Hyper-V ou QEMU ?

Hyper-V suffit pour les crashes et les logs de nos modules. QEMU peut faciliter des séances de débogage avec GDB en configuration adaptée. Le choix dépend du test, pas d'un classement général.


85. Une méthode de diagnostic reproductible

Pour reproduire une erreur : notez la release, collectez les logs, isolez le cas minimal, modifiez une chose à la fois et conservez une version fonctionnelle. C'est le début d'un diagnostic rigoureux.


CHAPITRE 11 - Compilation croisée et systèmes embarqués


86. Définition

La compilation croisée produit du code pour une cible différente du système qui compile, par exemple ARM64 depuis x86-64. Il faut une toolchain et des sources/configurations adaptées.


87. Hôte, cible et environnement

Le système de build est la machine qui exécute le compilateur. La cible est le système sur lequel le code devra fonctionner.

Notre TP se fait « nativement » dans une VM x86-64 dont le noyau cible est celui de la même VM. Il n'y a donc aucun besoin immédiat de toolchain croisée.


88. ARM, ARM64 et x86-64

x86-64 et ARM64 ont des registres, conventions et règles matérielles différents. Pour les futurs drivers matériels, surveillez les accès MMIO, le DMA et l'ordonnancement mémoire.


89. QEMU et Raspberry Pi

QEMU permet d'émuler certaines machines. Un Raspberry Pi fournit du matériel réel, utile pour GPIO, I2C ou SPI. Nous gardons ces environnements pour une partie ultérieure.


90. Exemple non nécessaire pour ce TP

Une toolchain ARM64 utilise par exemple aarch64-linux-gnu-gcc. Kbuild peut recevoir ARCH=arm64 et CROSS_COMPILE=aarch64-linux-gnu- ; ne les utilisez pas pour le module x86-64 de notre VM.


91. Quand changer d'environnement ?

Gardez Hyper-V pour les modules logiciels et les IOCTL ; envisagez QEMU pour une émulation ou un débogage ciblés, et une carte réelle lorsque le driver dépend du matériel.


CHAPITRE 12 - TP : premier module noyau en C


92. Objectif du premier module Linux

Nous allons créer mon_module.c, un Makefile Kbuild et compiler mon_module.ko.

Ce module logiciel ne crée aucun /dev, ne lit aucun fichier et ne touche à aucun périphérique.

À l'insertion, il écrit un message dans les logs ; au retrait, il écrit un second message.

C'est l'équivalent pédagogique le plus simple d'un cycle « initialisation / nettoyage », sans reproduire les objets WDM.

Avant le TP : créez un point de contrôle Hyper-V et vérifiez le noyau actif.

Code: Select all

uname -r
uname -m
ls -ld /lib/modules/$(uname -r)/build
Si la dernière commande échoue, revenez au chapitre 4 : ne continuez pas en remplaçant la version par une valeur inventée.


93. Créer un répertoire de travail

Code: Select all

mkdir -p ~/kernel-lab/hello_module
cd ~/kernel-lab/hello_module
pwd
L'option « -p » crée les parents nécessaires et ne signale pas d'erreur si le répertoire existe déjà.

« ~ » est le dossier personnel de l'utilisateur courant ; les fichiers produits seront donc éditables sans sudo.


94. Code source de mon_module.c

Créez le fichier « mon_module.c » dans ce répertoire et copiez exactement ce code :

Code: Select all

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/init.h>
#include <linux/printk.h>

static int __init mon_module_init(void)
{
    pr_info("Module chargé : bonjour depuis le noyau !\n");
    return 0;
}

static void __exit mon_module_exit(void)
{
    pr_info("Module déchargé : au revoir !\n");
}

module_init(mon_module_init);
module_exit(mon_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cours Linux Kernel");
MODULE_DESCRIPTION("Module logiciel de demonstration");

95. Explication ligne par ligne
  • #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt : définit un préfixe de log dérivé du nom du module, utile pour rechercher ses messages. Cette macro doit être définie avant l'inclusion qui fournit pr_info.
  • #include <linux/module.h> : macros et déclarations relatives aux modules et métadonnées.
  • #include <linux/init.h> : annotations d'initialisation et de sortie.
  • #include <linux/printk.h> : interfaces de messages noyau, dont pr_info.
  • static : limite la visibilité du symbole de fonction à ce fichier source ; il ne s'agit pas d'une exportation vers les autres modules.
  • int : le callback d'initialisation retourne un entier. 0 indique le succès ; une valeur négative correspondant à une erreur signifie l'échec de l'initialisation.
  • __init : annotation utilisée pour les fonctions d'initialisation ; selon le contexte de build, le code d'initialisation peut être placé dans une section particulière et sa mémoire libérée après démarrage réussi.
  • pr_info : produit un message de niveau information dans les logs noyau. Le « \n » termine la ligne.
  • __exit : annotation utilisée pour le chemin de sortie, notamment lors d'un retrait de module chargeable.
  • module_init / module_exit : enregistrent les fonctions qui servent de points d'entrée pour ce module. Ce ne sont pas des appels qui exécutent aussitôt les fonctions pendant la compilation.
  • MODULE_LICENSE("GPL") : déclare une licence correspondant au module ; ne confondez pas cette chaîne avec un contrôle juridique automatique du droit d'auteur. Ne déclarez pas faussement la licence d'un code repris d'ailleurs.
  • MODULE_AUTHOR, MODULE_DESCRIPTION : métadonnées consultables avec modinfo.

96. Pourquoi ne pas utiliser DriverEntry ?

Sous Windows WDM, DriverEntry reçoit notamment un PDRIVER_OBJECT et un chemin de registre. Ici mon_module_init(void) ne reçoit aucun objet équivalent.

Le chargeur de modules Linux et les infrastructures utilisées par chaque sous-système ont leurs propres mécanismes.

Il n'existe pas de « DRIVER_OBJECT Linux universel » à initialiser comme sous WDM.


97. Écrire le Makefile du module

Créez un fichier nommé exactement « Makefile » (M majuscule) à côté de mon_module.c :

Code: Select all

obj-m += mon_module.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD  := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
Conservez les TABULATIONS avant $(MAKE).

Ici obj-m est pris en compte par Kbuild au moment de la construction du module ; les cibles all/clean constituent l'interface pratique appelée depuis le terminal.

La substitution de commande $(shell uname -r) est celle de make, non du shell.


98. Compiler, sans sudo

Code: Select all

cd ~/kernel-lab/hello_module
make
La sortie de make doit inclure des étapes de compilation et de liaison propres à la version du noyau. Il est normal de voir des noms comme CC [M], MODPOST ou LD [M].

Une compilation réussie se termine avec le fichier mon_module.ko. Le nombre exact de lignes varie selon la version et les options de Kbuild.

Code: Select all

ls -lh mon_module.ko
file mon_module.ko
modinfo ./mon_module.ko
Vérifiez la présence de « license: GPL » et d'un champ « vermagic » cohérent. Ce contrôle doit être fait AVANT de charger le code dans le noyau.


99. Charger le module

Ouvrez deux terminaux. Dans le premier :

Code: Select all

sudo dmesg -w
Dans le second, depuis le répertoire du module :

Code: Select all

sudo insmod ./mon_module.ko
Le premier terminal doit recevoir un message proche de « mon_module: Module chargé : bonjour depuis le noyau ! ».

Le format précis du préfixe, de l'horodatage et de la priorité peut changer. « insmod » ne renvoie pas nécessairement un texte en cas de succès.

Une commande silencieuse ne dispense pas de vérifier lsmod et les logs.


100. Vérifier la présence dans le noyau

Code: Select all

lsmod | grep '^mon_module'
modinfo ./mon_module.ko
Pourquoi deux commandes ? lsmod renseigne sur ce qui est chargé dans le noyau en cours ; modinfo sur les informations du fichier .ko.

Un fichier peut exister sans être chargé. À l'inverse, un module chargé peut ne plus correspondre au fichier local si vous l'avez recompilé après insertion.


101. Retirer le module

Code: Select all

sudo rmmod mon_module
sudo dmesg | tail -n 20
Vous devriez retrouver le message « Module déchargé : au revoir ! ». Contrôlez ensuite :

Code: Select all

lsmod | grep '^mon_module' || echo "Module absent"
La partie « || » demande au shell d'exécuter echo si grep retourne un état d'échec (absence de ligne).

Ce résultat signifie seulement que le nom recherché n'apparaît pas dans cette sortie, ce qui est conforme à notre exercice.


102. Nettoyer le répertoire

Code: Select all

make clean
ls -la
Les fichiers source et Makefile doivent rester. Les objets et fichiers intermédiaires devraient être supprimés par la cible clean.

Gardez le répertoire pour les exercices suivants ; il représentera votre première base de code Linux Kernel.


103. Dépannage des erreurs classiques

Erreur « No such file or directory » pour .../build : headers absents ou différents du noyau démarré.

Revérifiez uname -r et les dépôts ; ne créez pas un lien symbolique arbitraire vers des headers incompatibles.

Erreur « missing separator » dans Makefile : une ligne de recette contient probablement des espaces là où make attend une tabulation. Activez l'affichage des caractères invisibles dans l'éditeur.

Erreur « Invalid module format » : vérifiez les logs dmesg, l'architecture et le champ vermagic ; recompilez contre le bon noyau.

Erreur « Required key not available » : examinez la politique de signature du noyau et l'état Secure Boot.

Sur Ubuntu, vous pouvez consulter « mokutil --sb-state » après installation de mokutil. Des modules non signés ne sont pas forcément acceptés, même s'ils compilent.

Préférez une signature correcte ; à défaut, dans une VM de test isolée uniquement, vous pouvez modifier sa configuration Secure Boot en comprenant le compromis de sécurité.

Erreur « Operation not permitted » : privilèges insuffisants ou restrictions de sécurité. Vérifiez le contexte avant de modifier la configuration du noyau.

Erreur « Module in use » : le noyau refuse le retrait pendant qu'il reste des utilisateurs ou références. Ne forcez pas arbitrairement le retrait d'un driver réel.


104. Exercices progressifs

Exercice A : changez uniquement le message de chargement, recompilez, réinsérez puis observez la différence. Pensez à retirer le module avant de le recharger.

Exercice B : faites retourner -EINVAL depuis mon_module_init après avoir ajouté « #include <linux/errno.h> ».

Préparez un point de contrôle, recompilez puis testez insmod : il doit échouer avec une erreur correspondant à l'argument invalide, et le module ne doit pas rester chargé.

Rétablissez ensuite le retour 0. Ne faites ce test qu'avec ce module volontairement minimal, sans ressources acquises.

Exercice C : notez les différences entre le fichier local .ko, les métadonnées modinfo et la liste lsmod.

Expliquez pourquoi « compiler », « charger » et « exécuter le code d'initialisation » désignent trois événements distincts.


105. Le prochain driver : /dev/mon_driver

Dans la Partie 2, le module deviendra un character device driver.

Il devra enregistrer un périphérique, exposer des opérations via struct file_operations et interagir avec un programme utilisateur.

Le projet final reprendra deux commandes : incrémenter un compteur et lire sa valeur, comme dans l'ancien exercice WDM avec IOCTL_INCREMENT et IOCTL_GET_COUNT.

Nous expliquerons alors précisément la construction des numéros de commande (_IO, _IOR, _IOW, _IOWR), l'usage de copy_to_user/copy_from_user, les validations, les références et la synchronisation.

Rien de cela n'est nécessaire pour le simple « hello module ».


CHAPITRE 13 - Exercices et questions pièges


106. Les dix connaissances à conserver
  • 1) Ubuntu est une distribution ; Linux est le noyau.
  • 2) Un driver peut être compilé dans le noyau ou en module.
  • 3) Un module peut planter la VM : point de contrôle avant insertion.
  • 4) uname -r décrit le noyau en cours d'exécution.
  • 5) Les headers doivent correspondre au noyau cible.
  • 6) Kbuild est l'outil adapté aux modules externes.
  • 7) .o et .ko ne sont pas synonymes d'exécutable utilisateur.
  • 8) lsmod et modinfo répondent à des questions différentes.
  • 9) Un code qui compile peut contenir des défauts de sécurité graves.
  • 10) Les API internes du noyau ne possèdent pas d'ABI stable pour les modules externes.

107. Questions de compréhension
  • Q1. Pourquoi faut-il redémarrer après une mise à jour du noyau avant de construire un module contre la nouvelle release ?
  • Q2. Pourquoi l'IDE KDevelop ne suffit-il pas à déterminer les bonnes options du compilateur pour un module ?
  • Q3. Quelle est la différence entre insmod et modprobe ?
  • Q4. Quelles commandes montrent la release active et les métadonnées d'un module ?
  • Q5. Pourquoi la lecture de « man 2 ioctl » ne suffit-elle pas à écrire la fonction ioctl d'un driver ?

108. Questions pièges
  • PIÈGE 1 : « mon_module.ko existe donc il fonctionne. » Faux : la construction peut réussir, mais le chargement ou l'exécution échouer.
  • PIÈGE 2 : « Linux 6.x garantit la compatibilité de mes modules externes. » Faux : l'ABI interne n'est pas stable.
  • PIÈGE 3 : « Tout driver Linux reçoit des IRP. » Faux : les IRP appartiennent au modèle Windows WDM.
  • PIÈGE 4 : « Le code noyau peut utiliser printf sans problème. » Faux : printf appartient à la libc utilisateur.
  • PIÈGE 5 : « Un module GPL ne peut jamais marquer le noyau comme tainted. » Faux : d'autres causes de taint existent, dont certains modules externes.
  • PIÈGE 6 : « Un checkpoint remplace mes sauvegardes. » Faux : il protège un état de VM, pas toutes les données externes.

109. Réponses commentées aux questions
  • R1. uname -r ne change qu'après démarrage d'un nouveau noyau ; les headers doivent suivre le noyau ciblé.
  • R2. Les fichiers générés, macros et options de compilation dépendent de la configuration du kernel et de Kbuild ; l'indexation de l'IDE est auxiliaire.
  • R3. insmod charge le fichier fourni ; modprobe s'appuie sur les bases et dépendances de modules installés.
  • R4. uname -r et modinfo ; lsmod vérifie séparément l'état chargé.
  • R5. La documentation du syscall utilisateur ne décrit pas l'ensemble de la structure ni des contraintes de callback d'un driver.

110. Avant de passer à la Partie 2

Avant la Partie 2, vérifiez que vous savez créer et restaurer une VM, identifier la release, installer les headers, comprendre le Makefile, compiler et examiner un .ko, le charger, lire ses messages et le retirer.

Comprendre un échec est plus important que mémoriser une commande.


Résumé

Pour développer un module Linux, il faut disposer d'une VM Ubuntu, du compilateur GCC, de Make, et des en-têtes correspondant au noyau exécuté.

KDevelop sert à écrire le code, mais la compilation d'un module doit être effectuée avec Kbuild.

Un module doit être compilé, inspecté, chargé et retiré en vérifiant les messages du noyau.

Code: Select all

uname -r
make
modinfo ./mon_module.ko
sudo insmod ./mon_module.ko
lsmod | grep mon_module
sudo dmesg | tail -n 20
sudo rmmod mon_module
La suite abordera les character device drivers, la création de /dev/mon_driver, struct file_operations, les IOCTL, les transferts entre user mode et kernel mode et la synchronisation.

Documentation à consulter

Return to “Kernel Linux”

Who is online

Users browsing this forum: No registered users and 1 guest