Making a RISC-V Operating System Using Rust (2019)
osblog.stephenmarz.com
osblog.stephenmarz.com
We've had this paradigm about how memory and permission and OS resource management works that we've have since the PDP-11 and I think we ought to be looking at how to move beyond that.
I have read that 'call' in assembly is a pseudo-instruction, which uses auipc and jalr to build the destination address.
But surely that can only supply 32 bits of address? Is there a different sequence on 64 bit RISC-V? Or have I fundamentally misunderstood?
Both save the address of the next instruction in lr, the first is a relative branch the second is to the address pointed by a register plus an offset
Use "jalr r0, 0(lr)" to return
Anything else is just syntactic sugar in the assembler to set up the reg for jalr
We need to build operating systems from the ground up in languages like Rust. We also need to let go of the UNIX way of doing things and move beyond it. It cannot possibly be the best there can ever be.
Build them for modern hardware and even more so future hardware we can see coming down the line. Build them with security in mind from the ground up. Things like ransomware could be made close to impossible by an operating system.
What will that be? I am not sure.
I am hoping for a lot of people to try a lot of new things and allow things to evolve.
So...dump all existing unix/linux software in the bin and start writing things like browsers and desktop managers and office suites from scratch. Have fun...see you in 20-30 years.
It feels we don't really have the same kind of match in modern programming languages. I wonder what a modern equivalent of C would look like that was tailor fitted to RISC-V ISA and an OS that was targeted to that device?
It just is the case that one of the most successful programming languages of all time, C, was shaped by the existing hardware and the needs of the OS the language designers were building. But now we are going in the other direction by starting with an existing programming language and presumably shaping the OS to its strengths.
The big question is whether we still want to write new production code in C. For one, the C standard is horrible. Just about everything is undefined behavior. The second is that you don't want to spend a lot of time checking code for array bound errors, use after free, other problems with pointers, variables or data structures that are not (properly) initialized, etc.
In addition, for large projects, all of the manual namespace management is also a bit of a bore.
One big issue with C is that more or less, the CPU architecture has to treat memory as an array of bytes. There is just too much code that assumes that this is the case. Every data pointer can be converted to a void *, all function pointers are alike, etc. With Rust, there is a chance that radically new architectures are possible, if compatibility with C is no longer required.
How is this horrible? This is the core defining feature of C. It is equivalent of saying that a phallic shaped object with a sharp edge is terrible because you may cut/stab yourself and bleed to death, therefore dull equivalent is better (always). This is known as a knife. Knives with extremely sharp blades exist because grinding at the thing you're trying to cut for hours isn't something all of us enjoy. Have you tried not hurting yourself instead before calling it terrible? Sure, one idiot may die because of it, some unlucky guy may get killed with one, but really, I prefer using a real knife when cooking my food and I will never change my mind about that until I die.
> In addition, for large projects, all of the manual namespace management is also a bit of a bore.
When you're writing in C, fun is the least of your worries. I can recommend C++ for big projects, noone but yourself forces use of templates and classes in that department. I can't speak for people writing for controllers made in 1984 with proprietary compilers that probably aren't even maintained anymore, but that's not my problem. For projects like Linux, C++ would be more than sufficient. It's ironic that Rust has been accepted to Linux kernel because what he said about C++ over a decade ago couldn't be more true for Rust aswell, even though all you need to fix that is a bit of discipline, ironic given C programming is practically 99.97% of that.
> One big issue with C is that more or less, the CPU architecture has to treat memory as an array of bytes.
Not relevant, virtual memory is used in practically all modern hardware and "virtual" here means that you have arrays and have no clue what it's truly like already in hardware. Even kernels run in this mode, and all kernel code is written with this expectation. Before you make up such bullshit nonsense, you should tell us about this memory model that's superior to dumb arrays, because until then, noone has ever thought of a better one.
This is just horrible. C started out as portable assembler. Now essential instructions get deleted. The C standard is a horrible way to define a low-level programming language. Obviously, C is a sharp object. But standard C is a sharp object that jumps at you.
I had a lot of fun writing C. The same way you can have fun writing assembler or do other level stuff. Compared to low-level kernel stuff, C is not a big problem.
I'm talking radically different architectures. Virtual memory has a high cost in the implementation of a CPU. There could be other architectures, capability based, segment based, etc. But if your CPU has to run C efficiently, then you probably don't want to go there.
There is a lot of cruft, like ASLR, that only exists because memory is a flat array of bytes.
As long as the primary goal of a CPU is to run C efficiently, we won't get beyond paged virtual memory.
I wouldn't dispute that. You actually caught me expressing a raw and incomplete idea.
I'm imagining a totally fictitious set of events. Kernighan and Ritchie sitting in front of a PDP-11 and thinking to themselves: we'd really like a way to get this hardware to support multiple users simultaneously. I mean, the impetus of the language was this transitive dependency from hardware -> user need -> OS -> language to build that OS.
So I am looking at these new RISC-V SoCs that are coming out. I'm thinking about things like tenstorrent and their chiplet designs where you have a RISC-V CPU core surrounded by an ungodly amount of GPU-like tensor cores. Nvidia's Grace Hopper chip designs are kinda-sorta similar but with ARM cores.
We have a brand new kind of system architecture on our hands. And we have a new kind of challenge, which is the need to efficiently use that hardware. Instead of multi-user sharing of a single mini-computer, we have to find a way to saturate the GPU-like tensor cores surrounding a RISC CPU.
So we have a new chain with "new hardware" -> "new user need". So, maybe we do need a "new OS" that isn't POSIX-ish. And if that chain follows, then I wonder what "new language" would look like to complete that chain. Because I doubt that C or Rust are probably the right ones.
It seems we are grabbing tools that happen to be handy and applying them to a new situation without doing the same kind of first-principle, ground-up thinking that appears to have been the kind of thinking that lead to C. In other words, we're asking "what kind of OS can I build with this language?" instead of "what kind of language do I need to build this OS?"
A key thing about Unix is that they took existing hardware and did something creative with that.
One thing that is hard about languages that support new hardware is that the core concepts of the new hardware architecture have to be stable enough that the new language can become popular. I don't know enough about GPU architectures, but it seems that they are not very stable and hidden behind abstractions provided by libraries. Which is not an ideal basis for a new language.
The reason why my mind is on this topic is an interview with Jim Keller where he mentioned that when he designed modern CPUs he was conceptually thinking of their front-end as a machine that processes C code (or, the kind of assembly that a compiler made to compile C would generate). Internally the CPU does all kinds of black magic that is provably correct to rewrite that machine code into a form that is performant on modern architectures (e.g. out-of-order processing, pre-fetching). So you end up with things like the Java JVM optimizing high-level languages while also trying to make itself look like a C program, and the hardware providing a front-end to make itself look like a C-processing machine. A bit funny when you think about it. But it all works since this implicit contract has been stable for decades.
But that contract doesn't exist for GPUs or other new kinds of hardware. A lot of modern silicon is "system-on-a-chip" kinda stuff, with multiple CPUS, some high performance and some not, AI accelerator/neural engines, dsps, media endocders/decoders, etc. That trend appears like it will continue.
Keller mentions that Nvidia got ahead of it with Cuda. They presented a C-like interface in front of their GPUs and then just threw insanely complex hardware in front of it and used that to hide the horror spread between the hardware and their drivers. But nowhere else does such a contract exist (and arguably, it doesn't exist even for Nvidia who seem to just barely be holding it together). Now that Keller is designing these SoC he is confronted with the new-normal of no pre-defined contract between software and hardware.
It is a daunting task to face this new reality. But I really wish I saw some evidence of people tackling it from first principles. To sit down, look at the new architectures and dream of new OS paradigms and the new languages that could support it. Because I strongly doubt that such a re-imagining would look anything like Rust.
I'm sure many people have a lot to say to counter this fact, but really, all I need to do is look at https://www.cvedetails.com/vulnerability-list/vendor_id-1902..., whoever begins writing a Rust kernel will reintroduce at least half of these and the other half will be bugs you never even imagined because of false sense of security and lower barrier to entry into a field that's pretty bare, and very very cruel.
If you really believe that Rust fixes having to remember 300 different hardware types that you support and somehow will check that your code is safe for all of them at compile time, you're gravely mistaken.
I don't think that's a defensible claim, nor that your evidence is compelling. Rust has some disadvantages vs C, certainly - I'd start with number of target hardware platforms, personally - but I doubt that they're meaningfully infinite. Likewise, rust certainly has more than 0 advantages over C; just making the most common memory errors harder to hit would be fairly compelling on its own (personally I like Pascal and Ada for that, but apparently Rust hit the popularity jackpot so oh well). As to rust having CVEs... sure, it's not perfect, but I have to notice that that page shows no more than a handful of vulnerabilities per year, while, say, Linux has had far more vulnerabilities, many of them caused by the exact memory errors that rust tries to fight.
There are legitimate arguments against rust (honestly, your initial claim of bloat sounds plausible), but unless I've missed something this isn't the one I would pick.
However rust versions everything, so even if you have a new compiler that removed support for something, you can still pull the old toolchains and build legacy codebases (like this one).
My Rust OS not only pins the edition (which is the default anyway with `cargo init`) but also uses a toolchain file to pin the date of the nightly build I want contributors to build with.
Rust tooling is really fantastic in this regard.
I chuckled a little, after all, GCC isn't versioned and there's no companies out there still using GCC 4 or whatever kind of suffering they prefer.
I also found that there's a fair amount of code Stephen never introduces or talks about on the videos, and just has dropped in the repo. This can make cobbling together your own version of this a little tougher, but maybe a fun experience.
Rust’s compile-time guarantees make an entirely different, single address space, single privilege level OS architecture possible. There is no kernel/userspace distinction. I hope it succeeds.