# Lecture 2 - Processes: Virtualizing the CPU

Source: https://wisconsin.twango.dev/sp26-cs537/lectures/lecture-02

# Lecture 2 - Processes: Virtualizing the CPU

**Instructor:** Michael Swift & Tej Chajed
**Topics:** Processes, process state, CPU virtualization, limited direct execution, context switching

> [!abstract]- Learning Outcomes
> - Describe the process abstraction
> - Argue why limited direct execution is a good approach for CPU virtualization
> - Reproduce the mechanism for limited direct execution

---

## What is a Process?

> **Process:** An execution stream in the context of a process state.

> **Execution stream:** A stream of executing instructions; a running piece of code; a "thread of control."
>
> **Process state:** Everything that the running code can affect or be affected by.

Process state includes:
- **Registers** — general purpose, floating point, status, program counter, stack pointer
- **Address space** — heap, stack, and code
- **Open files**

A process is different from a program:
- **Program:** Static code and static data (lives on disk)
- **Process:** Dynamic instance of code and data (lives in memory)

Can have multiple process instances of the same program (e.g., many users running `ls` at the same time).

---

## Process Creation

> **Process Creation:**
>
> 1. Program sits on disk (code + static data)
> 2. OS loads code and static data into memory
> 3. OS allocates heap and stack in the address space
> 4. Process is ready to execute

### Two Ways to Create a Process

1. **Build a new empty process from scratch**
2. **Copy an existing process and change it appropriately**

### Option 1: New Process from Scratch

- Steps:
  - Load specified code and data into memory; create empty call stack
  - Create and initialize PCB (make look like context-switch)
  - Put process on ready list
- Advantages: No wasted work
- Disadvantages: Difficult to setup process correctly and to express all possible options
  - Process permissions, where to write I/O, environment variables
  - Example: Windows has call with 10 arguments

### Option 2: Clone Existing Process and Change

- Example: Unix `fork()` and `exec()`
  - `fork()`: Clones calling process
  - `exec(char *file)`: Overlays file image on calling process
- `fork()`:
  - Stop current process and save its state
  - Make copy of code, data, stack, and PCB
  - Add new PCB to ready list
- `exec(char *file)`:
  - Replace current data and code segments with those in specified file
- Advantages: Flexible, clean, simple
- Disadvantages: Wasteful to perform copy and then overwrite memory

### Unix Shell Implementation

```c
while (1) {
    char *cmd = getcmd();
    int retval = fork();
    if (retval == 0) {
        // This is the child process
        // Setup the child's process environment here
        // E.g., where is standard I/O, how to handle signals?
        exec(cmd);
        // exec does not return if it succeeds
        printf("ERROR: Could not execute %s\n", cmd);
        exit(1);
    } else {
        // This is the parent process; Wait for child to finish
        int pid = retval;
        wait(pid);
    }
}
```

---

## Virtualizing the CPU

Goal: Give each process the impression it alone is actively using the CPU.

| Resource | Sharing Strategy |
|----------|-----------------|
| CPU      | **Time sharing** |
| Memory   | **Space** sharing (later) |
| Disk     | **Space** sharing (later) |

> [!note] Lecture Note
> Time sharing on the physical CPU means processes take turns: A, B, A, B, A... Each process thinks it has its own virtual CPU. Memory and disk use space sharing — each process gets its own region.

### Process States

```mermaid
stateDiagram-v2
    running --> ready : deschedule
    ready --> running : schedule
    running --> blocked : I/O issued
    blocked --> ready : I/O complete
```

- **Running:** On the CPU (only one on a uniprocessor)
- **Ready:** Waiting for the CPU
- **Blocked:** Asleep — waiting for I/O or synchronization to complete

> [!note] Lecture Note
> When B is running, A sits in the ready state. The scheduler picks from the ready queue.

OS must track every process in the system:
- Each process identified by unique **Process ID (PID)**

OS maintains queues of all processes:
- **Ready queue:** Contains all ready processes
- **Event queue:** One logical queue per event
  - e.g., disk I/O and locks
  - Contains all processes waiting for that event to complete

---

## Limited Direct Execution

### Direct Execution

- Allow user process to run directly on hardware
- OS creates process and transfers control to starting point (i.e., `main()`)

### Problems with Direct Execution

1. **Process could do something restricted** — could read/write other process data (disk or memory)
2. **Process could run forever** (slow, buggy, or malicious) — OS needs to be able to switch between processes
3. **Process could do something slow** (like I/O) — OS wants to use resources efficiently and switch CPU to other process

Solution: **Limited direct execution** — OS and hardware maintain some control.

---

## Problem 1: Restricting Execution

How can we ensure user process can't harm others?

Solution: **privilege levels supported by hardware** (bit of status)
- User processes run in **user mode** (restricted mode)
- OS runs in **kernel mode** (not restricted)
  - Instructions for interacting with devices
  - Could have many privilege levels (advanced topic)

How can a process access a device?
- **System calls** (function call implemented by OS)
- Change privilege level through **syscall trap instruction**

### What User Processes Cannot Do

