CS 111 Notes January 16, 2008


OS Organizations


What are our goals?

(occasionally utilization and performance conflict, but not usually)

How will the OS do all of this?

Virtualization!

The kernel has fill privileges while the applications have controlled access to the hardware (usually through the kernel)(figure 1

Figure 1: Applications must go through the kernel

When designing the kernel, we have to figure out how we want to control access to each piece of hardware.

ALU:

There are no real restrictions on computations (it would slow things down). Applications have full access (with few exceptions)

Time:

It is worth noting that the OS must also deal with time, such as controlling processes that may be stuck in an infinite loop or idle processes. This means that we cannot give a process complete control.

Memory:

An application should only have access to memory belonging to that application. We don't, however, want to use system calls to accomplish this because it would be far too slow.
In order to do this, the computer uses address translation. This means that each process "thinks" that it has its own memory starting at, say, 0x00000000. On the actual memory this is not the case (figure 2). Each address is translated by the hardware to a different location on the physical memory. This prevents individual apps from having access to any memory other than that which is allocated to it.
In the hardware there is a register called the "page register" that says which translation to use. This same register can tell a process that it cannot access certain locations in memory (called a page fault).

Figure 2: Virtual memory


Registers:

Registers are divided by the privilege level required in order to use them. The general purpose registers can be used by any programs, while there are some registers that are reserved for the kernel. When we have to deal with multiple processes, how will this work? Each application thinks that it has its own set of registers, but there is only one set on the CPU. This means that when the OS switches processes, it must save the current state of the old process (in a place in memory, this is called the "process descriptor") before giving the registers over to the new process. There is a specific location in memory called the "process descriptor table" which houses all of the process descriptors (see figure 3 and 4).

Figure 3: Process descriptors


Figure 4: Process descriptor table

When an interrupt occurs:

This leads us to an important point regarding the number of registers that a system should have. On the one hand, it seems like a great idea to have lots and lots of them, it would be faster and easier on the compilers. There is a downside that would occur when switching processes though, all of these registers would have to be saved, causing hang and extra memory usage (keep in mind that registers have to be swapped at each system call since the kernel must take over). There are a few possible solutions to this issue:

Access to I/O:

I/O devices require a different access strategy than the previously considered components because they are much slower. As many as 20 million instructions can be executed in a processor while awaiting the results of reading a single random sector off a hard drive. Also, since many I/O devices are used for permanent storage, robustness is a major factor. The lack of standardization in device interfaces can also create a problem which we would like to abstract away from the user to allow more portable access to peripherals. Because of these factors, system calls are an appropriate way to implement device access functions because they can be executed almost entirely in the kernel.

Before considering the appropriate interface for devices, it is important to partition them into their two main categories and consider the properties of each.


Disk Family

Network devices, keyboard, mouse, other input devices

There are also other types of devices (such as the display), but we will focus on these two generic types of devices.


File Descriptors


Figure 5: File descriptors

There are many ways to implement device access, but we will focus on the strategy adopted by UNIX. In order to simplify the issues of device access, UNIX has a common interface for all I/O devices. All devices and the data on them is described by the file descriptor type and accessed with the same API.

The file descriptor consists of an integer number as a file handle and an offset, which points to the specific location within the file that the next read or write will come from.

The process of making a disk access system call generally proceeds like the following:

  1. user process issues system call

  1. the kernel takes over, examines the calling process's registers and memory, and determines the appropriate type of system call to make

  2. kernel consults File Descriptor Table within the process descriptor

Note that the File Descriptor Table is another field in the process descriptor. This consists of an array of file descriptors indicating which files that process is currently reading from or writing to.

In order to open a file descriptor, a process must make the system call

When a process is finished with a file, it must close it in order to prevent a file descriptor leak. The system call to close a file descriptor is as follows:

Because of the high frequency of errors that can arise when writing to a disk, it is important to always check for errors after system calls.