CS 134

Understanding the Wait Channel (wchan) Facility in OS/161

The wait channel (wchan) facility is a fundamental low-level synchronization mechanism in OS/161. It provides a way for threads to block (sleep) while waiting for a particular condition to be met, and for other threads to wake them up when that condition occurs. This document explains the concept, implementation, and usage of wait channels in OS/161.

Concept and Purpose

A wait channel represents a queue of threads waiting for a specific event or condition. It allows threads to

  1. Block efficiently when they can't proceed
  2. Be woken up when the condition they're waiting for is met

Wait channels are a low-level primitive used to implement higher-level synchronization constructs such as semaphores, locks, and condition variables.

Suppose we had a struct frying_pan that multiple threads wanted to use. If the pan was in use, a thread would need to wait until it was available. The pan could have a wait channel to manage the queue of threads waiting to use it. Thus our frying pan struct might look like

struct frying_pan {
        struct spinlock pan_lock;
        struct wchan *pan_wchan;
        bool in_use;
        struct sausage_list pan_contents;
};

Internal Data Structures

The definition of wait channels is not found in kern/include/wchan.h, but instead in kern/thread/thread.c because we really don't expect any code outside of the thread subsystem to need to see their internals (it's almost like declaring the fields private in a C++ class).

struct wchan {
        const char *wc_name;            /* name for this channel */
        struct threadlist wc_threads;   /* list of waiting threads */
};

But this definition helps us understand the structure of a wait channel—it holds a list of threads that are waiting on the channel.

Core Operations

Creation and Destruction

struct wchan *wchan_create(const char *name);
void wchan_destroy(struct wchan *wc);
  • wchan_create: Allocates and initializes a new wait channel
  • wchan_destroy: Cleans up and frees a wait channel (must be empty)

In our frying pan example, our frying pan would likely have its own create and destroy functions, and inside the pan_create function, we'd call wchan_create to create the wait channel for the pan. Likewise, in the pan_destroy function, we'd call wchan_destroy to clean up the wait channel.

Sleep and Wakeup

void wchan_sleep(struct wchan *wc, struct spinlock *lk);
void wchan_wakeone(struct wchan *wc, struct spinlock *lk);
void wchan_wakeall(struct wchan *wc, struct spinlock *lk);
  • wchan_sleep: Puts the current thread to sleep on the wait channel
    • The passed spinlock must be held; it will be released while the thread sleeps and reacquired before returning
  • wchan_wakeone: Wakes up one thread from the wait channel
    • The passed spinlock must be held
  • wchan_wakeall: Wakes up all threads from the wait channel
    • The passed spinlock must be held

Note the spinlock should be the one that protects that data structure containing the wait channel, and should be consistent. For the same wait channel, you may not pass one spinlock into wchan_sleep and a different one into wchan_wakeone or wchan_wakeall.

Integration with Spinlocks

Wait channels are always used in conjunction with spinlocks, which is crucial for preventing race conditions during sleep and wakeup operations. The spinlock must be held

  1. When calling wchan_sleep
  2. When calling wchan_wakeone or wchan_wakeall

The wchan_sleep function releases the spinlock atomically with putting the thread to sleep, and reacquires the lock before returning.

Very often, the data structure that has a wait channel will also have a spinlock to protect access to the data structure. In our frying pan example, the pan_lock spinlock would be used to protect access to the pan, and it would be held when calling wchan_sleep and wchan_wakeone or wchan_wakeall.

Usage in Higher-Level Primitives

Wait channels are used to implement other synchronization primitives in OS/161.

Semaphores

In kern/include/synch.h and kern/thread/synch.c, semaphores use a wait channel for the queue of waiting threads:

struct semaphore {
        char *sem_name;
        struct wchan *sem_wchan;
        struct spinlock sem_lock;
        volatile unsigned sem_count;
};

The P operation uses wchan_sleep when the semaphore count is zero, and the V operation uses wchan_wakeone to wake a waiting thread.

Locks and Condition Variables

In the third assignment, you'll implement mutexes and condition variables using wait channels. Your code will have a lot in common with the semaphore implementation.

Advantages and Design Considerations

  1. Simplicity: The API is straightforward, only offering the bare essentials for sleep and wakeup operations.
  2. Efficiency: Wait channels allow threads to block without busy waiting, conserving CPU resources.
  3. Flexibility: They can be used to implement various synchronization primitives.
  4. Scalability: Multiple wait channels can exist simultaneously, allowing fine-grained synchronization.
  5. Debuggability: Named wait channels make it easier to track synchronization issues.

Summary

Wait channels are a core synchronization primitive in OS/161, providing a way for threads to block and be woken up efficiently. They are used to implement higher-level synchronization constructs and are essential for building concurrent systems in the OS.

(When logged in, completion status appears here.)