COMS 4995 Advanced Systems Programming

Interrupts

Processor Modes

CPU runs with a privilege mode that determines what kind of operations it can perform:

A userspace program (running in user mode) can issue a syscall (e.g. read()) to enter kernel mode so that it can perform a privileged operation (e.g. issue I/O request).

priv-mode

Syscalls act as predefined entry-points into the kernel. They allow userspace programs to “trap” into the kernel to perform a privileged operation and then “return-from-trap” back to user mode.

User-invoked syscalls aren’t the only way for the kernel to gain control of the system. There are many instances where the kernel must gain control regardless of what the user program is doing.

Interrupts

Interrupts are a mechanism that stops the CPU from executing the current stream of instructions and makes it jump to a predetermined piece of OS kernel code to perform certain tasks.

There are three classes of interrupts:

  1. Hardware interrupts
    • asynchronous (i.e., can arrive at any time, regardless of what the CPU is doing)
    • e.g., network packet arrival, timer interrupts, key press, mouse click
    • Hardware clock will periodically deliever timer interrupts. The OS kernel can use these to determine if a process has run for too long and possibly perform a context switch to another process.
  2. Exceptions & Faults
    • synchronous – they happen as a result of executing some instruction
    • e.g., dividing by zero, segmentation fault, page fault
  3. Software interrupts
    • synchronous – program code explicitly raising an interrupt
    • x86 int <code> instruction: raise software interrupt
      • x86-32 system call: int 0x80 (see syscall dispatch section below)
      • Debugger software breakpoints: int3 (see breakpoint section below)

The psuedocode below demonstrates how the CPU handles interrupts while executing instructions:

while (1) {
    if (interrupt or exception) {
        n = interrupt/exception type
        call interrupt handler n
    }

    fetch next instruction
    if (instruction == int n)
        call interrupt handler n
    else
        run instruction
}

Note: do not confuse interrupts with userspace signals!

Linux System Call Dispatch

The diagram below illustrates how system calls are invoked in x86-32:

dispatch

User program invokes read():

  1. libc system call wrapper invokes software interrupt 0x80 (system call)
    • Places syscall number __NR_read into %eax register
  2. Trap into kernel mode, look up 0x80 in Interrupt Descriptor Table (IDT)
  3. Jump to interrupt 0x80’s handler: system_call()
  4. system_call() looks up __NR_read in sys_call_table
  5. Unpack registers, call __NR_read’s handler: sys_read()
  6. Perform read()’s real work in kernel’s sys_read()
    • file entry management, I/O requests, copying data, etc.

int 0x80 was how system calls were invoked in x86-32. x86-64 has a dedicated syscall instruction so it no longer relies on the software interrupt mechanism to invoke system calls.

Debugger Breakpoints

Debuggers allow users to stop execution at arbitrary points in the debugged program by defining “breakpoints”. One way to implement breakpoints in x86 is by using the int3 instruction as follows:

These breakpoints are known as “software breakpoints” because they are implemented by the debugger using software instructions. Many CPUs offer special debug registers that can be used to implement “hardware breakpoints” (i.e., watchpoints). These are much more powerful than software breakpoints – it allows debuggers to specify memory addresses and what kinds of operations should cause the breakpoint to be triggered (e.g, read/write/execute).


Last updated: 2024-03-18