CS 208 s22 — The Process Model and Exceptional Control Flow

1 Intro

1.1 Normal control flow

  • control flow is the successive transfer of control from one instruction to the next
    • each control transfer within this flow involves going from executing \(I_k\) at address \(a_k\) to instruction \(I_{k+1}\) at address \(a_{k+1}\)
    • \(I_k\) may be adjacent to \(I_{k+1}\) in memory, but instructions like jumps, calls, and returns can redirect to different regions of stored instructions

1.2 Exceptional control flow (ECF)

  • jumps, calls, and returns only react to internal program state, ECF allows all levels of the system to react to abrupt external changes
  • underlies mechanisms for I/O, processes, virtual memory
  • involved in how application interacts with the OS (system calls)
  • an important part of writing event-driven programs like Unix shells and web servers
  • basic building block for concurrency

1.3 The Operating System Kernel

  • the kernel is the core part of the operating system
    • it is trusted and has unrestricted access to the hardware (e.g., is free to perform whatever operations in memory and on disk it wants)
    • this trusted state is called kernel-mode
  • all other programs run in user-mode, which restricts access to the hardware

kernelmode.png

2 Exceptions

  • partly hardware, partly software
    • thus, details vary by system (general ideas the same)
  • exception is abrupt change in control flow in response to change in processor state
    • change in state called an event
    • Java/Python exceptions are not the same, they react to program state and don't involve the hardware

exception.png

  • indirect jump to exception handler procedure through exception table

exception-table.png

2.1 Exception Handling

  • each possible exception assigned unique exception number
    • either by the processor designers or system kernel, depending on the type of exception
  • exception table initialized at boot
    • address store in special exception table base register
  • differences from a normal procedure call
    • return address pushed on the stack can be current or next instruction, depending on exception
    • other processor state pushed onto stack to facilitate restoring pre-expception context
    • may use the kernel's stack instead of user's
    • run in kernel mode, given full access to system resources

2.2 Classes of Exceptions

Class Cause Async/sync Return behavior
interrupt signal from I/O device async always return to next instruction
trap intentional exception sync always return to next instruction
fault potentially recoverable error sync might return to current instruction
abort nonrecoverable error sync never returns

2.2.1 Interrupts

interrupt.png

  • asynchronous since exception arises from I/O rather than specific instruction
    • hitting ctrl-C, clicking the mouse, data arrives from the network, timer interrupt

2.2.2 Traps and System Calls

trap.png

  • interface between user programs and kernel: the system call
  • request for services like reading a file, creating a new process, exiting current process
  • from the programmer's perspective appears like any other function call, implementation very different
  • returns to next instruction

2.2.3 Faults

fault.png

  • page faults, segment protection faults, divide-by-zero
// page fault example
int a[1000];
int main() {
    a[500] = 13; // this portion of memory is currently only on disk
}
  • mov instruction interrupted while page fault handler loads corresponding page into memory
  • mov restarted afterwards
// seg fault example
int a[1000];
int main() {
    a[5000] = 13; // this refers to an invalid address
}
  • mov instruction interrupted while page fault handler detects invalid address
  • sends SIGSEGV signal to use process, causing it to exit in a seg fault
  • handler tries to correct, returns to current instruction if it can
  • otherwise returns to abort routine

2.2.4 Aborts

abort.png

  • fatal errors, application terminated
    • hardware failure

2.3 Exceptions on Linux/x86-64 Systems

  • divide error: divide by 0 or result too big for destination, Unix opts to abort ("floating exceptions")
  • general protection fault: access to undefined area of virtual memory or attempt to write to read-only segment, no recovery
    • notorious "segmentation faults"
  • page fault: segment of memory must be loaded from disk into memory, instruction restarted
  • machine check: fatal hardware error, always aborts
  • C standard library provides wrappers for system calls (system level functions)

3 Processes

  • an instance of a program in execution
  • each process has the illusion of exclusive use of the processor and memory

process-illusion.png

3.1 Logical Control Flow

  • processes take turns using the processor
    • a process is temporarily suspended or preempted when other processes take their turns
  • this switching is hidden from the processes
    • only detectable through precise measurment of time between instructions

