# 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](https://developer.hashicorp.com/vagrant) verwendet, um eine virtuelle Maschine mit [VirtualBox](https://www.virtualbox.org/) 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). ```{code-block} ruby 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. ```{code-block} c #include #include #include 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 ``, `` und ``. Die Header-Datei `` 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 `` 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 `` 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. ```{code-block} make 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. ```{code-block} sh $ make ``` Mit dem Befehl `insmod` wird anschließend das Kernel Modul geladen. ```{code-block} sh $ sudo insmod hello.ko ``` Um das Kernel Modul zu entladen, wird der Befehl `rmmod` verwendet. ```{code-block} sh $ sudo rmmod hello ``` Nun können die Nachrichten des Kernel Moduls mit dem Befehl `dmesg` angezeigt werden. ```{code-block} sh $ 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. ```{code-block} rust 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 { 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. ```{code-block} make 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. ```{code-block} sh $ 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.