Required reading: Exokernel paper.
One way to think about an operating system interface is that it extends the hardware instructions with a set of "instructions" that are implemented in software. These instructions are invoked using a system call instruction (int on the x86). In this view, a task of the operating system is to provide each application with a virtual version of the interface; that is, it provides each application with a virtual computer.
One of the challenges in an operating system is multiplexing the physical resources between the potentially many virtual computers. What makes the multiplexing typically complicated is an additional constraint: isolate the virtual computers well from each other. That is,
In this lecture, we will explore at a high-level how to build virtual computer that meet these goals. In the rest of the term we work out the details.
To give each application its own set of virtual processor, we need to virtualize the physical processors. One way to do is to multiplex the physical processor over time: the operating system runs one application for a while, then runs another application for while, etc. We can implement this solution as follows: when an application has run for its share of the processor, unload the state of the phyical processor, save that state to be able to resume the application later, load in the state for the next application, and resume it.
What needs to be saved and restored? That depends on the processor, but for the x86:
To enforce that a virtual processor doesn't keep a processor, the operating system can arrange for a periodic interrupt, and switch the processor in the interrupt routine.
To separate the memories of the applications, we may also need to save and restore the registers that define the (virtual) memory of the application (e.g., segment and MMU registers on the x86), which is explained next.
Approach to separating memories:
Lets assume unlimited physical memory for a little while. We can enforce separation then as follows:
To allow for controled sharing and separation with an application, extend domain registers with protectioin bits: read (R), write (W), execute-only (X).
How to protect the domain registers? Extend the protection bits with a kernel-only one. When in kernel-mode, processor can change domain registers. As we will see in lecture 4, x86 stores the U/K information in CPL (current privilege level) in CS segment register.
To change from user to kernel, extend the hardware with special instructions for entering a "supervisor" or "system" call, and returning from it. On x86, int and reti. The int instruction takes as argument the system call number. We can then think of the kernel interface as the set of "instructions" that augment the instructions implemented in hardware.
We assumed unlimited physical memory and big addresses. In practice, operating system must support creating, shrinking, and growing of domains, while still allowing the addresses of an application to be contiguous (for programming convenience). What if we want to grow the domain of application 1 but the memory right below and above it is in use by application 2?
How? Virtual addresses and spaces. Virtualize addresses and let the kernel control the mapping from virtual to physical.
Address spaces provide each application with the ideas that it has a complete memory for itself. All the addresses it issues are its addresses (e.g., each application has an address 0).
Lab 2 explores this topic in detail.
A central theme in operating system design is how to organize the operating system. It is helpful to define a couple of terms:
Example: trace a call to printf made by an application.
There are roughly 4 operating system designs:
Although monolithic operating systems are the dominant operating system architecture for desktop and server machines, it is worthwhile to consider alternative architectures, even it is just to understand operating systems better. This lecture looks at exokernels, because that is what you will building in the lab. xv6 is organized as a monolithic system, and we will study in the next lectures. Later in the term we will read papers about microkernel and virtual machine operating systems.
The exokernel architecture takes an end-to-end approach to operating system design. In this design, the kernel just securely multiplexes physical resources; any programmer can decide what the operating system interface and its implementation are for his application. One would expect a couple of popular APIs (e.g., UNIX) that most applications will link against, but a programmer is always free to replace that API, partially or completely. (Draw picture of JOS.)
Compare UNIX interface (v6 or OSX) with the JOS exokernel-like interface:
enum { SYS_cputs = 0, SYS_cgetc, SYS_getenvid, SYS_env_destroy, SYS_page_alloc, SYS_page_map, SYS_page_unmap, SYS_exofork, SYS_env_set_status, SYS_env_set_trapframe, SYS_env_set_pgfault_upcall, SYS_yield, SYS_ipc_try_send, SYS_ipc_recv, };
To illustrate the differences between these interfaces in more detail consider implementing the following:
How well can each kernel interface implement the above examples? (Start with UNIX interface and see where you run into problems.) (The JOS kernel interface is not flexible enough: for example, ipc_receive is blocking.)
The central challenge in an exokernel design it to provide extensibility, but provide fault isolation. This challenge breaks down into three problems: