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.
> 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.
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.
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.
> 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).
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.
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.
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.
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.
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.
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.
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
Edit omg… I was thinking of unikernels…
> 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).
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.
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.
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.