Intéressé par des cours d'informatique en ligne ?
Visitez mon nouveau site https://www.yesik.it !

Le noyau Linux est un noyau monolithique modulaire. Concrètement, cela veut dire qu'il est possible de dynamiquement y charger des modules logiciels pour en étendre les fonctionnalités. Écrire un nouveau module est donc une des solutions à envisager quand on veut adapter le système d'exploitation aux spécificités matérielles d'une plate-forme embarquée. Sauf que sur un système embarqué, les ressources sont souvent limités – ce qui rend nécessaire la compilation croisée. Comme il s'agit d'une plate-forme populaire et bon marché, voici donc comment procéder à la compilation croisée d'un module Linux pour Rasberry Pi.

Toolchain

Avant d'aller plus loin, vous devez disposer sur votre machine hôte (celle qui sert à compiler) de l'ensemble des outils nécessaires à la compilation pour votre cible. C'est ce qu'on appelle la toolchain. Si ce n'est déjà fait, je vous renvoie sur l'article compagnon de celui-ci: Compilation croisée facile pour Raspberry Pi.

Cross-compilation module pour RPi

Récupérer les sources du noyau

Pour pouvoir compiler un module pour le noyau Linux, vous devez ensuite disposer des sources adéquates. Il s'agit bien ici des sources du noyau de la cible. Et pas celles de l'hôte. Dans mon cas, ma carte Raspberry Pi tourne sous Raspbian (basé sur le noyau Linux 3.2.27). J'ai donc téléchargé l'archive correspondante à partir du dépôt GitHub adéquat:

sylvain@arm-builder:~$ mkdir ${HOME}/kernel
sylvain@arm-builder:~$ cd ${HOME}/kernel
sylvain@arm-builder:~/kernel wget https://github.com/raspberrypi/linux/archive/rpi-3.2.27.tar.gz -O - | tar xzf -

En plus des sources à proprement parler, vous avez besoin de la configuration du noyau. Celle-ci peut être directement récupérée dans le pseudo-système de fichiers /proc de la cible:

sylvain@arm-builder:~/kernel$ cd linux-rpi-3.2.27
sylvain@arm-builder:~/kernel/linux-rpi-3.2.27$ ssh pi@10.129.36.203 cat /proc/config.gz | gunzip - > linux-rpi-3.2.27/.config

Mais les sources et la configuration ne suffisent pas. Il faut aussi compiler le noyau. Ce n'est pas compliqué; juste long…

sylvain@arm-builder:~/kernel/linux-rpi-3.2.27$ make ARCH=arm CROSS_COMPILE=arm-unknown-linux-gnueabi- oldconfig
sylvain@arm-builder:~/kernel/linux-rpi-3.2.27$ make ARCH=arm CROSS_COMPILE=arm-unknown-linux-gnueabi- prepare
# L'option -j9 indique au make de lancer jusqu'à 9 processus en parallèle. 
# À ajuster en fonction du nombre de "cœurs" de votre machine
sylvain@arm-builder:~/kernel/linux-rpi-3.2.27$ make -j9 ARCH=arm CROSS_COMPILE=arm-unknown-linux-gnueabi-

CROSS_COMPILE=arm-unknown-linux-gnueabi-

La macro CROSS_COMPILE sert à indiquer le préfixe des outils de la toolchain. Cela permet au make d'invoquer le bon compilateur – c'est à dire celui de la cible et non pas celui de l'hôte par erreur.

Dans mon cas, avec une toolchain générée en conservant au maximum les options par défaut de crosstool-NG, ces outils sont:

  • arm-unknown-linux-gnueabi-gcc
  • arm-unknown-linux-gnueabi-as
  • arm-unknown-linux-gnueabi-ld

Avec une autre toolchain il faudra sans doute ajuster l'option CROSS_COMPILE pour l'adapter.

Compiler son propre module

Une fois les outils en place, écrire son propre module revient à écrire un programme C contenant les points d'entrée init_module (appelé par le kernel au chargement du module) et cleanup_module (appelé par le kernel au déchargement du module):

#include <linux/module.h>  /* Needed by all modules */
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sylvain@chicoree.fr, 2012");
MODULE_DESCRIPTION("Demo module for cross compiling");
 
int init_module(void)
{
  // Print a message in the kernel log
  printk("Hello world\n");
 
  // A non 0 return means init_module failed; module can't be loaded.
  return 0;
}
 
 
void cleanup_module(void)
{
  // Print a message in the kernel log
  printk("Goodbye world\n");
}

La construction du module à partir de ce fichier source se faisant avec les makefiles spécifiques du kernel, l'usage est d'utiliser un fichier Makefile local au répertoire du module, et qui se contente d'invoquer de manière récursive ses homologues présents dans le dossier du kernel:

#
# My Module Makefile
#
obj-m := my-module.o
KDIR := /home/sylvain/kernel/linux-rpi-3.2.27/
PWD := $(shell pwd)
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

Pensez à modifier dans le fichier ci-dessus la ligne KDIR:= pour refléter l'emplacement des sources du kernel sur votre machine. Puis, lors de l'invocation du make, n'oubliez pas de définir les macros ARCH et CROSS_COMPILE adéquate pour votre cible:

# Construction du module
sylvain@arm-builder:~/test$ make ARCH=arm CROSS_COMPILE=arm-unknown-linux-gnueabi-
# Copie sur la cible
sylvain@arm-builder:~/test$ scp my-module.ko pi@10.129.36.203:.

Pour tester, il suffit de charger/décharger le module du noyau de la cible, chacune de ces opération faisant apparaitre un message dans le journal du système (grâce aux printk présents dans le code source):

# Test
sylvain@arm-builder:~/test$ ssh pi@10.129.36.203
pi@raspberrypi ~ $ sudo insmod my-module.ko
pi@raspberrypi ~ $ dmesg
[...]
[69370.606868] Hello world
pi@raspberrypi ~ $ sudo rmmod my_module
pi@raspberrypi ~ $ dmesg
[...]
[69370.606868] Hello world
[69411.891597] Goodbye world

Comme vous le voyez, une fois les outils en place, compiler un module, puis le charger ou décharger de la cible n'est pas compliqué. Ce qui l'est, c'est d'écrire le module qui correspond à votre besoin. Et pour ça, je ne peux pas trop vous aider…

Ressources