Deprecated: Assigning the return value of new by reference is deprecated in /srv/www/u-classroom.net/wiki/inc/parserutils.php on line 208
Deprecated: Assigning the return value of new by reference is deprecated in /srv/www/u-classroom.net/wiki/inc/parserutils.php on line 211
Deprecated: Assigning the return value of new by reference is deprecated in /srv/www/u-classroom.net/wiki/inc/parserutils.php on line 421
Deprecated: Assigning the return value of new by reference is deprecated in /srv/www/u-classroom.net/wiki/inc/parserutils.php on line 594
Deprecated: Function split() is deprecated in /srv/www/u-classroom.net/wiki/inc/auth.php on line 154
Warning: Cannot modify header information - headers already sent by (output started at /srv/www/u-classroom.net/wiki/inc/parserutils.php:208) in /srv/www/u-classroom.net/wiki/inc/auth.php on line 245
Warning: Cannot modify header information - headers already sent by (output started at /srv/www/u-classroom.net/wiki/inc/parserutils.php:208) in /srv/www/u-classroom.net/wiki/inc/actions.php on line 370
Warning: Cannot modify header information - headers already sent by (output started at /srv/www/u-classroom.net/wiki/inc/parserutils.php:208) in /srv/www/u-classroom.net/wiki/inc/actions.php on line 374
====== Gestion des LKM (loadable kernel module) ======
Ce cours s'adresse à des débutants //confirmés//, c'est-à-dire que pour suivre cette session, il n'est pas besoin d'avoir un niveau exceptionnel. Il suffit simplement de savoir se servir un minimum d'un terminal.
Ce cours a été présenté par illovae lors d'une session sur le canal francophone d'u-classroom.net. Il est sous licence [[http://creativecommons.org/licenses/by-nc-sa/2.0/legalcode|CC-by-nc-sa]] ce qui signifie que vous pouvez le copier, modifier, distribuer à volonté pourvu que vous n'en fassiez pas un usage commercial et que vous citiez l'auteur original en ajoutant un lien vers le cours original sur u-classroom.net. De plus tout ou partie de votre production reprenant ce cours devra elle aussi respectée la même licence que ce présent document. Pour plus d'informations, veuillez s'il vous plaît vous reporter au texte de la licence.
===== 0. Introduction =====
Comme vous le savez tous, le noyau Linux est un noyau monolithique. Dans les temps anciens, quand on avait besoin d'ajouter la prise en charge d'un système de fichier ou encore un nouveau périphérique, nous n'avions d'autres choix que de patcher le noyau, de le recompiler et de rebooter. Ce qui n'est évidemment pas très pratique.
Très vite donc, les LKMs (Loadable Kernel Module) - qu'on appelle plus simplement kernel modules ou même modules - ont été créés (dès 1995). Le but étant qu'on puisse charger et décharger ces modules afin de libérer de la mémoire des choses dont on ne se sert qu'occasionnellement. Notons que ce fonctionnement n'est utile que dans certains cas. Il serait bête d'avoir le support d'ext3 en module si vous l'utilisez tous les jours évidemment.
Les modules sont essentiels à comprendre et à maîtriser pour qui s'intéresse à la façon dont fonctionne l'OS. En effet, c'est au travers d'eux que vous pourrez finement configurer votre machine et en retirer le comportement que vous désirez.
N'allez pas imaginer qu'un LKM est quelque chose qui discute avec le kernel en dehors de celui-ci. Un LKM est une partie intégrante du noyau (quand il est chargé). Cependant on en fait la distinction du //noyau central// en parlant de "base kernel" : id est tout ce qui correspond à ce qui est lancé par l'image lors du boot. Ainsi on peut se représenter les LKMs comme une "extension" du noyau.
Au delà du simple intérêt de décharger de la mémoire des choses dont on a pas besoin, les LKMs sont recommandés pour plusieurs raisons :
- L'utilisation des modules le plus souvent possible nous évite de devoir recompilé tout le kernel. Quand on a un base kernel qui fonctionne correctement et qui boot, il est dommage de perdre du temps à le refaire simplement pour avoir sa webcam qui marche.
- Un autre avantage est de pouvoir circonscrire des problèmes liés à un périphérique. Un bug dans un driver qui serait implémenté dans le base kernel peut casser votre boot et votre système ne démarre plus. Par contre, l'avoir comme module, non seulement évite ce désagrément car vous aurez toujours accès au système, mais rend plus facile la recherche du problème.
- D'un point de vue du développeur, c'est un gain de temps très appréciable, étant donné qu'on n'est pas obligé de recompiler le kernel et d'attendre un reboot simplement pour tester l'ajout d'une fonction ou le changement d'un paramètre.
Je veux quand même revenir une seconde sur le côté charge mémoire du noyau. Si vous ne le savez pas la mémoire utilisé par le noyau n'est *jamais* swappée. Ce qui signifie que si vous avez un noyau avec des tas de choses inutiles en BUILT-IN et non en modules, ça va prendre de la place inutilement.
Enfin une dernière chose. Certains ont tendance à croire qu'un LKM est plus lent q'un module implémenté dans le base kernel. L'allocation en mémoire se fait un peu différemment certes mais les performances n'en sont pas amoindries.
===== 1. Gestion pratique des modules =====
Les modules sous GNU/Linux se trouvent dans ''/lib/modules'' et ont donc l'extension ''.ko'' (pour kernel object). Vous noterez que chaque version de kernel à son propre jeu de modules.
Les outils pour la gestion des modules se trouvent dans le paquet ''module-init-tools'' ; qui fait partie des paquets de base de tous systèmes GNU/Linux évidemment. Nous allons les passer en revue.
==== 1.1. lsmod ====
lsmod est un tout petit programme qui ne sert simplement qu'à lister les modules actuellement chargés. Il n'a d'autres fonctions que "d'arranger joliement" ce qui se trouve dans ''/proc/modules''.
Il n'y a rien de compliqué dans la sortie : respectivement, le nom du module, la taille en mémoire, le nombre d'utilisation et enfin quel(s) autre(s) module(s) est/sont en train de l'utiliser.
Cela nous permet de voir qu'un module peut donc avoir des dépendances. Un exemple simple et compréhensible : pour lancer les filtres iptable (ip6table_filter) il faut évidemment que iptables lui-même soit lancé (ip6_tables).
==== 1.2. depmod ====
À propos de ces dépendances, depmod est l'outils qui les résoud. Il scan simplement tous les modules présents dans ''/lib/modules/'' et créé un fichier modules.dep dans ce même dossier avec ''nom_du_module.ko: dependances1.ko dependances2.ko'' etc.
Pour voir à quoi ça ressemble vous pouvez faire un :
$ sudo less /lib/modules//modules.dep
Bon en pratique on utilise presque //jamais// depmod, sauf quand on s'amuse à tester/rajouter tout un tas de modules et leurs dépendances...
==== 1.3. modinfo ====
Un autre outil très simple mais très pratique est modinfo qui nous permet d'avoir des informations sur un module en particulier. Par exemple, vous pouvez faire un sudo modinfo snd (ou tout autre module qui vous chante bien sûr). Nous verrons plus tard où ce trouve ces informations au niveau du module lui-même ;)
Notez que la sortie peut être allégée en ajoutant l'option ''-F '' sachant que les paramètre peuvent être author, description, license, param, depends, et alias (dixit la page de manuel). On peut donc faire par exemple :
$ sudo modinfo -F author snd
==== 1.4. insmod ====
Ceci est l'outils qui permet de charger un module. Il est très peu utilisé car on lui préfère modprobe qui a une gestion plus "intelligente" des modules. Nous allons y revenir.
==== 1.5. rmmod ====
Comme vous le savez (ou comme vous vous en doutez) rmmod sert à décharger un module. Il est encore beaucoup utilisé même si on lui préfère l'option ''-r'' de modprobe.
==== 1.6. modprobe ====
Maintenant le gros morceau.
modprobe est l'outils de prédilection pour charger et décharger des modules. C'est un peu le couteau suisse des modules. Avec lui insmod et rmmod deviennent carrément useless.
Son principal atout est de gérer comme un grand les dépendances... qui peuvent être diablement complexe parfois. On peut en voir un aperçu avec les modules pour gérer le son :
$ lsmod | grep snd
Il utilise pour ce faire le fichier modules.dep cité plus haut ce qui implique qu'il soit à jour.
modprobe permet aussi par exemple de lister les dépendances d'un module particulier grâce à l'option ''--show-depends''. Par exemple, on peut voir les dépendances directes du modules snd :
$ sudo modprobe --show-depends snd
Et aussi les dépendances de ces dépendances :
$ sudo modprobe --show-depends snd-pcm-oss
Notons aussi que modprobe a une option ''-l'' qui permet de faire une recherche de module existant. Mais la page de manuel indique que find et basename sont des outils certainement plus efficient pour faire une recherche correcte ;) Cependant pour des recherches simples, ça suffit largement (et c'est d'autant plus rapide qu'elle utilise modules.dep pour faire sa recherche).
=== 1.6.1. Configuration ===
Le second très gros avantage de modprobe est sa configuration. Historiquement, il a un fichier de configuration ''/etc/modprobe.conf'' mais qui est considéré comme deprecated. En effet, on lui préfère le dossier ''/etc/modprobe.d/'' qui permet une organisation plus fine. On retrouve dans ce dossier les aliases, les blacklists, etc. Normalement ce dossier est plutôt bien fourni.
Il est important de comprendre que modprobe est un élément essentiel du système au delà de l'utilisation que l'end user peut lui-même en faire. Je m'explique :
$ cat /proc/sys/kernel/modprobe
On voit ici que le kernel est renseigné sur le PATH de modprobe. En effet, lors de son lancement le kernel via udev va lister les périphériques branchés et va utilisé modprobe pour lancer les modules appropriés lors du boot. Ainsi seul les LKMs nécessaires au bon fonctionnement seront lancés via modprobe.
Cependant udev a une gestion dynamique des noeuds de périphériques. Ainsi, dans le cas d'un périphérique parfaitement reconnu, quand il n'est pas branché lors du boot, le module n'est pas chargé, épargnant la mémoire.
MAIS quand vous allez brancher votre webcam, udev va la repérer et - selon des rêgles prédéfinie - va lancer modprobe qui d'une part vérifiera si un module existe pour ce périphérique, puis vérifiera qu'il n'est pas déjà lancé auquel cas il le lancera avec sa tripotée de dépendances. Et cela de façon parfaitement transparente grâce à udev.
Comme sous-entendu ci-dessus, la configuration de modprobe accepte un jeu de commandes assez utile :
== 1.6.1.1. aliases ==
Tout d'abord les alias : ils sont simplement utilisé afin de faciliter l'appel de module. Généralement, c'est l'équipe de module-init-tools qui s'occupe de ça. Vous pouvez voir la liste par défaut :
$ less /etc/modprobe.d/aliases
== 1.6.1.2. blacklist ==
Celui-là non plus n'est pas difficile à comprendre. Il arrive parfois que deux modules supportent un certain type de périphérique, ou bien encore qu'un module prétend pouvoir prendre un charge ce périphérique alors que ce n'est pas le cas. On utilisera donc blacklist pour contrarier le chargement automatique de ce module.
Par exemple, le module ''de4x5'' prend en charge certains types de carte réseau. Mais il a été supplanté par le module ''tulip''. On retrouve donc dans ''/etc/modprobe.d/blacklist'' une entrée ''blacklist de4x5''.
== 1.6.1.3. install ==
install est très puissant et très utiliser car il permet de lancer des commandes. Je vais prendre un exemple concret. Mon laptop a plusieurs détecteurs de mouvement et possède une technologie qui, lorsque celui tombe, rabat le bras du disque dur pour éviter tout dégât. Le module qui permet de gérer ceci est hdaps.
Il se trouve que j'ai aussi besoin du module tp_smapi qui lui permet //principalement// de gérer ma batterie. Cependant, il implémente aussi certaines fonctions déjà remplies par hdaps (mais pas toute !).
L'idée est donc que je dois lancer d'abord tp_smapi pour profiter du support de ma batterie **puis** hdaps pour profiter de l'active protection system pour mon disque dur.
Ici il n'y a pas de relation de dépendances entre les deux. Si je modprobe tp_smapi, il ne me lancera pas hdaps. Je pourrais simplement donc mettre un modprobe tp_smapi puis un modprobe hdaps dans un fichier au sein de ''/etc/modprobe.d/''.
Mais voilà, chez moi c'est udev qui lance hdaps. Mais il faut pourtant que tp_smapi soit lancé avant.
Donc, pour être sûr et certain que les modules se lancent dans l'ordre voulu, j'ai quelque chose de ce genre dans ''/etc/modprobe.d/tp_smapi.conf''.
install hdaps /sbin/modprobe -i tp_smapi ; /sbin/modprobe -i hdaps
Ce qui a pour résultat que lorsqu'udev - au moment du boot - détecte mon accelerometre, il appelle modprobe pour lancer hdaps. Avec cette configuration modprobe trouve cette ligne et exécute la commande indiquée : il me lance d'abord tp_smapi **puis** hdaps.
(Notez que l'option ''-i'' sert simplement à empêcher de relancer à nouveau la commande install).
== 1.6.1.4. remove ==
La commande remove sert à faire exactement l'inverse de install. Je peux donc avoir une ligne de ce genre :
remove hdaps /sbin/modprobe -r hdaps ; /sbin/modprobe -r tp_smapi
Quand je décharge hdaps, il me décharge aussi tp_smapi.
== 1.6.1.5. options ==
Comme le montre modinfo, certains modules acceptent des options. Je vais à nouveau prendre mon cas personnel pour vous expliquer cela. J'utilise le module thinkpad_acpi qui me permet d'avoir accès aux senseurs thermaux, aux touches de fonctions et autres. Par défaut le ventilateur est complètement gérer par le kernel. Cependant, en fonction des situations, je veux pouvoir rêgler moi-même la vitesse du ventilo.
J'ai donc été faire un tour sur la documentation du module en question et j'ai trouvé qu'il faut simplement ajouter le paramètre ''fan_control=1'' lors du chargement du module.
Ce qui donne un fichier ''/etc/modprobe.d/thinkpad_acpi.conf'' avec dedans :
options thinkpad_acpi fan_control=1
==== 1.7. Autres ? ====
Nous avons vu ici les principaux outils permettant de gérer les modules, mais il en existe d'autres. Je crois que les deux dont je vais parler ne sont spécifique qu'aux debian-like. Je n'ai jamais utilisé ni l'un ni l'autre pour ma part, je les présente, histoire que vous en aillez entendu parler si ce n'est pas déjà le cas.
Il existe modconf qui permet de façon graphique de spécifier quels modules vont être chargés au démarrage. Mais je crois qu'il n'est pratiquement plus utilisé, étant donné l'automatisation du processus (notamment grâce à udev).
Il existe aussi un autre bien plus connu et qui se nomme module-assistant (ou m-a). Ce dernier suivant un ensemble de règles va simplement récupérer les sources (vanilla ou debian) d'un module ainsi que tout ce qu'il faut pour la compilation de celui-ci.
Puis il va construire le paquet - en compilant donc - et l'installer (tout cela en fonction du noyau). Je sais que c'est pas mal utilisé pour intaller des drivers Nvidia par exemple.
===== 2. Autopsie et compilation/installation d'un module =====
Dans cette partie nous allons voir en gros comment est construit un module. C'est-à-dire que nous allons regarder un fichier source très basique d'un module Hello World. Ce module ne fera rien d'autres que d'afficher un petit message dans les logs lors de son lancement.
C'est écrit en C, mais n'ayez crainte, c'est on ne peut plus simple, même pas besoin des bases pour comprendre ce qu'il se passe. L'intérêt est que cela va nous permettre de comprendre de façon clair qu'un module est bien un 'bout' de kernel et non quelque chose en dehors comme cela a déjà été dit.
Avant cela, il va nous falloir quelques petites choses. Notamment les sources de notre kernel ainsi que ce qu'il faut pour compiler.
[SOUS DEBIAN: ''sudo apt-get install linux-headers-$(uname -r) build-essential'']
Je vous ai donc préparé une petite archive avec les sources de ce module et un Makefile :
$ wget [[http://u-classroom.net/files/2010-07-03/hello.tar|http://u-classroom.net/files/2010-07-03/hello.tar]]
Pendant que tout ceci ce télécharge, vous pouvez déjà remarquer que vous avez une bonne flanquée de modules déjà présent sur votre système :
$ find /lib/modules/ -iname '*.ko' | wc -l
En parcourant ''/lib/modules'', vous verrez que tout est bien rangé, par catégories.
Maintenant que vous avez tout récupérer, placez-vous dans le dossier où vous avez télécharger hello.tar et faites :
$ tar xvf hello.tar
L'idée ici est que nous avons un matériel fictif non reconnu par le système par défaut. Nous venons de récupérer les sources pour ce matériel, nous allons dans un premier temps regarder comment les sources sont faites, puis compiler et loader ce module.
Dans le dossier ''hello/'' il y a donc un fichier .c et un Makefile.
$ less hello.c
Dans ce fichier, il y a 4 parties mais deux seulement nous intéresse vraiment. Pour ceux qui n'ont jamais fait de C, les include en haut ça sert juste à faire appel à des fonctions déjà écrites (histoire que nous n'ayons pas à réinventer la roue). Ensuite nous avons des MODULE_LICENSE et autres (souvenez-vous de modinfo ;)). Elles ne sont pas obligatoire en tant que telle, mais très fortement conseillées, surtout en ce qui concerne la license.
Ensuite, un module est constitué de deux choses essentielles et obligatoire a minima : une fonction "start" et une fonction "end".
static int __init init_hellow(void)
Ceci est la fonction "start" et la fonction "end" est donc :
static void __exit cleanup_hellow(void)
Après chacune de ces fonctions vous voyez des {}, à l'intérieur nous avons notre code, qui est là tout simple et fait simplement un appel à printk (ce qui signifie affiche tel message dans les logs). Le printk est une commande qui est déjà écrite et qui se trouve dans ''linux/kernel.h'' (iirc).
Ce que va faire ce module est donc très simple : quand on va le lancer, il va nous mettre une ligne dans les logs ; et quand on va le décharger il va en mettre une autre. Le développeur peut bien sûr ajouter d'autres fonctions qui lui semblent utiles, voir même séparer le fichier en plusieurs histoire que ça soit clair. Mais ces deux fonctions sont juste essentielles.
Normalement il faut savoir qu'un programme en C doit conteniur une fonction main() (la fonction principale) et vous remarquez qu'ici il n'y en a pas. C'est normal car le module est une partie intégrante du kernel donc il suffit juste de faire appel aux deux fonctions (init et exit) et ça suffit.
Nous allons donc pouvoir passer à la compilation. Dans le dossier des sources :
$ make
Vous devriez avoir un résultat de ce genre :
make -C /lib/modules/2.6.29.6-smp/build M=/home/illovae/q/hello modules
make[1]: Entering directory `/usr/src/linux-2.6.29.6'
CC [M] /home/illovae/q/hello/hello.o Building modules, stage 2. MODPOST 1 modules
CC /home/illovae/q/hello/hello.mod.o
LD [M] /home/illovae/q/hello/hello.ko
make[1]: Leaving directory `/usr/src/linux-2.6.29.6'
Vous vous retrouvez après ça avec un ''hello.ko''. D'ores et déjà vous pouvez faire un :
$ modinfo hello.ko
Ensuite on va simplement charger le module depuis où on est, mais avant ça, ouvrez un autre terminal et affichez les logs systèmes :
$ sudo tail -f /var/log/messages /var/log/syslog /var/log/kern.log
(Je précise ici plusieurs fichiers, parce que selon les distributions, les logs kernels ne s'affichent pas aux mêmes endroits...)
Enfin :
$ sudo insmod hello.ko
Et normalement vous voyez dans vos logs un :
Jul 3 17:11:11 EVA01 kernel: I am putting myself to the fullest possible use.
Nous avons utilisé ici insmod parce que c'est le plus simple, mais vous pouvez le faire avec modprobe, cependant pour ça, il faut installer à la main le module.
$ sudo mkdir /lib/modules//extra/
$ sudo cp hello.ko /lib/modules//extra/
$ sudo depmod
$ sudo modprobe hello
Vous pouvez ensuite décharger le module :
$ sudo rmmod hello
Et voir à nouveau une ligne dans vos logs précisant que le module est bien déchargé.
La procédure pour installer les modules est très souvent la même. Il faut compiler et installer. Lisez le fichier ''README'' fourni avec les sources de vos modules pour savoir comment vous y prendre exactement, mais sachez que c'est toujours à base de make && make install. Parfois, vous aurez même des sources avec un fichier.sh qui automatisera le tout (c'est à dire, savoir si vous avez les bonnes dépendances pour compiler, qui compilera, et installera lui-même les modules).
Pour résumer, en cas de problème de reconnaissance de matériel :
- blacklister les modules qui posent problème
- les renmmer / supprimer, s'ils continuent à se charger malgré avoir été blacklistés (ce cas de figure arrive très rarement et est souvent le fruit d'un bug indépendant de votre volonté)
- télécharger le source du module
- compiler/installer ou lancer le script d'installation s'il est fournis avec les sources
- lancer depmod et charger avec modprobe
====== Références ======
* manpages
* http://tldp.org/HOWTO/Module-HOWTO/index.html
* http://tldp.org/LDP/lkmpg/2.6/html/index.html
* http://www.faqs.org/docs/Linux-HOWTO/Kernel-HOWTO.html
* http://www.linuxforums.org/articles/introducing-lkm-programming-part-i_110.html