Sometimes education is about pure theory, and sometimes it's about how people do things out in the world. I think this is a case where the second approach is much more valuable.
If you want to write an oh-cool blog piece for general programmer audiences about how OSes work, sure, go ahead and write it in Python (seen that done, and pretty well, in fact).
But if you want to prepare people to be able to work on real operating systems, they need to know how to do it in C.
Are you implying that another language would be more clear?
Quite the opposite, C doesn't hide anything from you. When you want to understand what the computer is doing you can tell directly from the C code. Unlike other languages where you need to understand what the language is doing first, and only then can you understand the computer.
There are time where "hiding" the computer is useful, but not here.
Yes, it does.
C hides cache, SIMD, registers, the stack (no multiple return values for you!), the details of the heap (malloc() either succeeds or fails, and you can't know what it's going to do until you call it), SMP, instruction-level parallelism, and the details of atomicity, all of which are relevant to OS programming.
C is a nice language. Don't pretend it's how the hardware really works.
C is a nice wrapper around assembly. In a few cases, you wish it were a bit better specified to control the actual assembly/ABI binding a bit better. The problem I see is a lack of contract enforcement which makes introducing hard to diagnose bugs really easy. Maybe Rust will fill the gap.
Most other languages hide away implementation details that are critical to writing an operating system--how would you handle, say, interrupts?
In addition, you can annotate a protected object (essentially, a group of shared procedures protected by an implicit mutex) as being interrupt-safe, and then any access to that object will be automatically protected by the appropriate instructions.
Plus, if you're in a Posix environment, you can use the exact same mechanism for interrupt handling. It's all remarkably elegant.
Alas, like everything Ada, the documentation is opaque in the extreme, but:
https://www2.adacore.com/gap-static/GNAT_Book/html/aarm/AA-C...
Note that at the bottom they're defining a parameterised interrupt handler structure and then instantiating it multiple times on multiple IRQs, each of which is in its own isolation domain...