CS361: Operating System

Jian Huang — Spring 2012

EECS | University of Tennessee - Knoxville

Protection

The first purpose of operating system is "protection" among multi-processes and between processes and the kernel. The key mechanisms for protecting processes from each other are:

How does address translation help with "protection"? Process address space means a group of memory addresses used by a process. The simple policy is processes are not allowed to read and write memory of other processes or that of the operating system. The addresses known and used by CPU are called virtual addresses. Mapping of virtual addresses to physical addresses is often, note: not always, performed in hardware by memory management unit, a.k.a. MMU. MMU maintains a consistent view of how different process address spaces are separated.

Dual-mode operation relies on hardware to provide at least 2 modes: kernel mode or protected mode; user mode in which normal programs execute, some instructions are prohibited in user mode, for example to modify a page table.

Transitions from user mode to kernel mode can be achieved by system calls, interrupts, and other exceptions. Why and how kernel model protects address spaces requires an understanding of process states. A short take-home message - kernel mode is a mode for CPU operation, it is not a mode for process.

Process States

On a single processor the illusion of multiple processors comes from multiplexing in time. Each virtual CPU uses a structure to hold program counters stack pointers registers etc. This structure is called state block. Switching from one virtual CPU to another simply means saving a state block and loading a new state block. And the switch is triggered by preemptive timer, voluntary yield, I/O, and other things.

This multi-programming model can be implemented in an unprotected way. Where each thread can access the data of every other thread and threats can share instructions. This is quite common with embedded applications and a few early operating systems.

As a process executes, it has 5 possible states. New. Ready. Running. Waiting. Terminated. The transition looks like the following. The new process is that admitted into the ready state, a scheduler dispatches a process into running state. A running state gets interrupted and put back to ready - that's preemption. A running process calling exit will result this process to be terminated. A running process incurring I/O or event wait will be put into wait. Upon completion of I/O or event, the process will be put into the ready state again.

When a process is created, the new PCB must be constructed. That is very inexpensive. The new page table must be set up for its address space, this step is more expensive. I/O states such as file handlers also need to be copied, this is medium expensive. Copying data from the parent process is very expensive. But with “copy on write” this is much less expensive.

All processes in the ready state are held in the ready queue. All processes waiting for I/O requests to be completed, they wait in I/O queue. Now, a question for you to think about - when a system call is executing - in which state is the process?

Let's use memory access as an example when you read or write to an address, there are multiple possible outcomes: nothing happens, acts like regular memory, it ignores the write request, causes interrupt and input output operation, for example the memory mapped I/O devices, and finally causes exception or fault.

For a process in UNIX. It is a single sequential stream of execution in its own address space. The memory, i.e. contents of address space, is protected. And its file descriptors, i.e. I/O, is also protected. The current state of process is held in PCB which stands for process control block. Only one PCB is active at the time. Being that PCB for each process. The stored information include process state, processed number, program counter, registers, memory limits, list of open files, and other things.

Context switching is the official name of CPU switch from process to process. Each process has its own PCB. When the process is interrupted or issues a system call, the operating system kernel saves state into the corresponding PCB, do some other processing, and reload the state from a different PCB and that corresponding process is then put into execution. Obviously the kernel will incur overhead during a context switch. The overhead sets a minimum practical switch in time.

Concurrency

The basic problem of concurrency involves sharing resources, while the user programs can think that the have exclusive access to shared resources. And the operating system has to coordinate all activities, the basic idea is to use virtual machine abstraction.

The modern techniques is to use the superscalar processors through hyperthreading. Can schedule each threat as if they were separate CPUs. Originally this technique was called simultaneous multithreading. And they were available on quite a few processors such as Alpha, SPARC, Pentium 4, Power 5.

The concept of process allows protection, the concept of thread allows concurrency within the same protected domain. The state shared by all threads in the same process space are, the contents of memory and the file system, and the network connections etc. The state that is private to each thread are kept in sweat control block, a.k.a. TCB. The CPU registers including program counter and the execution stack are kept with the TCB.

Thread itself has been used since the very early days of operating system. In comparison to process, thread is much more lightweight. The thread is a sequential execution stream with the process. There are no protection between threads. Using multithreading a single program can handle a number of different concurrent activities.

To protect threads from each other, we need protection of memory, protection of I/O devices. And this means every task does not have access to all memory or to every device, also protection of access to processor by using preemptive switch.

Real operating systems have many address spaces and many threads per address space. Operating systems that allow only one address space and only one thread per address space also existed. This discussion of separation is largely motivated by the need of protection so in that regard any system that allows users to override process tables and the system DLLs cannot be considered safe.

Operating System Structure

Since the early days the original UNIX operating system kernel actually consists of everything below the system call level and above the physical hardware. It provides the file system, CPU scheduling, memory management, and other operating system functions, all in one layer. The early days of UNIX had small kernels, even though those kernels had within them device drivers and file systems manager. The era of big kernels started with BSD where additional filesystems, complete socket for TCP/IP, and number of virtual devices, mostly to allow existing programs to work over the network. After this growth kernels with millions of lines of source code can mean to be. Bugs become harder to fix.

The idea of microkernel considers building operating system from many user level processes. Actually a concerted effort has been made to move as much functionality from the kernel to be in those user level processes. Also, the concept of modular programming has led the core kernel to use dynamically loadable modules.

Microkernel structure move as much from the kernel into user space. That means small core operating system processes running at kernel level. And operating system services are built from many independent user level processes. The communication between modules are through message passing.

In microkernel design some mechanisms are still kept within the kernel they are for: address space and address translation, or managing thread and process scheduling, and for interprocess communication to invoke servers running in their own address space.

The benefits are obvious, 1st is easier to extend a microkernels functionality, it's easier to port operating system into a new architecture, running in kernel mode which is more reliable, and parts of the kernel are naturally protected from other parts of the kernel and thereby provides fault isolation. The challenge with this design is that performance overhead is severely high for naïve implementation.


Jian Huang / EECS /UTK / revised 01/2012