CS 134

A Tour of thread.c in OS/161

Introduction

The file kern/thread/thread.c is a crucial component of OS/161, implementing the core thread-management functionality. This tour will give you an overview of its key features and functions, helping you understand how thread management works in the operating system.

Purpose and Overview

thread.c is responsible for

  • Thread creation, destruction, and lifecycle management
  • CPU and thread state management
  • Context switching and scheduling
  • Thread migration for load balancing
  • Integration with synchronization primitives

Key Data Structures

Thread Structure

The struct thread is defined in kern/include/thread.h. It contains essential information about a thread, including

  • Thread identification (name, ID)
  • Current state (running, ready, sleeping, etc.)
  • CPU assignment
  • Execution context and stack
  • Pointers for list management (to use struct thread as a linked-list node)
  • Process association
  • Interrupt and synchronization state

For a complete view of the structure, refer to the thread.h header file.

CPU Structure

The struct cpu is defined in kern/include/cpu.h. It tracks information relevant to each CPU in the system, such as

  • Current running thread
  • Run queue of ready threads
  • List of zombie threads
  • Interrupt and synchronization state
  • Statistics and management information

For full details, check the cpu.h header file.

Current Thread and CPU

OS/161 uses the pointers curthread and curcpu to refer to the currently executing thread and the CPU it's running on, respectively. These are defined in kern/include/current.h and kern/arch/mips/include/current.h.

The MIPS-specific current.h file defines curthread as

register struct thread *curthread __asm("$23"); /* s7 register */

So while curthread looks like the name of a global variable, stored in memory, it's actually kept in a processor register (s7 in this case).

  • Duck speaking

    Is that a performance optimization?

  • PinkRobot speaking

    No, it's actually critical. All the CPU cores share the same memory, but each core has its own registers. We want each core to have its own value for curthread, so it needs to be in a register.

  • Rabbit speaking

    Needs is a little strong. Each CPU core already has its own stack, so it would be technically possible to work out which thread we are based on that, but it would be annoying. Using a processor register is easier, even though it means that the CPU has one fewer register to use for other things as s7 is reserved all the time for curthread.

The architecture-specific code for MIPS also defines __NEED_CURCPU, which causes the generic current.h to define curcpu in terms of curthread using a macro as

#define curcpu curthread->t_cpu

That header also defines curproc as curthread->t_proc, which is the process that the current thread is part of. Currently, process functionality is not really implemented in OS/161, but we can see some intentions as to how it's expected to be implemented.

In code, we use curthread and curcpu to access the current thread and CPU as if they were magically thread-specific global variables, but there's more going on under the hood.

Thread States and Lifecycle

Threads in OS/161 can be in one of the following states (defined in kern/include/threadlist.h):

State Description
S_RUN Currently running on a CPU
S_READY Ready to run, waiting in the run queue
S_SLEEP Blocked, waiting on a wait channel
S_ZOMBIE Exited, waiting to be cleaned up

The lifecycle of a thread involves transitions between these states, managed by functions in thread.c.

Key Functions

Thread Creation and Destruction

  • thread_create: Creates a new thread structure
  • thread_fork: Creates a new thread and sets it up to run a specified function
  • thread_destroy: Cleans up and frees a thread structure

Thread Scheduling and Context Switching

  • thread_switch: Performs a context switch, moving the current thread to a new state and selecting a new thread to run
  • thread_make_runnable: Called from thread_switch, thread_fork, and wait channel functions to make a thread runnable by adding it to the run queue
  • thread_yield: Voluntarily gives up the CPU, moving the current thread to the ready state
  • thread_consider_migration: Checks if threads should be migrated between CPUs for load balancing

CPU Management

  • cpu_create: Initializes a new CPU structure
  • cpu_idle: Called when a CPU has no threads to run
  • thread_bootstrap: Sets up the initial thread and CPU structures during system boot

Synchronization and Wait Channels

thread.c uses wait channels for thread blocking and waking. For details on wait channels, see the wait channel documentation.

Key functions related to wait channels:

  • wchan_sleep: Puts a thread to sleep on a wait channel
  • wchan_wakeone: Wakes up one thread from a wait channel
  • wchan_wakeall: Wakes up all threads from a wait channel

Spinlocks

Spinlocks are used for low-level synchronization in thread.c. For more information, refer to the spinlock documentation.

Important uses of spinlocks in thread.c:

  • Protecting the run queue during thread scheduling
  • Synchronizing access to CPU-specific data
  • Managing inter-processor interrupts (IPIs)

Thread Migration and Load Balancing

The thread_consider_migration function implements a simple load balancing strategy:

  • It checks if the current CPU is busier than others
  • If so, it moves threads to less busy CPUs
  • This helps distribute the workload across available processors

Interaction with Process Management

Threads in OS/161 belong to processes. The thread structure includes a pointer to its parent proc structure (t_proc). The thread_fork function can create threads within the same process or as part of a new process.

Currently, process functionality (declared in kern/include/proc.h and implemented in kern/proc/proc.c) is incomplete in OS/161. Special “in-kernel” processes are sufficiently supported, but many features of user processes are not yet implemented. There is one special kernel process for each CPU.

Important Concepts to Understand

  1. Thread States: Understand how threads move between different states and what causes these transitions.
  2. Context Switching: Grasp the basic idea of how the OS switches between threads, especially in thread_switch.
  3. Synchronization: Notice how thread.c uses wait channels and spinlocks to manage thread synchronization and protect shared data.
  4. CPU Management: Understand how the OS manages multiple CPUs and distributes threads among them.
  5. Scalability Considerations: Think about how the thread management system might behave with many threads and CPUs.
  6. Current Thread and CPU: Understand the significance of curthread and curcpu in the kernel code.

Conclusion

thread.c is a central part of OS/161, implementing core threading functionality. While you may not need to modify this file directly, understanding its structure and functionality is crucial for working on other parts of the OS, especially when implementing synchronization primitives or modifying the scheduler.

As you work on your projects, refer back to this file and the associated header files to understand how your code interacts with the thread management system. Remember that the abstractions provided by thread.c (like wait channels) are tools you'll use in implementing higher-level synchronization primitives.

(When logged in, completion status appears here.)