Content deleted Content added
m Fix KeyKOS |
|||
Line 18:
==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.
Line 33 ⟶ 49:
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==
|