After five years of development, something like seven art projects, one
commercial product, and many changes to the dark corners of the Rust
language, I’ve decided lilos is ready for a 1.0 release!
Some parts I’m excited about include:
As of this release, the lilos APIs are entirely cancellation-safe.
This release contains contributions from five other people, bringing the total
number of contributors to seven! (Want to be number eight? Come say hi!)
Thanks to one of those contributors, the operating system tests are now
running in CI on QEMU!
(For anyone who’s new, lilos is a tiny embedded operating system that uses
Rust async to allow complex multitasking on very limited microcontrollers
without requiring dynamic memory allocation. Read more about lilos on my
project page, where I link to the docs and provide a curated collection
of blog posts on the topic.)
See the release notes if you’re curious about what’s changed. If
you’ve got firmware written for an earlier version of lilos (particularly the
0.3.x series) and would like to update (you don’t have to!), those release notes
will guide you through the process. There have been some breaking API changes,
but I promise they’re all improvements.
I do a lot of electronics projects in my spare time, and I tend to try to make
reusable parts to save myself effort in the future. Because I have to order
ingredients in certain quantities, I often wind up with more than I need for my
project.
So I’ve opened a Tindie store, called Overengineered Widget
Laboratories. Right now there’s one product in the store, called
Keypad:GO. See, I built a sculpture last summer that needed to interact
with people through a phone-style keypad. The keypad interface part of it seemed
like something other people could use, so I made a few extras. This is a very
easy way to interface a keypad or small keyboard to an embedded electronics
project, because it handles all the basics for you — matrix scanning,
debouncing, key matrix collisions, etc.
It will also help you reverse engineer the keypad’s circuit, because often
cheap keypads arrive without good documentation. In the tiny flash of the
embedded microcontroller, I’ve packed a setup wizard that will walk you through
the process of setting up the keypad of your choice. All you need is a terminal
program. This is honestly my favorite part, and I demonstrate it in the video
below.
I’m trying to do something kind of unusual with lilos: in addition to almost
all the APIs being safe-in-the-Rust sense, I’m also attempting to create an
entire system API that is cancel-safe. I’ve written a lot about Rust’s async
feature and its notion of cancellation recently, such as my suggestion for
reframing how we think about async/await.
My thoughts on this actually stem from my early work on lilos, where I started
beating the drum of cancel-safety back in 2020. My notion
of what it means to be cancel-safe has gotten more nuanced since then, and I’ve
recently made the latest batch of changes to try to help applications built on
lilos be more robust by default.
So, wanna nerd out about async API design and robustness? I know you do.
I recently posted about my debugger for async Rust, which can
generate what I call “await-traces” for async code that’s suspended and not
currently running. I mentioned at the time that it appeared possible to get the
source code file name and line number corresponding to the await points, but
left that for future work.
In this post I’ll work through an example of why I’m so excited about this
technique, by building a real driver for a notoriously tricky bus one piece at a
time, using lilos.
I’m a big fan of Rust’s async feature, which lets you write explicit state
machines like straight-line code. One of the operating systems I maintain,
lilos, is almost entirely based on async, and I think it’s a killer
feature for embedded development.
async is also popular when writing webservers and other network services. My
colleagues at Oxide use it quite a bit. Watching them work has underscored one
of the current issues with async, however: the debugging story is not great.
In particular, answering the question “why isn’t my program currently doing
anything” is very hard.
I’ve been quietly tinkering on some tools to improve the situation since 2021,
and I’ve recently released a prototype debugger for lilos: lildb. lildb
can print await traces for uninstrumented lilos programs, which are like
stack traces, but for suspended futures. I wrote this to help me debug my own
programs, but I’m publishing it to try and move the discussion on async
debugging forward. To that end, this post will walk through what it does, how it
derives the information it uses, and areas where we could improve things.
Now that Hubris has gotten some attention, people sometimes ask me if my
personal projects are powered by Hubris.
The answer is: no, in general, they are not. My personal projects use my other
operating system, lilos, which predates Hubris and takes a fundamentally
different approach. It has dramatically lower resource requirements and allows
more styles of concurrency.