User processes are not allowed to perform:
- General memory access
- Disk I/O
- Special x86 instructions like `lidt`

If a process tries to do something restricted, the hardware generates a fault/exception.

### How System Calls Work

1. Process P wants to call `read()` — but P can only see its own memory because of **user mode** (other areas, including kernel, are hidden)
2. P cannot call kernel code directly — instead it uses a **syscall trap instruction**
3. The `read()` library function executes: `movl $6, %eax; syscall`
   - `$6` is the **syscall-table index** identifying which system call to invoke
   - `syscall` is the **trap instruction** that calls through the trap table
4. The `syscall` instruction switches to **kernel mode** — now the kernel can access all memory
5. The kernel follows entries in the trap table to the correct system call handler (e.g., `sys_read`)
6. The kernel can access user memory to fill in the user buffer
7. **Return-from-trap** at end to return to Process P (back in user mode)

---

## Problem 2: How to Take CPU Away?

OS requirements for **multiprogramming** (or multitasking):
- **Mechanism** — to switch between processes
- **Policy** — to decide which process to schedule when

### Separation of Policy and Mechanism

Reoccurring theme in OS:
- **Policy:** Decision-maker to optimize some workload performance metric
  - Which process when?
  - Process **Scheduler** (future lecture)
- **Mechanism:** Low-level code that implements the decision
  - How?
  - Process **Dispatcher** (this lecture)

### Dispatch Mechanism

OS runs a **dispatch loop**:

```text
while (1) {
    run process A for some time-slice
    stop process A and save its context
    load context of another process B
}
```

> [!note] Lecture Note
> The stop + save + load is called a **context switch**. Each process runs for a time slice (e.g., A, B, A, B...).

Two key questions:
1. How does the dispatcher gain control?
2. What execution context must be saved and restored?

### Q1: How Does the Dispatcher Get Control?

#### Option 1: Cooperative Multi-tasking

- Trust process to relinquish CPU to OS through traps
  - Examples: System call, page fault (access page not in main memory), or error (illegal instruction or divide by zero)
  - Provide special `yield()` system call
- Problem: Processes can misbehave
  - By avoiding all traps and performing no I/O, can take over entire machine
  - Only solution: Reboot!
- Not performed in modern operating systems

#### Option 2: True Multi-tasking

- Guarantee OS can obtain control periodically
- Enter OS by enabling periodic alarm clock
  - Hardware generates **timer interrupt** (CPU or separate chip)
  - Example: Every 10ms
- User must not be able to mask timer interrupt
- Dispatcher counts interrupts between context switches
  - Example: Waiting 20 timer ticks gives 200 ms time slice
  - Common time slices range from 10 ms to 200 ms

### Q2: What Context Must Be Saved?

Dispatcher must track context of process when not running:
- Save context in **process control block (PCB)** (or, process descriptor)

What information is stored in PCB:
- PID
- Process state (i.e., running, ready, or blocked)
- Execution state (all registers, PC, stack ptr)
- Scheduling priority
- Accounting information (parent and child processes)
- Credentials (which resources can be accessed, owner)
- Pointers to other allocated resources (e.g., open files)

> [!note] Lecture Note
> The PCB is a struct (e.g., `struct proc { pid, status, ... }`) that holds everything the OS needs to know about a process.

Requires special **hardware support**:
- Hardware saves process PC and PSR on interrupts

### Context Switch Procedure

```mermaid
sequenceDiagram
    participant Program as Program
    participant HW as Hardware
    participant OS as Operating System

    Note over Program: Process A running...
    HW->>HW: timer interrupt
    HW->>HW: save regs(A) to k-stack(A)
    HW->>HW: move to kernel mode
    HW->>OS: jump to trap handler
    OS->>OS: Handle the trap
    OS->>OS: Call switch() routine
    OS->>OS: save regs(A) to proc-struct(A)
    OS->>OS: restore regs(B) from proc-struct(B)
    OS->>OS: switch to k-stack(B)
    OS->>HW: return-from-trap (into B)
    HW->>HW: restore regs(B) from k-stack(B)
    HW->>HW: move to user mode
    HW->>Program: jump to B's IP
    Note over Program: Process B running...
```

> [!note] Lecture Note
> A timer interrupt (or syscall trap) causes hardware to save registers to the kernel stack and switch to kernel mode. The OS then performs a software context switch — saving A's registers to A's PCB and restoring B's from B's PCB. Finally, return-from-trap restores B's registers from B's kernel stack and jumps to B in user mode.

---

## Summary

- **Virtualization:** Context switching gives each process the impression it has its own CPU
- **Direct execution** makes processes fast
- **Limited execution** at key points ensures OS retains control
- Hardware provides a lot of OS support:
  - User vs kernel mode (privilege bit)
  - Timer interrupts
  - Automatic register saving and return-from-trap

---

## Related

- **Navigation:** [[lecture-01|← Lecture 1]] · [[lecture-03|Lecture 3 →]]
- **Exam:** [[exams/midterm-1/index|Midterm 1]]
- **Course:** [[README|CS 537 Map of Content]]
