Did you know...?LWN.net is a subscriber-supported publication; we rely on subscribers to keep the entire operation going. Please help out bybuying a subscription and keeping LWN on the net.
This 28-part patch series is focused on low-level support code, stillwithout much in the way of abstractions for dealing with the rest of thekernel. There will be no shiny new drivers built on this base alone. Butit does show another step toward the creation of a workable environment forthe development of code in the Linux kernel.
As an example of how stripped-down the initial Rust support is, considerthat the kernel haseightdifferent logging levels, from "debug" through "emergency". There is amacro defined for each level to make printing simple; screaming about animminent crash can be done withpr_emerg(), for example. The Rustcode in 6.1 defines equivalent macros, but only two of them:pr_info!()andpr_emerg!(); the macros for the other log levels were left out. The first order of businessfor 6.2 appears to be to fill in the rest of the set, frompr_debug!() at one end throughpr_alert!() at the other.There is alsopr_cont!() for messages that are pieced togetherfrom multiple calls.Thissample kernel module shows all of the print macros in action.
A rather more complex macro added in this series is#[vtable].The kernel makes extensive use of structures full of pointers to functions;these structures are at the core of the kernel's object model. A classicexample isstructfile_operations, which is used to provide implementations of themany things that can be done with an open file. The functions foundtherein vary from relatively obvious operations likeread() andwrite() through to more esoteric functionality likesetlease() orremap_file_range(). Anything in the kernelthat can be represented as an open file provides one of these structures toimplement the operations on that file.
Operations structures likefile_operations thus look a lot likeRusttraits, and they can indeed be modeled as traits in Rust code. But the kernel allows any givenimplementation to leave out any functions that are not relevant; aremap_file_range() operation will make no sense in most devicedrivers, for example. In the kernel's C code, missing operations arerepresented by a null pointer; code that calls those operations will detectthe null pointer and execute a default action instead. Null pointers,though, are the sort of thing that the Rust world goes out of its way toavoid, so representing an operations structure in Rust requires some extrawork.
The#[vtable] macro is intended to perform the necessary impedancematching between C operations structures and Rust traits. Both thedeclaration of a trait and of any implementations will use this macro, so atrait definition will look like:
#[vtable] pub trait Operations { /// Corresponds to the `open` function pointer in `struct file_operations`. fn open(context: &Self::OpenData, file: &File) -> Result<Self::Data>; // ... }While an implementation for a specific device looks like:
#[vtable] impl kernel::file::Operations for some_driver { fn open(_data: &(), _file: &File) -> Result { Ok(()) }// ... }If this implementation is to be passed into the rest of the kernel, it mustbe turned into the proper C structure. Rust can create the structure, butit is hard-put to detect which operations have been implemented and whichshould, instead, be represented by a null pointer. The#[vtable]macro helps by generating a special constant member for each definedfunction; in the above example, thesome_driver type would have aconstantHAS_OPEN member set totrue. The code thatgenerates the C operations structure can query those constants (at compiletime) and insert null pointers for missing operations; the details of howthat works can be seen inthispatch.
The submission for 6.2 adds#[vtable] but does not include anyuses of it. The curious can see it in use by looking atthislarge patch posted in August; searching for#[vtable] andHAS_ will turn up the places where this infrastructure is used.
Yet another new macro isdeclare_err!(), which can be used todeclare the various error-code constants likeEPERM. The 6.2kernel will likely includea fullset of error codes declared with this macro rather than the minimal setincluded in 6.1. There is alsoamechanism to translate many internal Rust error into Linux error codes.
The RustVectype implements an array that will grow as needed tohold whatever is put into it. Growing, of course, involves memoryallocation, which can fail in the kernel. In 6.2,Vec asimplemented in the kernel will likely have two methods calledtry_with_capacity() andtry_with_capacity_in(). They actlike the standardwith_capacity()andwith_capacity_in()Vec methods in that they preallocate memory for data to be storedlater, but with the difference that they can return a failure code. Thetry_ variants will allow kernel code to attempt toallocateVecs ofthe needed size and do the right thing if the allocation fails, rather thanjust callingpanic() like the standard versions do.
One of the more confusing aspects of Rust for a neophyte like your editoris the existence of two string types:strandString;theformer represents a borrowed reference to a string stored elsewhere, whilethe latter actually owns the string. The kernel's Rust support will definetwo variants of those, calledCStrandCString,which serve the same function for C strings. Specifically, they deal with astring that is represented as an array of bytes and terminated with aNUL character. Rust code that passes strings into the rest of thekernel will need to use these types.
The series ends with a grab-bag of components that will be useful forfuture additions. Thedbg!()macro makes certain types of debugging easier. There iscode forcompile-time assertions and toforcebuild errors. TheEithertype can hold an object that can be either one of two distinct types.Finally, theOpaquetype is for structures used by the kernel that are never accessed byRust code. Using this type can improve performance by removing the need tozero-initialize the memory holding it before calling the initializationfunction.
As can be seen, these patches are slowly building the in-kernel Rust codeup so that real functionality can be implemented in Rust, but this processhas some ground to cover yet. It's not clear whether more Rust code willbe proposed for 6.2, or whether this is the full set. The pace of changemay seem slow to developers who would like to start doing real work inRust, but it does have the advantage of moving in steps that can beunderstood — and reviewed — by the kernel community. The Rust-for-Linuxwork has been underway for a few years already; getting up to fullfunctionality may well take a while longer yet.
| Index entries for this article | |
|---|---|
| Kernel | Development tools/Rust |
Copyright © 2022, Eklektix, Inc.
This article may be redistributed under the terms of theCreative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds