alt.hn

7/26/2026 at 5:06:13 AM

Maybe we should revisit microkernels

https://notes.hella.cheap/maybe-we-should-revisit-microkernels.html

by morkin

7/26/2026 at 1:01:03 PM

Mach is not a representative proxy for “microkernels” in 2026.The L4 family demonstrated that Mach-like systems were slow because they put too much policy and too many abstractions in the kernel path, not because a minimal protection-domain crossing was intrinsically unaffordable. The article seems to rediscover, in a somewhat Mach/XNU-centered view, pieces of a design space the L4 community has already discovered and used for a long time. IOMMUs can improve the security and deployability of DMA and user-mode drivers, but barely make L4’s already-fast IPC fundamentally faster.

by Rochus

7/26/2026 at 2:43:47 PM

Not the first time I've said this: the 1970s-era GEC OS4000[1] was really fast, like compared with everyone's VAX 780s, and we users benefited in the '80s. I don't know whether or not it was strictly a microkernel, but at least the moral equivalent; the nucleus was originally in (hard/firm)ware, but it was later emulated on different hardware. (I guess, but don't know, that "4000" and "nucleus" reflected Brinch Hansen's RC 4000 system -- which seemed really steam-driven in the '80s).

1. https://en.wikipedia.org/wiki/OS4000

by gnufx

7/26/2026 at 10:59:23 AM

I think history has proven that this theory:

> Better security, better reliability, better modularity. Better security because in a muicrokernel [sic] system a bug in one driver only gives an attacker access to that subsystem (or maybe even that driver), as opposed to having unlimited access to the whole system like is the case with mainstream operating systems today. Better relaibility [sic] becasue [sic] a crash in one subsystem only crashes that subsystem instead of crashing everything; if windows were a microkernel system then the crowdstrike bug would have just stopped some IT security staff from getting telemetry instead of being front page news. If linux were a microkernel system then the linux kernel team wouldn't be responsible for merging every driver for every hardware device, and wouldn't be responsible for vetting code that they would have to become experts on the inner workings of every chip supported by the linux kernel to properly vet.

Is false. In particular:

- A kernel-level exploit can almost always be escalated. If I control your filesystem, you're SOL

- A kernel-level crash in any subsystem brings down the whole system. If your file system crashes, or your GPU is in an unstable state, there is no coming back

- Modularity is a question of clear boundaries. Whether those are static linking, dynamic linking, or XML-RPC doesn't change the level of modularity.

And so on.

Basically, none of the upsides have panned out. On the other hand, code complexity explodes. Microservices need to know who to call how. The whole "a crash in one subsystem only crashes that subsystem instead of crashing everything" means every subsystem needs defensive code for things going wrong elsewhere. KLOCs go up, bugs/KLOC stay constant, and things get less stable.

Plus, it's hard to reason about systemically, and without excellent telemetry, neigh-impossible to debug.

by frognumber

7/26/2026 at 7:04:36 PM

> history has proven

What history? What proof? What evidence?

What commercial microkernel systems are you referencing that suffer from trivial LPEs? Or whole system DoS from unprivileged code execution? To the extent that you argue your alleged failure modes are fundamental.

> none of the upsides have panned out

Again, what history? What proof? What evidence?

One of the stated upsides relative to monolithic kernels is superior reliability. There are numerous microkernels reliable enough for usage in critical flight systems. None of the standard commercial monolithic kernels are even in the vicinity of that.

Another of the stated upsides is superior security. There are multiple microkernels secure enough to be platforms for systems that need to be secure against state actors. None of the standard commercial monolithic kernels are even in the vicinity of that.

History has proven the opposite of your claims. The concrete evidence is the opposite of your vague unsupported assertions.

by Veserv

7/26/2026 at 6:10:47 PM

I can’t say I know much about kernels, but what you say rings true. It’s not unlike the micro service vs monolith argument. It depends on the use cases, but in a system where the micro services depend on each other, all you have is a distributed monolith. All the complexity and overhead, and none of the benefits. Keep dependencies close.

by move-on-by

7/26/2026 at 11:23:30 AM

Device drivers as separate processes might not improve much the resistance against intentional exploits, but they would certainly greatly improve the resistance against bugs.

There are parts that are so simple that it can be practically guaranteed that they are bug free, e.g. the keyboard driver. Also the microkernel itself can be practically bug-free.

A GPU driver is unlikely to ever be bug free, but if the GPU is in an unstable state, because the keyboard should still be working fine, it should be easy to reset the GPU and continue the work without rebooting the computer.

On Linux I have seen cases when the GPU crashed, but switching to a virtual console and back avoided the reboot, by reinitializing the GPU, but I have also seen crashes when not even the keyboard worked, which should never happen, so a reboot was the only solution.

With device drivers in separate processes, bugs in one should not corrupt the memory of others, so restarting a device driver should be enough, instead of rebooting the computer.

> Microservices need to know who to call how

Here there is no difference between a monolithic kernel and a microkernel. With a monolithic kernel, the applications need to know which syscalls to use.

Libraries like libc or liburing can be implemented either by using syscalls to a monolithic kernel or messages to the processes that own peripheral devices, without any change in the API used by application programs. Obtaining memory pages shared with device driver processes can be done by creating file mappings for some pseudo-files with standard names, which has no essential difference from doing I/O by opening device nodes from the /dev pseudo-filesystem in UNIX-like OSes.

Any complex program must be designed from the beginning to be easy to test and debug. If the device drivers are distinct processes, there is no reason to be more difficult to debug them. On the contrary, because the interactions between them are more limited than when they live in a common address space, they should be easier to debug.

A micro-kernel with the associated device driver processes would contain more or less the same code that is now included in the Linux kernel or in any other monolithic kernel but it would be separated in multiple parts with clear boundaries and interfaces between them.

This can only make much easier the reasoning about how the entire system works. The downside is that the interfaces must be well defined. The history of Linux shows a huge number of badly defined interfaces, which later had to be changed. Even in the syscalls, which are supposed to be stable, there are many syscalls already at their fourth or fifth variant, with the previous variants still kept around despite being obsolete, for backwards compatibility with legacy programs.

Nevertheless, defining good interfaces is not that difficult, especially today when there is much less pressure to save every bit in a parameter list (even when taking into account the current memory prices). Most Linux interfaces that had to be replaced were replaced for not having features that were obvious as necessary since the beginning, e.g. for not having a parameter with option-encoding flags or for using some data types that were too small.

by adrian_b

7/26/2026 at 9:07:41 PM

The asahi Linux gpu driver has gotten pretty darn close to being very solid.

by vsgherzi

7/26/2026 at 3:00:58 PM

Funnily enough, "GPU" (video driver) on Linux used to be microkernel-like: the Xorg would run in userspace and talk to device directly. It was exactly as you described - if Xorg crashed, you could use keyboard shortcut to reinitialize it.

And the major motivators for KMS/DRM, which moved a lot of that functionality into kernel, was performance and stability.

by theamk

7/26/2026 at 2:47:49 PM

Same story with Erlang and Elixir systems and their NIFs...the law of leaky abstractions...

by johnbarron

7/26/2026 at 2:27:46 PM

Symbian OS was a microkernel. For each functionality, you would run a "server" with determined privileges, your apps would connect as clients and make use of the functionality that the server was providing them with - i.e. audio, Bluetooth, filesystem access etc.

It was very efficient, a 200 MHz Nokia could run respectable loads. But the API was hell, which wasn't caused by the microkernel architecture, but rather by inheriting mid-90s EPOC idioms.

With a normal C++ stdlib, that would have been a very fine environment indeed.

by inglor_cz

7/26/2026 at 5:33:47 AM

Errors in the code potentially mean killing the liveness of the system or worse corrupting the system in a poor state. Think the networking stack getting corrupted because of bad business logic.

Also very hard to debug

by vsgherzi

7/26/2026 at 7:47:10 AM

Edit omg… I was thinking of unikernels…

by vsgherzi

7/26/2026 at 5:28:05 AM

> The overhead was too high because computers in the 80s didn't have a way for a userspace process to directly access a particular hardware device.

Eh, no, author mixed things up. To access a hardware device, you need ioperm, which is at least 20 years old. (Well, not today, but it was sufficient back then...). IOMMU is completely optional.

The microkernel overhead was too high because any operation (even the one that does not need access to hardware, like reading file from file cache) required a context switch. This does not change with IOMMU. And while having multiple cores helps, you are trading context switch to intra-core cache invalidation and extra scheduling overhead (because no modern system can just have a single core spinning forever).

by theamk

7/26/2026 at 9:53:35 AM

Ioperm, which was introduced by 80386 in 1985, is useful only for a few legacy peripherals.

It allows the operating system to allocate peripherals mapped in the special I/O address space to dedicated device driver processes. For example it could allocate the real-time clock to such a process and a serial UART interface to another process.

Most modern peripherals are memory-mapped. However, you are right that even on the older systems without an IOMMU, the operating system could allocate peripherals to separate device driver processes, using the standard CPU MMU and mapping the peripherals using appropriate page attributes, e.g. as uncacheable memory regions.

The IOMMU is not needed to protect the OS and the processes between themselves, but it is needed to protect both the OS and the processes from rogue peripherals. This is needed regardless if the kernel is monolithic or a microkernel.

The microkernel overhead in the old microkernels, like Mach and all those inspired by it, was indeed too high due to context switches, but that has nothing to do with microkernels but those microkernels just implemented an extremely inefficient method of IPC, which unfortunately has tarnished the reputation of microkernels.

The only right form of IPC does not use any context changes, i.e. it uses message queues located in shared memory pages, with zero copying and no context changes, which is much faster than the traditional syscalls used with monolithic kernels.

In TFA there is another thing with which I do not agree, it is said that message queues need atomic CAS (compare-and-swap, a.k.a. compare-and-exchange in Intel parlance).

In my opinion, using CAS for implementing message queues is a big mistake, because this instruction is non-deterministic and the execution time for any program that uses CAS is unbounded for the worst case.

One-to-one message queues do not need any atomic instructions, they just need store-release instructions and either wait-for-not-equal loops when the waiting times are expected to be short or futex_wait (or similar) syscalls when the waiting times are expected to be long.

Waiting for (multiple) events is one of the principal functions that must be implemented by a microkernel for enabling interprocess communication, together with a memory mapping API. IPC per se must not be implemented by the microkernel, but by user-space libraries.

The message queues that are many-to-one, one-to-many or many-to-many, can use the atomic fetch-and-add instruction (LOCK XADD on x86-64), to dynamically partition the queue, allowing the concurrent insertion and extraction of data into/from the queue by all communicating processes. In this case, only the updating of the queue pointers is serialized, resulting in very low overheads for the communication through the message queue, even in cases of high contention.

While I agree with you that TFA is incorrect in some details, I agree with the general idea that today one could implement microkernels that would be simultaneously faster and safer and easier to maintain than a monolithic kernel like Linux.

Nonetheless, the chances for such a microkernel to appear and to be successful are very low.

The reason is that the only way to be successful would be to implement a very easy transition to it from one of the popular OSes, e.g. from Linux.

This means that it would have to also implement a syscall interface completely compatible with that of Linux, to allow running unmodified legacy programs, and also a device driver API compatible to that of Linux, to allow the reuse of the existing device drivers.

Probably the easiest way to do this would be to start from Xen, still using the root Xen domain with a Linux kernel that manages the I/O, and running legacy Linux applications in virtual machines.

From this, another special domain with the new microkernel should be added, and then the peripheral handling should be migrated gradually from the Xen Dom0 Linux to the microkernel with its associated device driver processes, starting with those more important, i.e. time, keyboard & graphic pointer, GPU, NVMe, Ethernet, USB.

In parallel with migrating peripherals to the microkernel, user applications should be migrated gradually, to use the new I/O IPC instead of the Linux syscalls.

Only such a gradual evolution could succeed. Otherwise, a nice microkernel with which you cannot run any useful application would fail, like the many attempts of creating better operating systems that have failed in the past, even when they were backed by huge companies, like IBM OS/2.

I believe that the I/O API used by applications with such a microkernel should be extremely similar with that of liburing (Linux io_uring), but which would use internally IPC with the appropriate processes, instead of kernel syscalls.

by adrian_b

7/26/2026 at 2:53:50 PM

The only way to make it work with zero contest pages is to dedicate a whole core per process. There would be 1 core for filesystem, 1 core for network, 1 core for GPU, 1 core per device... You are going to run out of cores pretty fast! And even if you allow context switching for stuff like keyboard and mouse, you'd still need 3 cores for major functions (network, disk, graphics). Since a lot of modern systems might have as few as 8 cores, the overhead will be tremendous.

by theamk

7/26/2026 at 4:12:01 PM

There is no need whatsoever to dedicate a core for each process which owns peripherals and which services the requests for them.

All the I/O requests would be submitted in queues and whenever a queue would be empty the server process would sleep by invoking a wait for event syscall, like futex_wait of Linux. Obviously, when a device driver sleeps and must wake up, that involves context changes, but this will happen only when the peripheral is seldom used, so the performance loss is not important. Once the device driver process is active, no context changes are necessary for data transfers between processes. When there is heavy I/O activity, a few cores may be occupied with it, but they will always correspond to the active I/O subsystems, not to any that exist but are not currently in use. The bulk data transfers should be done by DMA directly from the user buffers, so the device driver processes should only orchestrate all the transfers, which should not consume much CPU time.

Nonetheless, I believe that the performance could be improved by reserving a single core for the microkernel, for hardware interrupt handling and also for the keyboard and graphic pointer drivers. Only in very big systems with hundreds of cores it might be necessary to reserve more than 1 core.

A user API like that of liburing of Linux can be reimplemented almost with no changes in a system with a micro-kernel. That means that a user application would have an AIO completion queue, which instead of receiving data from the Linux kernel would multiplex the completion messages from all the device driver processes.

For the opposite direction, from a user application to the device driver processes, the implementation of the I/O request submission queue would be a little more complex, because of security requirements. Processes that trust each other, e.g. the children of a single process, could inherit and use a common submission queue. Otherwise, the submission queues must be distinct, i.e. in mapped pages that are shared with the device driver processes, but not between the user processes. Because polling multiple queues might waste time in a device driver process, it is likely that submitting a batch of I/O requests should be done by writing the shared buffers with data, then invoking a syscall similar to io_uring_enter, which would alert the corresponding device driver processes (by posting the request in single queues monitored by each device driver process for new events, but the posting of this small message would be done by the micro-kernel, so that rogue processes cannot mess with it).

Simpler but less performant I/O APIs, like the stdio of libc, can be implemented on top of a multiplex AIO API, like that of liburing.

by adrian_b

7/26/2026 at 10:41:18 AM

There exist several microkernels that don't suffer much from overhead, it's not hypothetical but decades old technology, from oldest to newest: QNX, the L4 family, Fuchsia, RedoxOS.

by ahartmetz

7/26/2026 at 9:06:57 PM

What about Minix 3?

Probably the most widely deployed microkernel ever: it's inside every Intel CPU for the last ~20 years.

Already has a mostly-working port of the NetBSD userland.

It was shaping up quite well until Andy Tanenbaum retired. Since then, nothing.

Of course, Intel has never released any of its code, which is terrible -- but legal and license-compliant.

https://www.osnews.com/story/136174/minix-is-dead/

by lproven

7/26/2026 at 11:05:00 AM

Yes, but they remain confined to niche applications because none of them has been designed to be incorporated in a framework that would allow the gradual migration to them of the legacy applications and device drivers.

Only Fuchsia might have a chance, because of the disproportionate control that Google has over the Android applications and because of the constraints imposed on those.

by adrian_b

7/26/2026 at 9:06:29 PM

Unfortunately Fuchsia seems all but dead these days

by vsgherzi