A microkernel is a type of kernel which consists of a simple abstraction over the hardware, with a set of primitives or system calls to implement minimal OS services such as address space management, thread management, and inter-process communication. All other services, those normally provided by the kernel such as networking, are implemented in user-space programs referred to as servers.

Essential components of a microkernel
The minimal set of services required in a microkernel seems to be address space management, thread management, inter-process communication, and timer management. Everything else can be done in a user program, although in a minimal microkernel, some user programs may require special privileges to access I/O hardware. A few operating systems approach this ideal, notably QNX and IBM's VM.
Most microkernel systems don't go quite that far. Most put at least some device drivers in the kernel. LynxOS is an example. Most also put a file system in the kernel.
A key component of a microkernel is a good inter-process communication system. Since many services will be performed by user programs, good means for communications between user programs are essential, far more so than in monolithic kernels. The design of the inter-process communication system makes or breaks a microkernel. To be effective, the inter-process communication system must not only have low overhead, it must interact well with CPU scheduling.
Start up, or booting, of a microkernel can be difficult. The kernel alone may not contain enough services to start up the machine. Thus, either additional code for startup, such as key device drivers, must be placed in the kernel, or means must be provided to load an appropriate set of service programs during the boot process.
Some microkernels are designed for high security applications. EROS and KeyKOS are examples. Part of secure system design is to minimize the amount of trusted code; hence, the need for a microkernel. Work in this direction, with the notable exception of systems for IBM mainframes such as KeyKOS and IBM's VM, has not resulted in widely deployed systems.
Tradeoffs in microkernel design
Microkernels need a highly efficient way for one process to call another, in a way similar to a subroutine call or a kernel call. The traditional performance problems with microkernels revolve around the costs of such calls. Microkernels must do extra work to copy data between servers and application programs, and the necessary interprocess communication between processes results in extra context switch operations. The components of that cost are thus copying cost and context switch cost.
Attempts have been made to reduce or eliminate copying costs by using the memory management unit, or MMU, to transfer the ownership of memory pages between processes. Mach uses this approach. This approach adds complexity but reduces the overhead for large data transfers. It is unclear whether the additional complexity is worth the trouble. QNX manages without it, incurring some extra copying costs.
Systems which page programs out to disk create additional problems for interprocess communication. Unless both the source and destination areas are currently in memory, copying must be delayed, or staged through kernel-managed memory. Copying through kernel memory adds an extra copy cost and requires extra memory. Delaying copying for paging delays complicates the interprocess communication system. QNX ducks this problem entirely by not supporting paging, which is an appropriate solution for a real-time system like QNX.
Reducing context switch cost requires careful design of the interaction between interprocess communication and CPU scheduling. Historically, UNIX interprocess communication has been based on the UNIX pipe mechanism and the Berkeley socket mechanism used for networking. Neither of these mechanisms has the performance needed for a usable microkernel. Both are unidirectional I/O-type operations, rather than the subroutine-like call-and-return operations needed for efficient user to server interaction. QNX has high-performance call-and-return primitives. Mach has very general primitives which tend to be used in a unidirectional manner, resulting in scheduling delays.
The question of where to put device drivers owes more to history than design intent. In the mainframe world, where I/O channels have memory management hardware to control device access to memory, drivers need not be entirely trusted. The Michigan Terminal System (MTS), in 1967, had user-space drivers, the first operating system to be architected in that way.
Minicomputers and microcomputers have not, with a few exceptions, interposed a memory management unit between devices and memory. (Exceptions include the Apollo DOMAIN workstations of the early 1980s.) Since device drivers thus had the ability to overwrite any area of memory, they were clearly trusted programs, and logically part of the kernel. This led to the traditional driver-in-the-kernel style of UNIX, Linux, and Windows.
As peripheral manufacturers introduced new models, driver proliferation became a headache, with thousands of drivers, each able to crash the kernel, available from hundreds of sources. This unsatisfactory situation is today's mainstream technology.
With the advent of multiple-device network-like buses such as USB and FireWire, more operating systems are separating the driver for the bus interface device and the drivers for the peripheral devices. The latter are good candidates for moving outside the kernel. So a basic feature of microkernels is becoming part of monolithic kernels.
Microkernel servers
With IPC the operating system could once again be built up of a number of small programs. Networking could be removed from the kernel and placed in a separate user-space program, which would be called by other programs on the system. All hardware support would be handled in this fashion, with programs for networking, file systems, graphics, etc.
Servers are programs like any others, allowing the operating system to be modified simply by starting and stopping programs. For a small machine without networking support, for instance, the networking server simply isn't started. Under a traditional system this would require the kernel to be recompiled, something well beyond the capabilities of the average end-user. In theory the system is also more stable, because a failing server simply stops a single program, rather than causing the kernel itself to crash.
However, part of the system state is lost with the failing server, and it is generally difficult to continue execution of applications, or even of other servers with a fresh copy. For example, if a (theoretic) server responsible for TCP/IP connections is restarted, applications could be told the connection was "lost" and reconnect, going through the new instance of the server. However, other system objects, like files, do not have these convenient semantics, are supposed to be reliable, not become unavailable randomly and keep all the information written to them previously. So, database techniques like transactions, replication and checkpointing need to be used between servers in order to preserve essential state across single server restarts.
The system would look as if it had a full Unix kernel; the fact that the networking support was being "handed off" would be invisible. That system would be considerably easier to maintain: instead of a single six million-line kernel, there would be a series of smaller programs instead. Additionally, users could choose capabilities as needed and run only those programs, tailoring the system to their needs. The same sort of changes to a traditional kernel, also known as a monolithic kernel or monokernel, are very difficult due to the high level of interconnectedness between parts of the system.
The role of the kernel in such a system is limited. In addition to providing basic task management (starting and stopping other programs), it would have to provide the IPC system and provide security. When booting, the kernel would start up a series of servers to handle the hardware on the system, granting those servers additional rights as needed. New programs, those being started by the user, would use the IPC system to access hardware, calling the kernel to pass along messages after being checked for rights and validity. To handle these tasks, Mach included the concept of ports, which introduced filesystem-like endpoints into the IPC system, complete with rights for other programs to use them. For instance, a network server would hold the write permissions to the networking hardware, and keep a number of ports open for reading to allow other programs to call it. Other programs could not take over the networking hardware without the kernel specifically granting this access, and only after the networking server agreed to give up those rights.
The "collection of servers" model offered many advantages over traditional operating systems. By placing the majority of code in well-separated programs, development on such a system was considerably easier. Developing new networking stacks on a traditional monolithic kernel required the entire kernel to be recompiled and rebooted, hard-crashing the machine if there was a bug. With a microkernel there was little chance that an updated networking system would do anything other than inconvenience the user and require that one program to be relaunched. It also offered considerably more security and stability for the same reasons. Additionally the kernel itself was much smaller — later versions of Mach were only 44,000 lines of code.
Kernel bloat
Early operating system kernels were rather small, partly because computer memories were small. As the capability of computers grew, the number of devices the kernel had to control also grew. Early versions of UNIX had kernels of quite modest size, even though those kernels contained device drivers and file system managers. Berkeley UNIX begin the era of the "big kernel". When address spaces increased from 16 to 32 bits, kernel design was no longer cramped by the hardware architecture, and kernels began to grow. This growth trend continued for several decades, resulting in Unix, Linux, and Windows kernels with millions of lines of privileged code.
To date, attempts to reverse this trend have not been highly successful. This is not a technical problem.
Microkernels vs. monolithic kernels
Microkernels generally underperform traditional designs, sometimes dramatically. This is due in large part to the overhead of moving in and out of the kernel, a context switch, in order to move data between the various applications and servers. It was originally believed that careful tuning could reduce this overhead dramatically, but by the mid-90s most researchers had given up. In more recent times newer microkernels, designed for performance first, have addressed these problems to a very large degree. Nevertheless the market for existing operating systems is so entrenched that little work continues on microkernel design.
Examples of microkernels and OSs based on microkernels:
- AIX operating system
- AmigaOS
- Amoeba
- BeOS
- Brainix
- Chorus microkernel
- Coyotos
- EROS
- Haiku
- K42
- LSE/OS (a nanokernel)
- KeyKOS (a nanokernel)
- The L4 microkernel family
- Mach, used in GNU Hurd, NEXTSTEP, OPENSTEP, and XNU (used in Mac OS X)
- MERT
- Minix
- MorphOS
- Phoenix-RTOS
- QNX
- RadiOS
- Spring operating system
- Symbian OS
- VSTa
References
- Exokernel a research kernel architecture with a more minimalist approach to kernel technology.
External links
- Description from the Portland Pattern Repository
- Citations from CiteSeer
- The Tanenbaum-Torvalds Debate