upvote
I'm not a kernel developer but I am an embedded firmware engineer.

To be clear: I like Rust. It's great, I use it a lot. But, Rust's memory safety stuff can't really save you from the screwiness of ISRs. Here's a long-winded example:

ST has a nifty double-buffer DMA mode for their ADCs, so you can give the ADC two different buffers, it'll fill one, fire an IRQ, you catch the IRQ and handle the data, meanwhile, it's filling the other buffer, and the IRQ fires again, you handle the data in the other buffer, rinse, repeat.

This allows the ADC to run continuously, monotonically and at very high sample rates, without monopolizing CPU. It's really a terrific design. I used it for a DIY telephony project once to run continuous FFTs on several ADC channels at once.

This is all fun, but the architecture introduces synchronization issues that aren't immediately solvable within Rust's data model.

Okay, so I can't run the FFT from within the ISR, so I delegate that to a thread. Do I have the thread read the DMA buffer directly, and just pray that it does it fast enough that the ADC doesn't loop back around to that buffer until the thread is done?

Or, do I have the ISR copy the buffer into a queue, mitigating the memory corruption risk? Well that seems good, but how do I make the queue visible to both the ISR and the thread? The ISR takes no arguments, it's just an address the CPU jumps to when a thing happens. Thus, the queue has to be global, which means more unsafe blocks and more very un-idiomatic Rust.

side note: in my use case, it actually worked just fine with the thread reading straight from the DMA buffer, even with the risk of memory corruption. But you can imagine use cases where the risk would be more severe, like maybe decoding packets from a serial interface.

reply
Rust is designed to make this type of issue impossible, but that assumes that you can correctly encode object lifetimes in the kernel in a way that allows the compiler to check them.

So I would say that any easy answer like “this would not compile” would just be a guess, because you would want to know more of the particulars in order to answer this question.

I know that this is kind of a non-answer, but if you want to write a kernel in Rust you have to figure out boundaries for where unsafe {} are. In a kernel, there are probably large chunks of unsafe {} and the Rust compiler prevents certain bugs outside unsafe {} assuming there aren’t bugs inside unsafe {} that would prevent the type checker from doing its job correctly.

reply
I would say that Rust has a good story here. The simple form of this wouldn't compile. So you are generally presented two options:

1. Slap a reference count on it.

2. Use `unsafe` to promise the compiler that your code is right.

I would say that 1 is a pretty good habit to have. It may open you to memory leaks if you aren't careful but those are much less bad than a use-after-free or other memory management issues. And of course the fact that this was the route the patch took is a good sign. I think this is a pretty good default option.

Now if performance is a major issue you may consider going to 2, so it is impossible to say "Rust would have prevented this" because if it was originally written in Rust this may have been the route taken. But I think it is still very valuable to make that an explicit choice and obvious to reviewers and readers.

reply
This is the answer I think. The correctness of your safe code is dependent on the diligence of the unsafe code except for the most simple cases. A kernel is going to have a pretty high unsafe to safe ratio compared to most usermode apps.

This really gets to the core of what I think Rust is about, you can add compiler checked constraints to your APIs that your C and C++ code can't. It's up to you to use them effectively. Rust's ability to keep your safe code safe is a measure of the language, but also your architecture. The buck has to stop somewhere for the language to prove safety, Rust lets you decide rather than the language itself.

reply
The Rust ownership model prevents use after free. This type of a bug would not compile.
reply
Not necessarily. Rust safety relies on OS primitives and the error here is in an OS primitive itself (kernel semaphores).

Yes Rust is one language that can be widely deployed in systems programming and potentially avoid classes of memory and ownership errors. No it doesn’t magically solve all the problems. Saying “Rust would fix this” in a hypothetical situation where Rust existed in 1995 or OpenBSD was rewritten from scratch, ok, well maybe. As of today only research kernels and a very small fraction of Linux systems have been written in Rust when we are talking about kernels.

People without systems and embedded programming experience need to sit down.

reply
I don't think this is about core kernel semaphores but rather the SysV semaphore system calls?
reply
You might not be able to express the ownership in the way that can be checked statically, so quite possibly this would then be downgraded to a runtime error (that could be handled with a panic)—but not undefined behavior.
reply
I’m not an OS programmer and have been dabbling with OpenBSD’s code for fun. But the fact is that Rust kinda lacks flexibility. Most of the OS is dedicated to building a beautiful lie for programs to run happily, and that’s where C shine.

I shudder to think about the amount of work that it would take to convince the rust compiler that everything is all right. Most hardware interactions is “parse, don’t validate” which means you’ll be pinky-swearing to the compiler.

And for my cursory glances at the code, most structures are handled well, that it’s mostly logic bug (from bad data) instead of bad memory access (which can happen).

reply
In practice you don't convince Rust that everything is right. You let it prove that most of the code is right and you promise it (via unsafe) that the rest is. Ideally these unsafe blocks would be carefully documented, reviewed and ideally enclosed in small modules that makes correctness easier to ascertain.

Rust is no panacea, but in my experience it is far easier to write memory safe code when the risky bits are discouraged and explicitly highlighted rather than every line of code being a possible risk. Humans are pretty bad at reviewing 100 lines of boring looking code (especially if this is one of dozens of patches this week) but much better (although by no means excellent at) reviewing 5 2-line unsafe blocks amongst 90 other lines of code.

reply
I thought that parsing implied validation. Is this not the case?
reply
I often refrain from commenting about meta-issues on HN, but I'm particularly annoyed by the downvotes on this question of mine. What is this forum for, if not for this exchange between makers? I've noticed downvotes on questions are an oft occuring pattern. I think the comments on HN should house more than self-contained absolute statements.
reply