Hi,
Combuster wrote:Its not that macrokernels have no overhead. Instead of communicating via IPC with another process, you communicate via a write() syscall of some sort. lots of IPC in a microkernel tends to imply a corresponding amount of system calls to a certain driver when run on your favorite *nix.
Yes, but I think for some micro-kernels part of the problem is "excessively reduced kernel scope". For example, imagine if you implement the linear memory manager and the physical memory manager as 2 separate processes, so that instead of doing a syscall and a few near calls to allocate or free some RAM you end up with at least 4 syscalls and 4 task switches ("caller -> kernel -> linear memory manager -> kernel -> physical memory manager -> kernel -> linear memory manager -> kernel -> user"). In this case you can easily add 5000 cycles of IPC overhead to an operation that could have only cost about 500 cycles; and while you might gain some theoretical advantage from extra flexibility/modularity, in practice no software really benefits from these theoretical advantages.
The other problem (IMHO) is that a lot of micro-kernels rely on synchronous/rendezvous messaging, where every message sent costs an immediate task switch. With asynchronous/buffered messaging, the messages can be queued and task switches can be avoided. For example, for synchronous/rendezvous messaging if you send 10 requests to a server and get 10 replies back then you need 20 task switches; but for asynchronous/buffered messaging you can send 10 requests, do one task switch to the server, the server can send 10 replies, and then you do another task switch back to the client who receives the 10 replies (20 task switches vs. 2 task switches); and on multi-CPU systems you can have the client and server running on different CPUs and end up with no task switches at all.
For monolithic kernels, the IPC tends to be similar to asynchronous/buffered messaging (pipes, file I/O, sockets, etc, all implemented with FIFO buffers).
Synchronous/rendezvous messaging is easier to use because you can hide the messaging in a library functions, and a standard C library function (e.g. "fopen()") looks like a standard C library function should. Asynchronous/buffered messaging can't really do this, and some standard C library functions can't be implemented easily (e.g. "fopen()" becomes "request_fopen()" plus either a callback or code shifted to a message handling loop). For a micro-kernel, this basically means you've got a choice - have poor performance and make it easy to port existing software to your OS, or have better performance and make it very difficult to port existing software to your OS.
Note: there are ways to make asynchronous/buffered messaging simulate synchronous/rendezvous messaging using client-side buffering and callbacks, etc; but this removes all the performance advantages of asynchronous/buffered messaging and adds some extra overhead of it's own.
Of course for most existing micro-kernel projects (L4, Mach, Minix, etc) the first thing the developers do is slap a Unix compatibility layer on top, so that everyone can port normal software (designed for monolithic systems) to it and compare it to existing monolithic OSs, and decide that it's slow. That's just plain stupid IMHO - it'd be better to design the micro-kernel without caring about "application portability", then write applications specifically designed for the micro-kernel, then port these applications to a monolithic OS and then claim that the monolithic OS is slow...
Cheers,
Brendan