context-switch1.png

context-switch2.png

context-switch3.png

3.2 Concurrent Flows

  • process flows overlap
  • switching between concurrent processes is called multitasking or time slicing
  • independent of whether there are multiple processors
  • two processes running simultaneously on different processors are said to be running in parallel

concurrent-processes.png

3.3 Private Address Space

  • illusion of uniform layout of exclusive memory for each process

3.4 User and Kernel Modes

  • typically control via mode bit in some special register
  • kernel mode can execute any instruction, access any memory
  • user mode prevented from executing privilaged instructions or accessing the kernel area of memory
    • instead must operate indirectly through system calls
  • exception handlers run in kernel mode

3.5 Context Switches

  • kernel maintains the context for each process
    • everything needed to restart a preempted process
    • register values, instruction pointer, user's stack, and other data structures
  • kernel switches from one process to another via context switch: saving current process context and loading in a previously saved one for a different process
    • the decisions for doing these switches are collectively called scheduling
  • contexting switching often occurs when a process is waiting for I/O (e.g., disk read) or when an interrupt occurs

context-switch-diagram.png

4 System Call Error Handling

  • good practice to always check for errors after making a system call
  • possible to define convenient wrapper functions that abstract the error checking

5 Process Control

5.1 Obtaining Process IDs

  • each process has a unique id (PID)
  • getpid returns PID of calling process, getppid returns PID of parent of calling process (i.e., process that created calling process)

5.2 Creating and Terminating Processes

  • a process is either running, stopped, or terminated
    • a stopped process is suspended until it receives a SIGCONT signal and resumes
    • a process is terminated by a signal, by returning from main, or by calling exit
      • exit takes an int determines the exit status of the process
      • exit status also set by the return value from main
      • exit status used to indicate whether a process completed successfully or ended in some kind of error state
  • a parent process creates a new child process by calling fork
    • child process get a separate copy of the parent's private address space and access to any file descriptors open when fork was called (differeny PIDs)
      • including code, child runs parent's code from the point of the fork call
    • fork is weird: called once, returns twice (in the parent and in the child)
      • returns child's PID to parent, returns 0 to child (providing a way for the code to check which process it is)
    • kernel free to interleave the execution of parent and child, meaning we can't assume anything about which will run first, etc.
    • multiple calls to fork can get very difficult to reason about

5.3 Reaping Child Processes

  • a terminated process persists until it is reaped by its parent
    • reaping passes the child's exit status to the parent
    • an un-reaped process is called a zombie
  • when a parent terminates, it's non-terminated child processes become children of the init process
    • inti reaps any zombie children
    • necessary for long-running processes like shells to reap children, as zombies still take up memory
  • a process waits for a child to terminate or stop by calling waitpid
    • by default it suspends calling process until a child process in its wait set terminates
    • returns immediately if a child has already terminated

5.3.1 Wait Set

  • waitpid takes pid arugment
    • if pid > 0, wait set is single process with that PID
    • if pid = -1, wait set is all child processes

5.3.2 Modifying Default Behavior

  • set options arugment to combinations (i.e., or-ing) of:
    • WNOHANG: return immediately with value 0 if no child process has terminated yet
    • WUNTRACED: wait for child process to terminate or stop
    • WCONTINUED: wait for child process to terminate or continue via SIGCONT signal

5.3.3 Checking the Exit Status of a Reaped Child

  • provide non-null pointer status argument, waitpid will store child's exit status as the value pointed to by status
  • wait.h provides macros for interpreting exit status

5.3.4 Error Conditions

  • waitpid returns -1 if there are no children or if it was interrupted

5.4 Putting Processes to Sleep

  • sleep stops process for a given number of seconds
  • pause stops process until it receives a signal

5.5 Loading and Running Programs

  • function execve runs and executable with an argument list and environment variable list
    • does not return unless there's an error

argv.png

envp.png

  • global variable environ also references environment variables
  • functions getenv, setenv, and unsetenv manipulate environment array

5.6 Using fork and execve

  • neat mini-shell example, p. 754-56