CFS and Signals

Why This Matters

Every real operating system must answer two questions that xv6 only partially addresses: How do we share the CPU fairly across many different kinds of processes? and How do we let the kernel notify a running process about an event without the process having to ask? Linux's Completely Fair Scheduler (CFS) solves the first; the Unix signal mechanism solves the second. Understanding both gives you the conceptual tools to reason about scheduling and asynchronous communication in any Unix-like system.


Quick Recap: The xv6 Scheduler

The xv6 scheduler is a simple round-robin loop in proc.c: scheduler(). It scans the process table, picks the next RUNNABLE process, and calls swtch() to swap CPU registers and stack. A running process eventually gives up the CPU through one of these paths:

Path Trigger
trap() β†’ yield() Timer interrupt fires
wait(), consoleread(), iderw() β†’ sleep() Process blocks on I/O
exit() Process terminates

All paths ultimately call sched() / swtch() to hand control back to the scheduler thread.


Time Slices and Their Tradeoffs

A time slice is the maximum period a process runs uninterrupted before the scheduler may preempt it. In xv6, the time slice is one tick (one timer interrupt period, configured in trap.c).

Choosing the right time slice is a classic tension:

Time slice Effect
Too long Poor interactive responsiveness β€” a user sees lag
Too short High context-switch overhead β€” CPU wastes time saving/restoring state

A Concrete Example

Imagine two processes sharing the CPU:

What we want:

A fixed time slice cannot satisfy both goals perfectly. This motivates a smarter scheduler.


Linux's Completely Fair Scheduler (CFS)

CFS was the default scheduler in Linux from kernel v2.6.23 through v6.6. Its core idea: at any moment, every runnable process of the same priority should have received exactly the same amount of CPU time.

Virtual Runtime (vruntime)

CFS tracks each process's virtual runtime β€” a weighted measure of how much CPU time it has consumed. Concretely:

sched(p):  p->vruntime += elapsed_cpu_time
next = process with min(vruntime) among RUNNABLE processes

CFS uses a red-black tree (ordered by vruntime) so picking the minimum is O(log n).

Why This Solves the Text-Editor / Encoder Problem

No fixed time slice needed β€” CFS dynamically adapts.


Unix / Linux Signals

What Is a Signal?

A signal is a software interrupt sent by the kernel (or by another process) to notify a process of an event asynchronously. The process doesn't need to poll; the kernel delivers the signal at the next opportunity.

Common signals:

Number Name Default action Typical cause
2 SIGINT Terminate Ctrl-C at terminal
9 SIGKILL Terminate (uncatchable) kill -9 <pid>
18 SIGTSTP Stop process Ctrl-Z at terminal
19 SIGCONT Continue kill -CONT <pid>
15 SIGTERM Terminate (catchable) kill <pid>

Default vs. User-Defined Behavior

Each signal has a default action (terminate, stop, ignore, etc.). A process can override this with a custom signal handler β€” a function that runs when the signal arrives.

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void handler(int signal) {
    printf("Got signal %d\n", signal);
}

int main(int argc, char **argv) {
    signal(SIGTERM, handler);   // install handler for SIGTERM
    while (1) sleep(10);
}

Now sending SIGTERM prints a message instead of killing the process. Note: SIGKILL cannot be caught or ignored β€” the kernel always terminates the process.

Signals vs. Hardware Interrupts

Hardware interrupt Signal
Source Hardware device Kernel or another process
Recipient CPU (kernel handles) A specific process
Mechanism Interrupt vector, IDT Kernel sets a pending flag in the process's PCB
When delivered Immediately (hardware) At next kernel→user transition

Signals are sometimes called software interrupts because they share the asynchronous notification model, but they operate at the process level, not the hardware level.

xv6 Signal-Related System Calls

xv6 (as extended in homework 5) provides three signal-related syscalls:

int  kill(int pid, int signal);             // send signal to pid
void signal(int signum, void (*handler)(int)); // install a handler
void alarm(int secs);                        // schedule SIGALRM after secs seconds

kill marks the target process as having a pending signal; the signal is actually delivered the next time that process returns from the kernel to user space (at a trap return).


Key Takeaways

Practice

  1. What does CFS stand for, and what is its primary scheduling goal?
  2. In CFS, which process does the scheduler choose to run next?
  3. Which signal cannot be caught or ignored by a process?
  4. A system has two processes: a text editor (mostly blocked on keyboard input) and a video encoder (running continuously). After the text editor wakes up from waiting for a keypress, what does CFS do?
  5. Explain the tradeoff involved in choosing a time slice length. What happens if the time slice is too long? Too short?
  6. How do signals differ from hardware interrupts?
  7. Write a brief C snippet (or pseudocode) that installs a custom handler for SIGTERM, then explain when the handler runs.
  8. In xv6, a signal is delivered when a process returns from kernel mode to user mode (at a trap return). Why is this the right place to deliver signals rather than immediately when kill() is called?