CS 350

An OS is a system that manages resources, creates execution environments, loads programs, and provides common services and utilities.

Three Views of an OS

  1. Application View: What services does it provide?
  2. System View: What problem does it solve?
  3. Implementation View: How is it built?

Application View

Provides an execution environment for running programs.

System View

The OS itself also uses resources.

Implementation View

Kernel: Part of OS that responds to system calls, interrupts, and exceptions.

Operating System: OS as a while includes the kernel, and may include other related programs to provide services for applications.

The execution environment provides by the OS includes a variety of abstractions.

Threads and Concurrency

Better utilization of the CPU.

What is a tread? It is a sequence of instructions.

OS/161 Thread Interface.

kern/include/thread.h

int thread_fork(
    const char* name,
    struct proc* proc,
    void (*func)(void*, unsigned long),
    void* data1,
    unsigned long data2);

// Terminate the calling thread.
void thread_exit(void);

// Voluntarily yield execution.
void thread_yield(void);

Implementing Concurrent Threads

What options exist?

  1. Hardware support. P processors, C cores, M multithreading per core. PCM threads can execute simultaneously.
  2. Timesharing. Multiple threads take turns on the same hardware, rapidly switching between threads.
  3. Hardware support and timesharing. PCM threads running simultaneously with timesharing.

Context Switching

Various Stack Frames \to thread_yield \to thread_switch \to switchframe.

kern/arch/mips/thread/switch.S

switchframe_switch:
    /* a0: switchframe pointer of old thread. */
    /* a1: switchframe pointer of new thread. */

    /* Allocate space for 10 registers. */
    addi sp, sp, -40
    /* Return address. */
    sw ra, 36(sp)
    /* Globals pointer. */
    sw gp, 32(sp)
    sw s8, 28(sp)
    sw s6, 24(sp)
    sw s5, 20(sp)
    sw s4, 16(sp)
    sw s3, 12(sp)
    sw s2, 8(sp)
    sw s1, 4(sp)
    sw s0, 0(sp)

    /* Store old stack pointer in old thread. */
    sw sp, 0(a0)

Loading data from the new stack pointer is similar. Uses nop operation in order to ensure loads have completed before usage.

Context Switch Causes

  1. Running thread calls thread_yield. Voluntarily allows other threads to run.
  2. Running thread calls thread_exit. Running thread is terminated.
  3. Running thread blocks, with call to wchan_sleep.
  4. Running thread is preempted. Involuntarily stops running.

Thread States

  1. Running. Currently executing.
  2. Ready. Ready to execute.
  3. Blocked. Waiting for something, not ready to execute.

Timesharing and Preemption

Preemptive Scheduling

OS/161 threads use preemptive round-robin scheduling.

Every time an interrupt is called, a trap frame is the first. Then the interrupt handler stack frame is called, followed by the normal context switching procedures.

When returning from the trap_frame, the original state is restored.

Locks (Mutex)

Before entering a critical section, acquire a lock. Upon leaving, release the lock.

Implemented in hardware because we need an atomic test-and-set.

Acquire(bool* lock) {
    while (Xchg(true, lock) == true);
}
Release(bool* lock) {
    *lock = false;
}

printf forces the execution of threads to be a certain way, and can hide race conditions in your code!

ARM

ARMTestAndSet(addr, value) {
    // Load value.
    tmp = LDREX addr
    // Store new value.
    result = STREX value, addr
    if (result == SUCCEED) return tmp
    return TRUE
}

Acquire(bool *lock) {
    while(ARMTestAndSet(lock, true) == true) {};
}

Spinlocks in OS/161

spinlock_acquire calls spinlock_data_testandset in a loop until the lock is acquired. It turns off interrupts on the cpu.

Spinlocks are not efficient, if we want large chunks of code protected, we should use a lock (mutex). The difference is that spinlocks spin, locks block.

Thread Blocking

Wait Channels in OS/161

Wait channels are used to implement thread blocking.

void wchan_sleep(struct wchan* wc);
void wchan_wakeall(struct wchan* wc);
void wchan_wakeone(struct wchan* wc);
void wchan_lock(struct wchan* wc);

Semaphores

  1. P: If the semaphore value is greater than 0, decrement. Otherwise wait until we can.
  2. V: Increment the value of the semaphore.

P and V are atomic.

Example.

volatile int total = 0;
struct semaphore *total_sem;
total_sem = sem_create("total_mutex", 1);

void add() {
    int i;
    for (i = 0; i < N; ++i) {
        P(sem);
            total++;
        V(sem);
    }
}

Condition Variables

A1 Hint Aside

  1. Implement locks.
  2. Implement condition variables.
  3. Traffic simulation.

Multiple ways to to keep track of where cars are in the intersection.

sy2, uw1, sy3 are the tests to run.

Volatile and Race Conditions

Deadlocks

lock lock_a, lock_b

int FuncA() {
    lock_acquire(lock_a);
    lock_acquire(lock_b);
    ...
    lock_release(lock_a);
    lock_relase(lock_b);
}

int FuncB() {
    lock_acquire(lock_b);
    lock_acquire(lock_a);
    ...
    lock_relase(lock_b);
    lock_release(lock_a);
}
  1. No Hold and Wait. Prevent a thread from requesting resources if it currently has resources allocated to it. Either get them all at once, or have none.
  2. Resource Ordering. Order the resource types and require each thread acquire resources in increasing type order.

Example.

lock A, B

try_acquire() {
    spin_acquire(lk->spin);
    if (lk->held) {
        release(lk->spin);
        return false;
    }
    lk->held = true;
    lk->owner = me;
    release(lk->spin);
    return true;
}

FuncA() {
    acquire(A)
    while(!try_acquire(B)) {
        release(A);
        acquire(A);
    }
}