How to write Rust in the kernel: part 3 [LWN subscriber-only content]
Welcome to LWN.net The following subscription-only content has been made available to you by an LWN subscriber. Thousands of subscribers depend on LWN for the best news from the Linux and free software communities. If you enjoy this article, please consider subscribing to LWN. Thank you for visiting LWN.net!
The interfaces between C and Rust in the kernel have grown over time; any non-trivial Rust driver will use a number of these. Tasks like allocating memory, dealing with immovable structures, and interacting with locks are necessary for handling most devices. There are also many subsystem-specific bindings, but the focus of this third item in our series on writing Rust in the kernel will be on an overview of the bindings that all kernel Rust code can be expected to use.
Rust code can call C using the foreign function interface (FFI); given that, one potential way to integrate Rust into the kernel would have been to let Rust code call kernel C functions directly. There are a few problems with that approach, however: __always_inline functions, non-idiomatic APIs, etc. In particular, C and Rust have different approaches to freeing memory and locking.
During the early planning phases, the project proposed adopting a rule that there should be a single, centralized set of Rust bindings for each subsystem, as explained in the kernel documentation. This has the disadvantage (compared to direct use of Rust's FFI) of creating some extra work for a Rust programmer who wishes to call into a new area of the kernel, but as more bindings are written that need should go away over time. The advantage of the approach is that there's a single set of standardized Rust interfaces to learn, with all of the documentation in one place, which should make building and understanding the bindings less work overall. The interfaces can also be reviewed by the Rust maintainers in one place for safety and quality.
Allocating memory
Like C, Rust puts local variables (including compound structures) on the stack by default. But most programs will eventually need the flexibility offered by heap allocation and the limitations on kernel-stack size mean that even purely local data may require heap-allocation. In user space, Rust programs use automatic heap allocations for some types — mainly Box (a smart pointer into the heap) and Vec (a growable, heap-allocated array). In the kernel, these interfaces would not provide nearly enough control. Instead, allocations are performed using the interfaces in the kernel::alloc module, which allow for specifying allocation flags and handling the possibility of failure.
The Rust interfaces support three ways to allocate kernel memory: Kmalloc , Vmalloc , and KVmalloc , corresponding to the memory-management API functions with similar names. The first two allocate physically contiguous memory or virtually contiguous memory, respectively. KVmalloc first tries to allocate physically contiguous memory, and then falls back to virtually contiguous memory. No matter which allocator is used, the pointers that are exposed to Rust are part of the virtual address space, as in C.
These three different types all implement the Allocator interface, which is similar to the unstable user-space trait of the same name. While the allocators can be used to directly create a [u8] (a sized array of bytes; conceptually similar to how malloc() returns a void * instead of a specific type), the more ergonomic and less error-prone use is to allocate Box or Vec structures. Since memory allocation is so common, the interfaces provide short aliases for boxes and vectors made with each allocator, such as KBox , KVBox , VVec , etc. Reference counted allocations can be made with Arc .
The choice of allocator is far from the only thing that kernel programmers care about when allocating memory, however. Depending on the context, it may or may not be acceptable to block, to swap, or to receive memory from a particular zone. When allocating, the flags in kernel::alloc::flags can be used to specify more details about how the necessary memory should be obtained:
... continue reading