This is an English version of my blog post. The original version is here (Japanese).
I made my project to write Embedded OS in Rust public in Github.
The target architecture is Arm Cortex-M series. My application is running on Nucleo-F4291ZI board and QEMU.
Currently, I implemented those functionality:
- Round-robin scheduler to schedule processes running in Thread mode
- SVC interfaces to call kernel function for user processes (e.g. print messages using USART, sleep to wait for interrupts)
- Move to sleep mode to wait for interrupt when there is no executable processes
There are only a few functionality yet, but the core structure is being completed, I think.
What I learned
I tried to utilize several crates provided by Rust Embedded group, especially, svd2rust. svd2rust generates nice Rust interfaces from SVD file. It looks very nice, and with this, I could write small bare-metal programs, like LED blink, hello world with UART or something like that. But when I tried to write OS, I found that this is not so convenient as I expected. svd2rust generates API to get an instance to access peripherals safely. But the code forbades to call this API twice to make this instance as singleton. It sounds reasonable to limit peripheral access, but in writing OS, I often met situation to share this instance between interrupt handlers and main function. Of course, you can share the instance through global variables, but access to mutable global variables has a lot of limitations in Rust. Especially, in no_std environment, we cannot use Mutex or Arc. So, code would become complicated to escape those limitations.
So, I implemented my own peripheral interfaces (see
device module in my project). Maybe, I can replace it with svd2rust APIs relatively easily for now, but in the early stage, I recommend to create your own peripheral interfaces.
Rust has nice built-in test framework. However, the framework depends on std libraries, so we cannot run tests in a straight-forward way. In Writing an OS in Rust, “Testing” section explains how to write unit tests using QEMU. In this way, we can run tests on QEMU if there are dependencies on Arm architecture. However, it is still difficult to write a good test related to peripherals. I think the best way to write tests is making modules independent from the hardware. My
util module doesn’t have dependency on hardware-related modules. So, I can write tests in the usual way in this module since it can run on std environment as well.
ErkOS doesn’t have much functionality. So, my plan for the next step is:
- Implement heap memory allocator
- Inter process communication
- Scheduler with priority
- More peripheral drivers
- Migrating to other architectures
Rust and its toolchains becomes better and better. Let’s try to write baremetal software with Rust!