Exceptions, Processes
and Signals
Computer Systems Organization (Spring 2016)
CSCI-UA 201, Section 2
Instructor: Joanna Klukowska
Slides adapted from
Randal E. Bryant and David R. OHallaron (CMU)
Mohamed Zahran (NYU)
Control Flow
Processors do only one thing:
From startup to shutdown, a CPU simply reads and executes (interprets) a sequence of
instructions, one at a time
This sequence is the CPUs control flow (or flow of control)
Physical control flow
Time
<startup>
inst1
inst2
inst3
instn
<shutdown>
2
Altering the Control Flow
Up to now: two mechanisms for changing control flow:
Jumps and branches
Call and return
React to changes in program state
Insufficient for a useful system:
Difficult to react to changes in system state
Data arrives from a disk or a network adapter
Instruction divides by zero
User hits Ctrl-C at the keyboard
System timer expires
System needs mechanisms for exceptional control flow
Exceptional Control Flow
Exists at all levels of a computer system
Low level mechanisms
1. Exceptions
Change in control flow in response to a system event
(i.e., change in system state)
Implemented using combination of hardware and OS software
Higher level mechanisms
2. Process context switch
Implemented by OS software and hardware timer
3. Signals
Implemented by OS software
4. Nonlocal jumps: setjmp() and longjmp()
Implemented by C runtime library
Exceptions
Exceptions
An exception is a transfer of control to the OS kernel in response to some
event (i.e., change in processor state)
Kernel is the memory-resident part of the OS
Examples of events: Divide by 0, arithmetic overflow, page fault, I/O request completes,
typing Ctrl-C
User code
Event
I_current
I_next
Kernel code
Exception
Exception processing
by exception handler
One of the following happens:
Return to I_current
Return to I_next
Abort
6
Exception Tables
Each type of event has a
unique exception number k
k = index into exception
table
(a.k.a. interrupt vector)
Handler k is called each time
exception k occurs
Exception
numbers
Code for
exception handler 0
Exception
Table
0
1
2
n-1
...
Code for
exception handler 1
Code for
exception handler 2
...
Code for
exception handler n-1
Asynchronous Exceptions (Interrupts)
Caused by events external to the processor
Indicated by setting the processors interrupt pin
Handler returns to next instruction
Examples:
Timer interrupt
Every few ms, an external timer chip triggers an interrupt
Used by the kernel to take back control from user programs
I/O interrupt from external device
Hitting Ctrl-C at the keyboard
Arrival of a packet from a network
Arrival of data from a disk
Synchronous Exceptions
Caused by events that occur as a result of executing an instruction:
Traps
Intentional
Examples: system calls (requests for services from the kernel)Returns control to next
instruction
Faults
Unintentional but possibly recoverable
Examples: page faults (recoverable), protection faults (unrecoverable), floating point
exceptions
Either re-executes faulting (current) instruction or aborts
Aborts
Unintentional and unrecoverable
Examples: illegal instruction, parity error, machine check
Aborts current program
System Calls
Each x86-64 system call has a unique ID number (assigned by the
operating system)
Examples:
Number
Name
Description
read
Read file
write
Write file
open
Open file
close
Close file
stat
Get info about file
57
fork
Create process
59
execve
Execute a program
60
_exit
Terminate process
62
kill
Send signal to process
10
System Call Example: Opening File
User calls: open(filename, options)
Calls __open function, which invokes system call instruction syscall
00000000000e5d70 <__open>:
...
e5d79: b8 02 00 00 00
mov $0x2,%eax # open is syscall #2
e5d7e: 0f 05
syscall
# Return value in %rax
e5d80: 48 3d 01 f0 ff ff cmp $0xfffffffffffff001,%rax
...
e5dfa: c3
retq
User code
Kernel code
syscall
cmp
Exception
Open file
Returns
%rax contains syscall number
Other arguments in %rdi, %
rsi, %rdx, %r10, %r8, %r9
Return value in %rax
Negative value is an error
corresponding to negative
errno
11
Fault Example: Page Fault
User writes to memory location
That portion (page) of users memory
is currently on disk
80483b7:
User code
movl
c7 05 10 9d 04 08 0d
int a[1000];
main ()
{
a[500] = 13;
}
movl
$0xd,0x8049d10
Kernel code
Exception: page fault
Return and
reexecute movl
Copy page from
disk to memory
12
Fault Example: Invalid Memory Reference
intSIGSEGV
a[1000]; signal to user process
Sends
main ()
User
{ process exits with segmentation fault
a[5000] = 13;
}
80483b7:
c7 05 60 e3 04 08 0d
User code
movl
movl
$0xd,0x804e360
Kernel code
Exception: page fault
Detect invalid address
Signal process
13
Processes
14
Processes
A process is an instance of a running program.
One of the most profound ideas in computer science
Not the same as program or processor
Process provides each program with two key abstractions:
Logical control flow
Each program seems to have exclusive use of the CPU
Provided by kernel mechanism called context switching
Memory
Stack
Heap
Data
Code
Private address space
Each program seems to have exclusive use of main memory.
Provided by kernel mechanism called virtual memory
CPU
Registers
15
Multiprocessing: The Illusion
Memory
Stack
Heap
Data
Code
Memory
Stack
Heap
Data
Code
Memory
Stack
Heap
Data
Code
CPU
CPU
CPU
Registers
Registers
Registers
Computer runs many processes simultaneously
Applications for one or more users
Web browsers, email clients, editors,
Background tasks
Monitoring network & I/O devices
16
Multiprocessing Example
Running program top on my Ubuntu desktop
System has 326 processes, 3 are running, 323 are sleeping
Identified by Process ID (PID)
17
Multiprocessing: The One-Core Reality
Memory
Stack
Heap
Data
Code
Stack
Heap
Data
Code
Saved
registers
Saved
registers
Stack
Heap
Data
Code
Saved
registers
CPU
Registers
Single processor executes multiple processes
concurrently
Process executions interleaved (multitasking)
Address spaces managed by virtual memory system (later in
course)
Register values for nonexecuting processes saved in memory
18
Multiprocessing: The One-Core Reality
Memory
Stack
Heap
Data
Code
Stack
Heap
Data
Code
Saved
registers
Saved
registers
Stack
Heap
Data
Code
Saved
registers
CPU
Registers
Save current registers in memory
19
Multiprocessing: The One-Core Reality
Memory
Stack
Heap
Data
Code
Stack
Heap
Data
Code
Saved
registers
Saved
registers
Stack
Heap
Data
Code
Saved
registers
CPU
Registers
Schedule next process for execution
20
Multiprocessing: The One-Core Reality
Memory
Stack
Heap
Data
Code
Stack
Heap
Data
Code
Saved
registers
Saved
registers
Stack
Heap
Data
Code
Saved
registers
CPU
Registers
Load saved registers and switch address space (context switch)
21
Multiprocessing: The Multi-Core Reality
Memory
Stack
Heap
Data
Code
Stack
Heap
Data
Code
Saved
registers
Saved
registers
CPU
CPU
Registers
Registers
Stack
Heap
Data
Code
Saved
registers
Multicore processors
Multiple CPUs on single chip
Share main memory (and some of the caches)
Each can execute a separate process
Scheduling of processors onto cores
done by kernel
(But we still will have more processes running
than there are cores on a machine.)
22
Concurrent Processes
Each process is a logical control flow.
Two processes run concurrently (are concurrent) if their flows overlap in
time
Otherwise, they are sequential
Examples (running on single core):
Concurrent: A & B, A & C
Sequential: B & C
Process A
Process B
Process C
Time
23
User View of Concurrent Processes
Control flows for concurrent processes are physically disjoint in time
However, we can think of concurrent processes as running in parallel with
each other
Process A
Process B
Process C
Time
24
Context Switching
Processes are managed by a shared chunk of memory-resident OS code
called the kernel
Important: the kernel is not a separate process, but rather runs as part of some existing
process.
Control flow passes from one process to another via a context switch
Process A
Process B
user code
kernel code
Time
context switch
user code
kernel code
context switch
user code
25
Process Control
(ways of manipulating processes)
26
System Call Error Handling
On error, Linux system-level functions typically return -1 and set global
variable errno to indicate cause.
Hard and fast rule:
You must check the return status of every system-level function
Only exception is the handful of functions that return void
Example:
if ((pid = fork()) < 0) {
fprintf(stderr, "fork error: %s\n", strerror(errno));
exit(0);
}
27
Error-reporting functions
Can simplify somewhat using an error-reporting function:
void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
if ((pid = fork()) < 0)
unix_error("fork error");
28
Error-handling Wrappers
We simplify the code we present to you even further by using Stevens-style
error-handling wrappers:
pid_t Fork(void)
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
}
pid = Fork();
29
Obtaining Process IDs
pid_t getpid(void)
Returns PID of current process
pid_t getppid(void)
Returns PID of parent process
30
Creating and Terminating Processes
From a programmers perspective, we can think of a process as being in one of
three states
Running
Process is either executing, or waiting to be executed and will eventually be scheduled (i.e.,
chosen to execute) by the kernel
Stopped
Process execution is suspended and will not be scheduled until further notice (next lecture
when we study signals)
Terminated
Process is stopped permanently
31
Terminating Processes
Process becomes terminated for one of three reasons:
Receiving a signal whose default action is to terminate (next lecture)
Returning from the main routine
Calling the exit function
void exit(int status)
Terminates with an exit status of status
Convention: normal return status is 0, nonzero on error
Another way to explicitly set the exit status is to return an integer value from the main routine
exit is called once but never returns.
32
Creating Processes
Parent process creates a new running child process by
calling fork
int fork(void)
Returns 0 to the child process, childs PID to parent process
Child is almost identical to parent:
Child get an identical (but separate) copy of the parents virtual address space.
Child gets identical copies of the parents open file descriptors
Child has a different PID than the parent
fork is interesting (and often confusing) because
it is called once but returns twice
33
fork Example
int main()
{
pid_t pid;
int x = 1;
pid = Fork();
if (pid == 0) { /* Child */
printf("child : x=%d\n", ++x);
exit(0);
}
Call once, return twice
Concurrent execution
Cant predict execution
order of parent and child
Duplicate but separate address
space
x has a value of 1 when fork
returns in parent and child
Subsequent changes to x
are independent
Shared open files
stdout is the same in both
parent and child
/* Parent */
printf("parent: x=%d\n", --x);
exit(0);
}
linux> ./fork
parent: x=0
child : x=2
34
Modeling fork with Process Graphs
A process graph is a useful tool for capturing the partial ordering of
statements in a concurrent program:
Each vertex is the execution of a statement
a -> b means a happens before b
Edges can be labeled with current value of variables
printf vertices can be labeled with output
Each graph begins with a vertex with no inedges
child: x=2
printf
exit
x==1
main
fork
parent: x=0
printf
exit
Child
Parent
35
Process Graph Example
int main()
{
pid_t pid;
int x = 1;
pid = Fork();
if (pid == 0) { /* Child */
printf("child : x=%d\n", ++x);
exit(0);
}
child: x=2
printf
exit
x==1
main
parent: x=0
fork
printf
exit
Child
Parent
/* Parent */
printf("parent: x=%d\n", --x);
exit(0);
}
36
Interpreting Process Graphs
Original graph:
child: x=2
printf
parent: x=0
x==1
main
exit
fork
printf
exit
Feasible total ordering:
Relabled graph:
Infeasible total ordering:
a
37
fork Example: Two consecutive forks
Bye
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
printf
L1
printf
L0
printf
Bye
fork
L1
fork
Feasible output:
L0
L1
Bye
Bye
L1
Bye
Bye
printf
printf
Bye
printf
Bye
fork
printf
Infeasible output:
L0
Bye
L1
Bye
L1
Bye
Bye
38
fork Example: Nested forks in parent
void fork4()
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
Bye
printf
L0
printf
Bye
printf
L1
fork
L2
printf fork
Feasible output:
L0
L1
Bye
Bye
L2
Bye
Bye
printf printf
Infeasible output:
L0
Bye
L1
Bye
Bye
L2
39
fork Example: Nested forks in children
void fork5()
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
L2
Bye
printf printf
L1
printf
L0
printf
fork
Bye
printf
Bye
fork
printf
Feasible output:
L0
Bye
L1
L2
Bye
Bye
Infeasible output:
L0
Bye
L1
Bye
Bye
L2
40
Reaping Child Processes
Idea
When process terminates, it still consumes system resources
Examples: Exit status, various OS tables
Called a zombie
Living corpse, half alive and half dead
Reaping
Performed by parent on terminated child (using wait or waitpid)
Parent is given exit status information
Kernel then deletes zombie child process
What if parent doesnt reap?
If any parent terminates without reaping a child, then the orphaned child will be reaped by init
process (pid == 1)
So, only need explicit reaping in long-running processes
e.g., shells and servers
(although you should be a good citizen and collect your zombies if possible)
41
Zombie
Example
void fork7() {
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1)
; /* Infinite loop */
}
}
linux> ./forks 7 &
[1] 6639
Running Parent, PID = 6639
Terminating Child, PID = 6640
linux> ps
PID TTY
TIME CMD
6585 ttyp9
[Link] tcsh
6639 ttyp9
[Link] forks
6640 ttyp9
[Link] forks <defunct>
6641 ttyp9
[Link] ps
linux> kill 6639
[1]
Terminated
linux> ps
PID TTY
TIME CMD
6585 ttyp9
[Link] tcsh
6642 ttyp9
[Link] ps
ps shows child process as
defunct (i.e., a zombie)
Killing parent allows child to
be reaped by init
42
Non-terminating
Child Example
void fork8()
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",
getpid());
while (1)
; /* Infinite loop */
} else {
printf("Terminating Parent, PID = %d\n",
getpid());
exit(0);
}
}
linux> ./forks 8
Terminating Parent, PID = 6675
Running Child, PID = 6676
linux> ps
PID TTY
TIME CMD
6585 ttyp9
[Link] tcsh
6676 ttyp9
[Link] forks
6677 ttyp9
[Link] ps
linux> kill 6676
linux> ps
PID TTY
TIME CMD
6585 ttyp9
[Link] tcsh
6678 ttyp9
[Link] ps
Child process still active even
though parent has terminated
Must kill child explicitly, or else will
keep running indefinitely
43
wait: Synchronizing with Children
Parent reaps a child by calling the wait function
int wait(int *child_status)
suspends current process until one of its children terminates
return value is the pid of the child process that terminated
if child_status != NULL, then the integer it points to will be set to a
value that indicates reason the child terminated and the exit status:
checked using macros defined in wait.h
WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG,
WIFSTOPPED, WSTOPSIG, WIFCONTINUED
see textbook for details
44
wait: Synchronizing with Children
void fork9() {
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
exit(0);
} else {
printf("HP: hello from parent\n");
wait(&child_status);
printf("CT: child has terminated\n");
}
printf("Bye\n");
HC
printf
HP
fork printf
exit
CT
Bye
wait printf
Feasible output:
HC
HP
CT
Bye
Infeasible output:
HP
CT
Bye
HC
45
Another wait Example
If multiple children completed, will take in arbitrary order
Can use macros WIFEXITED and WEXITSTATUS to get information about exit
status
void fork10() {
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
46
execve: Loading and Running Programs
int execve(char *filename, char *argv[], char *envp[])
Loads and runs in the current process:
Executable file filename
Can be object file or script file beginning with #!interpreter
(e.g., #!/bin/bash )
with argument list argv
By convention argv[0]==filename
and environment variable list envp
name=value strings (e.g., USER=droh)
getenv, putenv, printenv
Overwrites code, data, and stack
Retains PID, open files and signal context
(the current process is gone, it is now running different program)
Called once and never returns
except if there is an error
47
Structure of
the stack when
a new program
starts
argv
(in %rsi)
argc
(in %rdi)
Null-terminated
environment variable strings
Bottom of stack
Null-terminated
command-line arg strings
envp[n] == NULL
envp[n-1]
...
environ
(global var)
envp[0]
argv[argc] = NULL
argv[argc-1]
envp
(in %rdx)
...
argv[0]
Stack frame for
libc_start_main
Top of stack
Future stack frame for
main
48
execve Example
Executes "/bin/ls lt /usr/include" in child process
using current environment:
myargv[argc] = NULL
(argc == 3)
myargv
environ
myargv[2]
myargv[1]
myargv[0]
envp[n] = NULL
envp[n-1]
envp[0]
"/usr/include"
"-lt"
"/bin/ls"
"PWD=/usr/droh"
"USER=droh"
if ((pid = Fork()) == 0) {
/* Child runs program */
if (execve(myargv[0], myargv, environ) < 0) {
printf("%s: Command not found.\n", myargv[0]);
exit(1);
}
}
49
Summary
Exceptions
Call fork
One call, two returns
Events that require nonstandard control
flow
Generated externally (interrupts) or
internally (traps and faults)
Process completion
Call exit
One call, no return
Processes
At any given time, system has multiple
active processes
Spawning processes
Only one can execute at a time on a
single core, though
Reaping and waiting for
processes
Call wait or waitpid
Each process appears to have total
control of
processor + private memory space
Loading and running programs
Call execve (or variant)
One call, (normally) no return
50