The context for these remarks is a discussion on the kernel mailing list about a proposed session for the Linux Plumbers conference taking place online next month, aimed at open-source developers working on the innards of Linux, such as the kernel, core libraries and windowing systems. Google engineer Nick Defaulters proposed the topic of Linux Kernel in-tree Rust support as part of an LLVM “micro conference”.
A year ago, Microsoft's security research team posted about “why we think that Rust represents the best alternative to C and C++ currently available.” The initial attraction is safety, said Microsoft principal cloud developer advocate Ryan Levi ck.
Unless explicitly opted-out of through usage of the 'unsafe' keyword, Rust is completely memory safe,” with obvious attractions for operating systems like Windows which have suffered many memory-related bugs such as buffer overflow errors. “Many teams at Microsoft have found that Rust's rich type system makes writing expressive programs possible,” he said.
Triplet said: Rust has hard stability guarantees when upgrading from one stable version to the next.” The interest in using Rust for kernel development, and its support in principle from the likes of Torvalds and Kroah-Hartman, suggest that this may well happen, though it will be introduced slowly and carefully.
Josh Triplet, a former Intel engineer and a lead of the Rust language team, says he'd “love to see a path to incorporating Rust into the kernel “, as long as it's done cautiously and doesn't upset Linux kernel creator Linus Torvalds. “If building Rustic interfaces within the kernel requires some additional language features, we should see what enhancements to the language would best serve those requirements,” he said in a mailing list discussion about a proposed Linux Plumbers Conference session on support for Rust in the Linux kernel tree.
The Rust session was proposed by Google engineer Nick Defaulters, who works on compiling the Linux kernel with Clang and LLVM. In an exchange with Triplet ton Friday, Torvalds made it clear he wants Rust to be kept in check for now, until the first Rust driver for the Linux kernel becomes available and the kernel team has had time to test it.
Providing compelling use cases that go beyond just basic safety, such as concurrency checking, or lifetimes for object ownership. Moving slowly and carefully, making sure it's a gradual introduction, and giving people time to incorporate the Rust tool chain into their kernel workflows.
We build upon the freestanding Rust binary from the previous post to create a bootable disk image, that prints something to the screen. Table of Contents When you turn on a computer, it begins executing firmware code that is stored in motherboard ROM.
But this wide compatibility is at the same time the biggest disadvantage of BIOS booting, because it means that the CPU is put into a 16-bit compatibility mode called real mode before booting so that archaic bootloaders from the 1980s would still work. When you turn on a computer, it loads the BIOS from some special flash memory located on the motherboard.
The BIOS run self test and initialization routines of the hardware, then it looks for bootable disks. If it finds one, the control is transferred to its bootloader, which is a 512-byte portion of executable code stored at the disk's beginning.
The bootloader has to determine the location of the kernel image on the disk and load it into memory. Its third job is to query certain information (such as a memory map) from the BIOS and pass it to the OS kernel.
If you are interested in building your own bootloader: Stay tuned, a set of posts on this topic is already planned! The MMulti bootStandard To avoid that every operating system implements its own bootloader, which is only compatible with a single OS, the Free Software Foundation created an open bootloader standard called MMulti bootin 1995.
The reference implementation is GNU GRUB, which is the most popular bootloader for Linux systems. For example, the kernel needs to be linked with an adjusted default page size, because GRUB can't find the Multi boot header otherwise.
Another example is that the boot information, which is passed to the kernel, contains lots of architecture dependent structures instead of providing clean abstractions. GRUB needs to be installed on the host system to create a bootable disk image from the kernel file.
However, we plan to add Multi boot support to our boot image tool, so that it's possible to load your kernel on a GRUB system too. If you're interested in writing a Multi boot compliant kernel, check out the first edition of this blog series.
Now that we roughly know how a computer boots, it's time to create our own minimal kernel. Our goal is to create a disk image that prints a “Hello World!” to the screen when booted.
As you may remember, we built the freestanding binary through cargo, but depending on the operating system we needed different entry point names and compile flags. It allows you to install nightly, beta, and stable compilers side-by-side and makes it easy to update them.
Alternatively, you can add a file called rust -toolchain with the content nightly to the project's root directory. Note that such experimental features are completely unstable, which means that future Rust versions might change or remove them without prior warning.
Rust supports many target triples, including arm-linux-androideabi for Android or wasm32-unknown-unknown for Web Assembly. For example, the data-layout field defines the size of various integer, floating point, and pointer types.
Let's start by creating an x86_64-blog_OS.Jason file (choose any name you like) with the common content: This setting specifies that the target doesn't support stack unwinding on panic, so instead the program should abort directly.
Note that there must be no spaces between different flags, otherwise LLVM fails to interpret the features string. However, using the large Sims registers in OS kernels leads to performance problems.
The reason is that the kernel needs to restore all registers to their original state before continuing an interrupted program. This means that the kernel has to save the complete Sims state to main memory on each system call or hardware interrupt.
Since the Sims state is very large (512–1600 bytes) and interrupts can occur very often, these additional save/restore operations considerably harm performance. To solve this problem, we add the soft-float feature, which emulates all floating point operations through software functions based on normal integers.
Note that the entry point needs to be called _start regardless of your host OS. The error tells us that the Rust compiler no longer finds the core library.
This library contains basic Rust types such as Result, Option, and iterators, and is implicitly linked to all no_std crates. It allows to recompile core and other standard library crates on demand, instead of using the precompiled versions shipped with the Rust installation.
After setting the unstable.build-std configuration key and installing the rust -src component, we can rerun the build command: We see that cargo build now recompile the core, rustc-std-workspace-core (a dependency of compiler_builtins), and compiler_builtins libraries for our custom target.
However, there are some memory-related functions in that crate that are not enabled by default because they are normally provided by the C library on the system. While we didn't need any of these functions to compile our kernel right now, they will be required as soon as we add some more code to it (e.g. when copying structs around).
Since we can't link to the C library of the operating system, we need an alternative way to provide these functions to the compiler. Functions and apply the # attribute to them (to avoid the automatic renaming during compilation).
(Support for the compiler-builtins-mem feature was only added very recently, so you need at least Rust nightly 2021-09-30 for it.) With this change, our kernel has valid implementations for all compiler-required functions, so it will continue to compile even if our code gets more complex.
For more information on cargo configuration options, check out the official documentation. It is a special memory area mapped to the VGA hardware that contains the contents displayed on screen.
We will discuss the exact layout of the VGA buffer in the next post, where we write a first small driver for it. The reason is that the Rust compiler can't prove that the raw pointers we create are valid.
By putting them into an unsafe block we're basically telling the compiler that we are absolutely sure that the operations are valid. Note that an unsafe block does not turn off Rust's safety checks.
It's very easy to mess up when working with raw pointers inside unsafe blocks, for example, we could easily write beyond the buffer's end if we're not careful. For example, we could create a VGA buffer type that encapsulates all unsafely and ensures that it is impossible to do anything wrong from the outside.
First, we need to turn our compiled kernel into a bootable disk image by linking it with a bootloader. Then we can run the disk image in the Emu virtual machine or boot it on real hardware using a USB stick.
To turn our compiled kernel into a bootable disk image, we need to link it with a bootloader. As we learned in the section about booting, the bootloader is responsible for initializing the CPU and loading our kernel.
This crate implements a basic BIOS bootloader without any C dependencies, just Rust and inline assembly. Adding the bootloader as dependency is not enough to actually create a bootable disk image.
The problem is that we need to link our kernel with the bootloader after compilation, but cargo has no support for post-build scripts. For running boot image and building the bootloader, you need to have the llvm-tools-preview rust up component installed.
After installing boot image and adding the llvm-tools-preview component, we can create a bootable disk image by executing: We see that the tool recompile sour kernel using cargo build, so it will automatically pick up any changes you make.
Finally, boot image combines the bootloader and your kernel to a bootable disk image. After executing the command, you should see a bootable disk image named bootimage-blog_OS.bin in your target/x86_64-blog_OS/debug directory.
It then maps the program segments to virtual addresses in the page tables, zeroes the .BSS section, and sets up a stack. After writing the image to the USB stick, you can run it on real hardware by booting from it.
To make it easier to run our kernel in Emu, we can set the runner configuration key for cargo: The runner key specifies the command that should be invoked for cargo run.
The command is run after a successful build with the executable path passed as first argument. In the next post, we will explore the VGA text buffer in more detail and write a safe interface for it.