Introduction
Depuis quelques années, le langage Rust parvient à se démarquer des quelques nouveaux langages de programmation qui se veulent plus modernes et plus pratiques à utiliser. En effet, grâce à sa prédisposition à éliminer les erreurs de mémoire dans un programme, ce langage suscite l'intérêt des développeurs souhaitant mettre un terme à ce fléau de la programmation. En C, par exemple, il peut arriver qu'un programme fasse usage d'un pointeur vers un espace mémoire non autorisé, causant un comportement inattendu. Ceci n'est pas possible en Rust grâce à de nouveaux concepts introduits et parce que le compilateur vérifie la totalité de la gestion de mémoire à la compilation. Elle est donc clairement déterminée avant l'exécution.
Dans cet article, nous aborderons l'aspect du Rust en tant que langage du kernel Linux. En effet, depuis fin 2021, des développeurs travaillent sur l’intégration du langage dans Linux dans le projet Rust-for-Linux mené par Miguel Ojeda. Il se pourrait que leurs efforts soient intégrés à la prochaine version de release du kernel plus tard cette année, comme l’a mentionné récemment Linus Torvalds lui-même lors de l’Open Source Summit de juin 2022.
En attendant que cette intégration soit définitive, il est déjà possible de récupérer les sources du projet Rust-for-Linux afin d’intégrer Rust à un système Linux. Pour ce faire, nous allons utiliser le projet open source Buildroot et compiler un système comportant un kernel qui supporte le langage Rust. Il sera alors intéressant d’intégrer un driver Rust et de l’installer sur le système construit.
Requis
Logiciel :
- git
- minicom
Matériel :
- BeagleBone Black
- Carte micro SD
- Câble de communication série USB-TTL
I) Récupération des projets open source
Le projet Rust-for-Linux contient une version de développement du kernel visant à rendre possible le support de Rust dans Linux. Miguel Ojeda, le développeur responsable du projet, publie depuis décembre 2021 une série de patchs permettant de suivre l’avancement du projet et les améliorations qui y sont apportées.
Plaçons-nous dans notre répertoire personnel pour cloner les sources du projet ainsi que celles de Buildroot :
$ cd ~
$ git clone https://github.com/Rust-for-Linux/linux
$ git clone https://github.com/buildroot/buildroot.git
II) Configuration du système dans Buildroot
Pour commencer, nous créons une branche dans le projet Buildroot afin d’y sauvegarder toutes vos modifications sans altérer la version master récupérée.
$ cd buildroot
$ git checkout -b rust-in-buildroot
Comme nous utiliserons une carte BeagleBone Black pour faire tourner le système produit par Buildroot, il faut commencer par initialiser la configuration système avec les paramètres par défaut pour cette carte.
Note : Vous pouvez trouver la liste des différentes cartes supportées dans buildroot/boards et celle des configurations par défaut dans buildroot/configs.
$ make beaglebone_defconfig
Puis, nous pouvons commencer à configurer le système que nous voulons produire via l’interface menuconfig :
$ make menuconfig
Dans le menu Toolchain -> C library, sélectionner glibc .
Dans le menu Host utilities, sélectionner host-rust et activer cette option avec la touche "y".
La sauvegarde de la configuration se fait avec "Save", puis "Ok". Pour sortir du menu, la touche "Echap" peut être utilisée plusieurs fois.
On demande ici à Buildroot de télécharger les sources de Rust pour notre machine hôte.
Il faut à présent mettre à jour les paquets Rust dans Buildroot si la version de Rust ne correspond pas à celle utilisée dans le projet Rust-for-Linux. Nous devons remplacer le numéro de version utilisé dans le paquet rust :
$ gedit package/rust/rust.mk
RUST_VERSION = 1.61.0
Faire de même pour le paquet rust-bin :
$ gedit package/rust-bin/rust-bin.mk
RUST_BIN_VERSION = 1.61.0
Note : À la sortie de la version 1.62.0 de Rust, l'ensemble de ce tutoriel a été testé avec cette nouvelle version sans succès. Il semblerait qu'il y ait un conflit entre le compilateur rustc en 1.62.0 et le paquet hôte host-rust téléchargé à l'étape précédente.
Si le numéro de version est modifié, nous devons supprimer les fichiers de hash correspondant à ces paquets car les signatures présentes dans ces fichiers ne correspondent plus avec la version, et la compilation échouera. Idéalement, il faudrait garder ces fichiers .hash et remplacer toutes les signatures pour qu'elles correspondent à la version de Rust séléctionnée.
$ rm package/rust/rust.hash package/rust-bin/rust-bin.hash
Nous pouvons à présent lancer la compilation avec toutes les modifications effectuées (cela peut prendre une ou quelques heures selon la puissance de votre machine).
$ make
III) Configuration avancée du noyau Linux
À présent, nous avons un système compilé par buildroot mais son kernel ne comporte pas le support du langage Rust. Nous allons remédier à cela en entrant dans la configuration kernel intégrée à Buildroot.
À défaut de simplement modifier la version du kernel dans menuconfig (ce qui ne nous permettrait pas de modifier l'intérieur du kernel), nous allons utiliser une configuration avancée afin de pouvoir activer l'option Rust et modifier certaines dépendances.
D’abord, la création d’un fichier local.mk permet d’ajouter une option pour synchroniser les sources kernel du projet Rust-for-Linux avec celles de la configuration Buildroot locale.
$ echo "LINUX_OVERRIDE_SRCDIR = ~/linux" > local.mk
Note : le chemin "~/linux" doit être modifié par "mon_chemin/linux" si le clone du projet Rust-for-Linux a été sauvegardé dans un répertoire arbitraire "mon_chemin".
Le log suivant explicitant la synchronisation avec le projet Rust-for-Linux devrait apparaître au lancement de la prochaine commande de compilation :
>>> linux custom Syncing from source dir ~/linux
Observez-le en lançant l'interface de personnalisation du kernel :
$ make linux-menuconfig
Cette dernière commande va créer un répertoire output/build/linux-custom/ contenant notre configuration Linux personnalisée et nous permettre de configurer le kernel via l’interface menuconfig. Lorsque la fenêtre de configuration du kernel apparaît, effectuez les modifications suivantes pour permettre le support le support de Rust dans le kernel :
- Grâce à la touche "/", recherchez "MODVERSIONS" puis sélectionnez la configuration “-> Enable loadable module support” grâce à l’indicateur numérique (1).
- Désactivez "Module versioning support" en appuyant sur la touche "n" lorsque cette option est sélectionnée.
- Recherchez également la configuration "RUST" grâce à la touche "/" puis sélectionnez la avec (1).
- Activez l'option "Rust support" en appuyant sur la touche “y”
- Sauvegardez et enregistrez la configuration : "Save" -> "Ok"
Ensuite, nous avons remarqué que l’option RUST_IS_AVAILABLE ne se comporte pas exactement comme il serait attendu lors de la compilation. En effet, celle-ci désactive silencieusement l’option RUST lorsque les sources Rust ne sont pas détectées dans le répertoire output/host/lib/rustlib/src/rust/ lié à la compilation. Il est préférable ici de commenter cette dépendance en attendant une future correction sur Buildroot :
$ gedit output/build/linux-custom/init/Kconfig
Recherchez l'option "config RUST" et commentez la ligne "depends on RUST_IS_AVAILABLE" grâce au symbole #. Ne pas oublier de sauvegarder le fichier ensuite.
Maintenant que la configuration kernel permet le support de Rust, il est temps d’intégrer le code source du driver Rust que nous allons installer et tester ! Toujours depuis le répertoire ~/buildroot :
$ mkdir -p output/build/linux-custom/drivers/my_rust_driver
Sur l'éditeur de texte de votre choix (ici : gedit), copiez les codes suivants dans le fichier correspondant.
$ gedit output/build/linux-custom/drivers/my_rust_driver/my_rust_driver.rs
$ gedit output/build/linux-custom/drivers/my_rust_driver/Makefile
$ gedit output/build/linux-custom/drivers/my_rust_driver/Kconfig
************** my_rust_driver.rs **************
// SPDX-License-Identifier: GPL-2.0
use kernel::prelude::*;
use kernel::{chrdev, file};
module! {
type: MyRustDriver,
name: b"my_rust_driver",
author: b"Nicolas Tran",
description: b"Rust character device printing hello message on use",
license: b"GPL v2",
}
struct RustFile;
impl file::Operations for RustFile {
kernel::declare_file_operations!();
fn open(_shared: &(), _file: &file::File) -> Result {
Ok(())
}
}
struct MyRustDriver {
_dev: Pin<Box<chrdev::Registration<2>>>,
}
impl kernel::Module for MyRustDriver {
fn init(name: &'static CStr, module: &'static ThisModule) -> Result<Self> {
pr_info!("My custom Rust character device (init)\n");
pr_info!("Hello from the rustacean world! ? \n");
let mut chrdev_reg = chrdev::Registration::new_pinned(name, 0, module)?;
// Register the same kind of device twice, we're just demonstrating
// that you can use multiple minors.
chrdev_reg.as_mut().register::<RustFile>()?;
chrdev_reg.as_mut().register::<RustFile>()?;
Ok(MyRustDriver { _dev: chrdev_reg })
}
}
impl Drop for MyRustDriver {
fn drop(&mut self) {
pr_info!("Bye!\n");
pr_info!("My custom Rust character device (exit)\n");
}
}
*********** fin de my_rust_driver.rs ***********
******************* Makefile *******************
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_MY_RUST_DRIVER) += my_rust_driver.o
*************** fin de Makefile ***************
******************* Kconfig *******************
# SPDX-License-Identifier: GPL-2.0-only
menuconfig MY_RUST_DRIVER
tristate "my_rust_driver"
depends on RUST
help
A custom character device driver written in Rust
that prints a hello message on use.
**************** fin de Kconfig ****************
Il reste à ajouter ce driver à la liste du menu de configuration en modifiant le fichier Kconfig lié aux drivers :
$ gedit output/build/linux-custom/drivers/Kconfig
Dans ce fichier, il faut ajouter la ligne suivante :
source "drivers/my_rust_driver/Kconfig"
Ajoutez également le driver au fichier Makefile pour l'intégrer à la prochaine compilation :
$ gedit output/build/linux-custom/drivers/Makefile
Dans ce fichier, ajouter les deux lignes suivantes :
# My custom Rust character device driver
obj-$(CONFIG_MY_RUST_DRIVER) += my_rust_driver/
Retournez dans la configuration Linux pour activer le driver nouvellement ajouté au menu :
$ make linux-menuconfig
Device drivers -> my_rust_driver -> "m"
Vous devriez voir le symbole <M> à côté de "my_rust_driver". Cela signifie que notre driver sera configuré en module dans le kernel et devra être installé manuellement par la suite.
Sauvegardez la configuration et sortez du menu (“Save”, “Yes”, “Echap” plusieurs fois).
Nous avons remarqué pendant la phase de recherche que la compilation échouait à ce stade, et que certaines sources de Rust étaient nécessaires du côté de la machine hôte pour que le kernel modifié compile avec Rust. Même si la solution trouvée n'est pas idéale, nous allons utiliser un subterfuge pour fournir les sources requises dans le répertoire ciblé.
Pour ce faire, nous avons copié les sources host-rust-1.61.0 qui viennent d’être téléchargées par Buildroot vers le répertoire output/host/lib/rustlib/src/rust/ :
$ mkdir -p output/host/lib/rustlib/src/rust/
$ cp -r output/build/host-rust-1.61.0/* output/host/lib/rustlib/src/rust/
Note : Ce problème est en lien avec la raison pour laquelle l’option RUST_IS_AVAILABLE a dû être commentée précédemment. Il faudrait idéalement ajouter une contribution à Buildroot qui récupérerait les sources dans le bon répertoire lorsque cela est nécessaire...
Nous pouvons enfin lancer une nouvelle compilation avec toutes les modifications kernel effectuées et l’intégration de notre driver au système (en tant que module installable).
$ make
IV) Tests sur la cible matérielle
Nous allons à présent installer le système sur la cible matérielle BeagleBone Black.
À l’aide d’une carte micro SD et de son adaptateur, nous pouvons flasher l'image du système généré par Buildroot. Celle-ci contient le bootloader, l'image kernel ainsi que le device tree.
/!\ Les données présentes sur la carte micro SD seront effacées. Veuillez sauvegarder les données importantes au préalable.
/!\ Des données seront effacées et peuvent causer des dommages à votre système si vous ne choisissez pas le bon nom de disque à la prochaine étape.
Lorsque vous avez connecté votre carte micro SD, vérifiez le nom de disque correspondant avec la commande suivante :
$ lsblk -f
Dans notre cas, la carte micro SD porte le nom de disque "mmcblk0".
Note : remplacez ce nom dans les commandes suivantes si le nom du disque est différent.
Vérifiez d'abord que la carte SD n'est pas montée par votre système, puis flashez l'image dessus :
Note : ATTENTION, la commande suivante peut être fatale si vous utilisez le mauvais nom de disque. Assurez-vous que le nom correspond à la carte micro SD (grâce à sa taille de stockage, ou en comparant la commande “$ lsblk -f” avec l'adaptateur SD connecté et déconnecté).
$ sudo dd if=output/images/sdcard.img of=/dev/mmcblk0
Nous allons utiliser un port liaison série ttyUSB0 entre notre PC et notre carte BeagleBone Black afin de récupérer les informations de sortie. Le logiciel minicom va nous permettre de lire cette sortie sur un terminal. Dans une nouvelle fenêtre de terminal, utilisez :
$ sudo apt install minicom
$ sudo minicom -D /dev/ttyUSB0
Note : Il est supposé ici que la carte est connectée via le port ttyUSB0. Vérifiez les autres ports ttyUSB* si cela ne fonctionne pas.
Insérez ensuite la carte micro SD dans le port dédié de la BeagleBone et branchez l'alimentation tout en maintenant le bouton de boot personnalisé jusqu'à ce que la mention "Starting kernel..." apparaisse.
Vous devriez ensuite voir l'initialisation du kernel avant de pouvoir accéder à l'espace utilisateur avec le message "Welcome to Buildroot".
Entrez le mot de passe de l'utilisateur root par défaut : root
Nous avons maintenant accès au système que Buildroot a compilé pour nous !
Vérifiez tout d'abord que la version du kernel correspond bien à celle utilisée dans le projet Rust-for-Linux (5.19.0-rc1).
$ uname -r
Il est temps de finalement installer et tester notre driver Rust ! Pour cela, utilisez la commande suivante :
$ insmod /lib/modules/5.19.0-rc1/kernel/drivers/my_rust_driver/my_rust_driver.ko
Nous avons affiché le message contenu dans notre driver Rust sur la console de sortie du système que nous avons compilé ! Remarquez sur la capture d'écran suivante que le langage Rust permet la compatibilité des caractères spéciaux encodé en UTF-8 (ici par exemple, l'emoji en forme de crabe).
Afin de désinstaller le module, utilisez :
$ rmmod my_rust_driver
Vous pouvez aussi vérifier la présence des traces du driver dans la liste des traces du kernel :
$ dmesg
Voilà!
V) Conclusion
Rust est encore en pleine expansion dans le domaine de l'embarqué. Le langage risque de beaucoup faire parler de lui suite à son intégration très prochaine dans le kernel Linux (espérons-le, dans la version 5.20 fin 2022?). Pendant ce temps, le projet Rust-for-Linux, qui est porté par des développeurs enthousiastes de voir Rust se démocratiser, permet dès à présent de compiler une version de kernel expérimentale contenant le support au langage Rust. Nous avons vu le déploiement des sources de ce projet sur un système embarqué BeagleBone Black grâce à Buildroot.
Avec l'intégration prochaine de Rust dans la version stable du kernel, il sera bientôt plus facile de concevoir des systèmes qui possèdent des fonctionnalités écrites dans ce langage sécurisé ! Que pensez-vous de l'utilisation de Rust pour écrire des drivers Linux ?