Operating Systems

================ Start Lecture #5 ================
One can organize an OS around the scheduler.

2.1.6: Implementation of Processes

The OS organizes the data about each process in a table naturally called the process table. Each entry in this table is called a process table entry (PTE) or process control block.

An aside on Interrupts (will be done again here)

In a well defined location in memory (specified by the hardware) the OS stores an interrupt vector, which contains the address of the (first level) interrupt handler.

Assume a process P is running and a disk interrupt occurs for the completion of a disk read previously issued by process Q, which is currently blocked. Note that disk interrupts are unlikely to be for the currently running process (because the process that initiated the disk access is likely blocked).

  1. The hardware saves the program counter and some other registers (or switches to using another set of registers, the exact mechanism is machine dependent).

  2. Hardware loads new program counter from the interrupt vector.
  3. Assembly language routine saves registers.

  4. Assembly routine sets up new stack.
  5. Assembly routine calls C procedure (Tanenbaum forgot this one).

  6. C procedure does the real work.
  7. The scheduler decides which process to run (P or Q or something else). Lets assume that the decision is to run P.

  8. The C procedure (that did the real work in the interrupt processing) continues and returns to the assembly code.

  9. Assembly language restores P's state (e.g., registers) and starts P at the point it was when the interrupt occurred.

Remark: Again it would be a good idea to compare the actions above to those given for a trap and for a simple, use-mode procedure call.

2.2: Threads

Per process itemsPer thread items
Address spaceProgram counter
Global variablesMachine registers
Open filesStack
Child processes
Pending alarms
Signals and signal handlers
Accounting information

The idea is to have separate threads of control (hence the name) running in the same address space. An address space is a memory management concept. For now think of an address space as the memory in which a process runs and the mapping from the virtual addresses (addresses in the program) to the physical addresses (addresses in the machine). Each thread is somewhat like a process (e.g., it is scheduled to run) but contains less state (e.g., the address space belongs to the process in which the thread runs).

2.2.1: The Thread Model

A process contains a number of resources such as address space, open files, accounting information, etc. In addition to these resources, a process has a thread of control, e.g., program counter, register contents, stack. The idea of threads is to permit multiple threads of control to execute within one process. This is often called multithreading and threads are often called lightweight processes. Because threads in the same process share so much state, switching between them is much less expensive than switching between separate processes.

Individual threads within the same process are not completely independent. For example there is no memory protection between them. This is typically not a security problem as the threads are cooperating and all are from the same user (indeed the same process). However, the shared resources do make debugging harder. For example one thread can easily overwrite data needed by another and if one thread closes a file other threads can't read from it.

2.2.2: Thread Usage

Often, when a process A is blocked (say for I/O) there is still computation that can be done. Another process B can't do this computation since it doesn't have access to the A's memory. But two threads in the same process do share memory so that problem doesn't occur.

An important modern example is a multithreaded web server. Each thread is responding to a single WWW connection. While one thread is blocked on I/O, another thread can be processing another WWW connection. Why not use separate processes, i.e., what is the shared memory?
Ans: The cache of frequently referenced pages.

A common organization is to have a dispatcher thread that fields requests and then passes this request on to an idle thread.

Another example is a producer-consumer problem (c.f. below) in which we have 3 threads in a pipeline. One thread reads data from an I/O device into a buffer, the second thread performs computation on the input buffer and places results in an output buffer, and the third thread outputs the data found in the output buffer. Again, while one thread is blocked the others can execute.

Question: Why does each thread block?


  1. The first thread blocks waiting for the device to finish reading the data. It also blocks if the input buffer is full.

  2. The second thread blocks when either the input buffer is empty or the output buffer is full.

  3. The third thread blocks when the output device is busy (it might also block waiting for the output request to complete, but this is not necessary). It also blocks if the output buffer is empty.

Homework: 9.

2.2.3: Implementing threads in user space

Write a (threads) library that acts as a mini-scheduler and implements thread_create, thread_exit, thread_wait, thread_yield, etc. The central data structure maintained and used by this library is the thread table, the analogue of the process table in the operating system itself.



2.2.4: Implementing Threads in the Kernel

Move the thread operations into the operating system itself. This naturally requires that the operating system itself be (significantly) modified and is thus not a trivial undertaking.

2.2.5: Hybrid Implementations

One can write a (user-level) thread library even if the kernel also has threads. This is sometimes called the M:N model since M user mode threads run on each of N kernel threads. Then each kernel thread can switch between user level threads. Thus switching between user-level threads within one kernel thread is very fast (no context switch) and we maintain the advantage that a blocking system call or page fault does not block the entire multi-threaded application since threads in other processes of this application are still runnable.

2.2.6: Scheduler Activations


2.2.7: Popup Threads

The idea is to automatically issue a create thread system call upon message arrival. (The alternative is to have a thread or process blocked on a receive system call.) If implemented well the latency between message arrival and thread execution can be very small since the new thread does not have state to restore.

Making Single-threaded Code Multithreaded

Definitely NOT for the faint of heart.

2.3: Interprocess Communication (IPC) and Coordination/Synchronization

2.3.1: Race Conditions

A race condition occurs when two (or more) processes are about to perform some action. Depending on the exact timing, one or other goes first. If one of the processes goes first, everything works, but if another one goes first, an error, possibly fatal, occurs.

Imagine two processes both accessing x, which is initially 10.

Homework: 18.