Erstellen von Linux Kernel Modulen in C/Rust¶
Seit der Einführung von Rust im Linux Kernel 6.1 ist es möglich, Rust als Programmiersprache im Kernel zu verwenden. In diesem Projekt soll ein einfaches Kernel Modul in C und Rust geschrieben werden. Dabei soll die Funktionalität des Moduls in beiden Sprachen identisch sein und der Workflow zur Erstellung des Moduls in beiden Sprachen verglichen werden.
Linux Kernel Modul¶
Ein Kernel Modul ist ein Programm, das in den Kernel geladen wird, um die Funktionalität des Kernels zu erweitern. Es wird in der Regel als Bibliothek (.ko Datei) erstellt und kann zur Laufzeit in den Kernel geladen und entladen werden. Kernel Module werden in der Regel in C geschrieben, da C die Sprache ist, die schon lange im Linux Kernel verwendet wird und die meisten Kernel Entwickler mit C vertraut sind. Jedoch sollte es möglich sein, Kernel Module in anderen Sprachen wie Rust zu schreiben, da Rust viele neue Funktionen und Sicherheitsmechanismen bietet, die in C nicht verfügbar sind.
Folgende Anwendungsgebiete für Kernel Module sind denkbar:
Gerätetreiber
Dateisystemtreiber
Netzwerktreiber
Systemaufrufe
Entwicklungsumgebung¶
Um eine sichere Testumgebung zu schaffen, werden die erstellten Kernel Module in einer virtuellen Maschine getestet. Dafür wird Vagrant verwendet, um eine virtuelle Maschine mit VirtualBox zu erstellen. Vagrant ist ein Tool zur Erstellung und Verwaltung von virtuellen Maschinen, das auf der Kommandozeile verwendet wird. Der Vorteil von Vagrant ist, dass es einheitliche virtuelle Maschinen erstellt. Das folgende Vagrantfile ist im Repository enthalten und erstellt durch den Befehl vagrant up --provider=virtualbox eine virtuelle Maschine basierend auf Debian 12 (Bookworm).
Vagrant.configure("2") do |config|
config.vm.define "projekt3" do |c|
c.vm.box = "debian/bookworm64"
c.vm.provider "virtualbox" do |v|
v.memory = 4096
v.cpus = 8
end
c.vm.disk :disk, size: "50GB", primary: true
c.vm.provision "shell", name: "packages", inline: <<~SHELL
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y kmod build-essential linux-headers-$(uname -r) curl git clang bison flex bc zstd libssl-dev libelf-dev cloud-utils
SHELL
c.vm.provision "shell", name: "partition", reboot: true, inline: <<~SHELL
growpart /dev/sda 1
SHELL
c.vm.provision "shell", name: "resize2fs", inline: <<~SHELL
resize2fs /dev/sda1
SHELL
c.vm.provision "shell", name: "kernel", run: "never", reboot: true, privileged: false, inline: <<~SHELL
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env
curl https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.8.8.tar.xz | tar -xJ
cd linux-6.8.8
rustup default $(scripts/min-tool-version.sh rustc)
rustup component add rust-src
cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli
cp /vagrant/kernel-config .config
make -j$(nproc)
sudo make modules_install
sudo make install
SHELL
end
end
Um auf die virtuelle Maschine zuzugreifen, kann der Befehl vagrant ssh verwendet werden, womit eine SSH-Verbindung zur virtuellen Maschine aufgebaut wird. Der Projektordner wird mit der virtuellen Maschine unter dem Verzeichnis /vagrant geteilt, sodass einfach Dateien zwischen dem Host und der virtuellen Maschine ausgetauscht werden können.
Die meisten Befehle aus dem kmod Paket benötigen Root-Rechte, daher wird die Ausführung der Befehle mit sudo durchgeführt. Die virtuelle Maschine ist so konfiguriert, dass der Benutzer vagrant ohne Passwortabfrage Root-Rechte erhalten kann.
Erstellen eines “Hello World” Kernel Moduls¶
Um den Workflow zur Erstellung von Kernel Modulen in C und Rust zu vergleichen, wird ein einfaches “Hello World” Kernel Modul erstellt, das beim Laden und Entladen eine Nachricht auf der Konsole ausgibt.
Implementierung in C¶
Das folgende C-Programm implementiert ein “Hello World” Kernel Modul, das beim Laden und Entladen eine Nachricht auf der Konsole ausgibt.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
MODULE_AUTHOR("DVA");
MODULE_DESCRIPTION("A simple hello world module");
MODULE_LICENSE("GPL");
static int __init hello_init(void)
{
pr_info("Hello, world\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
Das Kernel Modul beginnt mit der Einbindung der Header-Dateien <linux/init.h>, <linux/module.h> und <linux/printk.h>. Die Header-Datei <linux/init.h> enthält die Makros module_init und module_exit, die die Funktionen hello_init und hello_exit als Initialisierungs- und Aufräumfunktionen des Kernel Moduls registrieren. Über die Header-Datei <linux/module.h> werden die Makros MODULE_AUTHOR, MODULE_DESCRIPTION und MODULE_LICENSE verwendet, um Metadateninformationen des Kernel Moduls definieren.
Die Makros __init und __exit kennzeichnen die Funktionen hello_init und hello_exit als Initialisierungs- und Aufräumfunktionen des Kernel Moduls.
Nachrichten werden über die Funktion pr_info ausgegeben, die in der <linux/printk.h> Header-Datei definiert ist. Die Funktion pr_info ist ähnlich wie printf in C, gibt aber die Nachrichten im Kernel Log aus.
Um das Kernel Modul zu erstellen, wird ein Makefile benötigt, das die Kompilierung des Moduls steuert. Das folgende Makefile erstellt das Kernel Modul hello.ko mithilfe des Kernel Build-Systems. Das Kernel Build-System ist ein Tool, das sich in einer Linux Installation unter dem Verzeichnis /lib/modules/$(uname -r)/build befindet, was die Nutzung sehr einfach macht.
obj-m += hello.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
Der Befehl make startet das Erstellen des Kernel Moduls.
$ make
Mit dem Befehl insmod wird anschließend das Kernel Modul geladen.
$ sudo insmod hello.ko
Um das Kernel Modul zu entladen, wird der Befehl rmmod verwendet.
$ sudo rmmod hello
Nun können die Nachrichten des Kernel Moduls mit dem Befehl dmesg angezeigt werden.
$ sudo dmesg -t | tail -n 2
Hello, world!
Goodbye, world!
Implementierung in Rust¶
Das folgende Rust-Programm implementiert das gleiche “Hello World” Kernel Modul wie in C.
use kernel::prelude::*;
module! {
type: HelloModule,
name: "hello",
author: "DVA",
description: "A simple hello world module",
license: "GPL",
}
struct HelloModule;
impl kernel::Module for HelloModule {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Hello, world!\n");
Ok(Self)
}
}
impl Drop for HelloModule {
fn drop(&mut self) {
pr_info!("Goodbye, world!\n");
}
}
Das Rust-Programm beginnt mit der Einbindung der kernel Crate, die die Funktionalität zum Erstellen von Kernel Modulen in Rust bereitstellt. Die Crate kernel enthält die Makros module! und pr_info!, die die Definition des Kernel Moduls und das Ausgeben von Nachrichten im Kernel Log ermöglichen.
Der Unterschied zur C-Implementierung besteht darin, dass die Init und Exit Funktionen als Methoden der eines Structs implementiert werden. Die kernel::Module Trait muss für das Struct implementiert werden, und die Exit Funktion wird durch die Drop Trait implementiert.
Die Kompilierung des Rust-Programms erfolgt ebenfalls über ein Makefile, das die Kompilierung des Kernel Moduls steuert. Es ist zu beachten, dass der Kernel mit der Option CONFIG_RUST kompiliert werden muss, um Rust Kernel Module zu erstellen. Das folgende Makefile erstellt das Kernel Modul hello.ko mithilfe des Kernel Build-Systems.
obj-m += hello.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
Da der Debian Kernel nicht standardmäßig mit Rust kompiliert wird, muss der Kernel mit der Option CONFIG_RUST kompiliert werden, um Rust Kernel Module zu erstellen. Dies erfordert zusätzliche Schritte, die im nächsten Abschnitt beschrieben werden.
Die Kompilierung des Kernels mit aktivierter Rust-Unterstützung kann mithilfe der Vagrant VM durchgeführt werden. Dafür wird ein Provisions-Skript verwendet, das die notwendigen Schritte zur Kompilierung des Kernels mit Rust durchführt. Dieses Skript ist im Vagrantfile enthalten, wird jedoch nicht automatisch ausgeführt. Es kann manuell mit dem Befehl vagrant provision --provision-with=kernel ausgeführt werden.
$ vagrant provision --provision-with=kernel
Dieser Prozess kann einige Zeit in Anspruch nehmen, da zunächst der Kernel Source Code heruntergeladen und kompiliert werden muss. Nach Abschluss des Prozesses ist der Kernel mit Rust-Unterstützung kompiliert und wird in der virtuellen Maschine installiert.
Anschließend kann das Rust-Programm mit dem Befehl make kompiliert werden und funktioniert ebenso wie die C-Implementierung.
Fazit¶
In diesem Projekt wurde ein einfaches “Hello World” Kernel Modul in C und Rust implementiert. Der Workflow zur Erstellung des Kernel Moduls in C ist in diesem Experiment deutlich einfacher als in Rust, da der Debian Kernel nicht standardmäßig mit Rust kompiliert wird. Dies erfordert zusätzliche Schritte, um den Kernel mit Rust zu kompilieren. Die Implementierung des Kernel Moduls in Rust ist jedoch ähnlich wie in C und bietet die Vorteile von Rust, wie Sicherheit, Geschwindigkeit und Nebenläufigkeit.
Insgesamt zeigt dieses Projekt, dass es möglich ist, Kernel Module in Rust zu schreiben und dass Rust eine vielversprechende Alternative zu C für die Entwicklung von Kernel Modulen ist.
Jedoch ist zum aktuellen Zeitpunkt die Unterstützung von Rust im Linux Kernel noch nicht vollständig, sodass zusätzliche Schritte erforderlich sind, um Rust Kernel Module zu erstellen. Es bleibt abzuwarten, wie sich die Unterstützung von Rust im Linux Kernel weiterentwickelt und ob Rust in Zukunft eine beliebte Alternative zu C für die Entwicklung von Kernel Modulen wird.