-
Notifications
You must be signed in to change notification settings - Fork 6
Strategy
So how do we port an operating system from C to Rust? Carefully, of course.
The following strategy depends on a breadth-first, iterative approach. I say "breadth-first" because we want as much of the code ported to Rust before we shape the code base into a more "idiomatic" rendition. This way, the more code that the Rust compiler is responsible for, the more likely we are to benefit from Rust's compile-time guarantees. A broad exposure to the code base will also help cultivate a better understanding of how the operating system works so that we can better identify pressure-points ripe for refactoring later.
-
Select a module to begin porting to Rust. Ideally, this is a module with few other dependencies (or better yet, the dependencies have already been ported to Rust). Create a corresponding Rust source file underneath the
src/
root. Expose the module you just made insrc/lib.rs
(pub mod uart
). -
Choose a function within the module that you have selected.
-
Port this function as verbatim as possible (this will be
unsafe
, of course); using#[no_mangle]
. Rust's Foreign Function Interface (FFI) will allow the C code to call this newly ported function. If this function has dependencies which have not been ported,extern
them in the appropriate corresponding Rust module (or do it in the same module you're using, so long as that dependency is not used by anyone else). -
Remove this function from the C code base. Update any part of the C code base where this function is included to be
extern
as it is defined in our Rust code base now (check header files, etc). If that was the last function within the module, remove its C source file and delete the referenced compilation unit from the build system (Makefile). -
Test the operating system as rigorously as possible. With a clean rebuild, the operating system should boot and successfully run through the
usertests
program in xv6. -
Repeat the previous steps until all of the C code has been ported to unsafe Rust.
Once Phase 1 is complete, the hard part begins. We need to refactor parts of the code base to create safe abstractions (shrinking the needed unsafe
blocks as much as possible).
This phase is just as breadth-first and iterative as the first phase.
-
Select a module. This will be easier if it's self-sufficient (i.e., no dependencies).
-
Refactor a function such that it is no longer labelled
unsafe
and its interiorunsafe
blocks are as small as possible. -
Test the operating system as rigorously as possible. With a clean rebuild, the operating system should boot and successfully run through the
usertests
program in xv6. -
Repeat the previous steps until as much of the code base's
unsafe
blocks have been shrunk as far as they can.
Armed with as many safe abstractions as we were able to suss out, begin ruthlessly criticizing the code base and shaping it to use Rust idioms. This might help reduce the unsafe
code further, too.
As always, one function at a time while also rigorously testing the operating system and ensuring usertests
pass.
We are not making drastic changes to how xv6 behaves, as it is supposed to be a faithful port (i.e., not writing a new scheduler).