Linking - Computer System Laboratory

Download Report

Transcript Linking - Computer System Laboratory

Computer System
Chapter 8. Exceptional Control Flow
Lynn Choi
Korea University
Control Flow
Computers 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 system’s physical control flow (or flow of control).
Physical control flow
Time
<startup>
inst1
inst2
inst3
…
instn
<shutdown>
Altering the Control Flow
Up to Now: two mechanisms for changing control flow:
Jumps and branches
Call and return using the stack discipline.
Both react to changes in program state.
Insufficient for a useful system
System should react to changes in system state
Data arrives from a disk or a network adapter.
Instruction divides by zero
User hits ctl-alt-delete at the keyboard
System timer expires
Parent process that creates child processes must be notified when their children
terminate
System needs mechanisms for “exceptional control flow”
Exceptional Control Flow
Mechanisms for exceptional control flow exists at all levels of a computer system.
At the hardware level
Exceptions: caused by an internal event created by the currently running process
Interrupts
Caused by an external event asynchronously
Implemented by combination of hardware and OS software
At the operating system level
Process context switch
System calls
Provide applications with entry points into the operating system
At the application level
Signal
A process can send a signal to another process that abruptly transfers control to a signal
handler
Nonlocal jumps (setjmp/longjmp)
React to errors, which sidesteps the usual stack discipline and make nonlocal jumps to
arbitrary locations (implemented by C language runtime library)
Exceptions
An exception is a transfer of control to the OS in response to some event (i.
e., change in processor state)
Implemented partly by the hardware and partly by the operating system
Event might be related to the execution of current instruction (exception)
Event might be unrelated to the execution of current instruction (interrupt)
On event, the processor makes an indirect procedure call (to the exception handler)
through a jump table called exception table
User Process
event
current
next
OS
exception
exception processing
by exception handler
exception
return (optional)
(Synchronous) Exceptions
Caused by events that occur as a result of executing an instruction:
Traps
Intentional exceptions
Examples: system calls, breakpoints, special instructions
Executing the syscall instruction causes a trap to an exception handler that decodes the
argument and calls the appropriate kernel routine (run in kernel mode)
Returns control to “next” instruction
Faults
Unintentional but possibly recoverable
Examples: page faults (recoverable), protection faults (unrecoverable).
Either re-executes faulting (“current”) instruction or terminate the process
Aborts
Unintentional and unrecoverable fatal errors
Examples: parity error, machine check.
Aborts the current process, and probably the entire system
Interrupt (Asynchronous Exception)
Caused by events external to the processor
Indicated by setting the processor’s interrupt pin
Handler returns to “next” instruction.
Examples:
I/O interrupts
Hitting ctl-c at the keyboard, arrival of a packet from a network, arrival of a data sector
from a disk
Hard reset interrupt: hitting the reset button
Soft reset interrupt: hitting ctl-alt-delete on a PC
(1) Interrupt pin
goes high during I
curr
execution of
Inext
current instruction
(2) Control passes
to handler after current
instruction finishes
(3) Interrupt
handler runs
(4) Handler
returns to
next instruction
(External) Interrupt
Interrupt Classification
Maskable interrupt
Can be disabled/enabled by an instruction
Generated by asserting INTR pin
External interrupt controllers
Intel 8259 PIC (programmable interrupt controller) delivers the interrupt vectors
on the system bus during interrupt acknowledge cycle
Non-maskable interrupt (NMI)
Cannot be disabled by program
Received on the processor’s NMI# input pin
Exception Handling Procedure
Exception handling procedure
Flush all the instructions fetched subsequent to the instruction causing the
condition from the pipeline
Drain the pipeline
Complete all outstanding write operations prior to the faulting instruction
Save the PC of the next instruction to execute
Also need to save the necessary registers and stack pointers to allow it to
restore itself to its state
Vector the interrupt
Fetch instructions from the ISR and service the interrupt
Return from the interrupt
Exception vs. Procedure Call
Differences
As with procedure call, CPU pushes return address on the stack before jumping to
the handler. But, the return address is either the current instruction or the next
instruction depending on the event.
CPU pushes some additional processor state onto the stack such as EEFLAGS
register that contains condition codes
All of these states are pushed onto the kernel’s stack rather than on the user’s
stack
Exception handlers run in kernel mode
After the hander processes the event, the handler (optionally) returns to the
interrupted program by executing a special “return from interrupt” instruction,
which pops states back into the registers (restoring user states)
Interrupt (Exception) Vectors
An exception table is a jump table where entry k contains the address of the
handler for exception k
Start address of the exception
table is contained in a special CPU Exception
numbers
register called the exception table
base register.
Each type of event has a unique
exception number k.
interrupt
Using the exception number as an
vector
index, you can fetch the address of 0
the corresponding handler.
1
The index of the jump table (or the 2
...
corresponding entry in the jump
table) is called interrupt vector n-1
Handler k is called each time
exception k occurs.
code for
exception handler 0
code for
exception handler 1
code for
exception handler 2
...
code for
exception handler n-1
Trap Example
Opening a File
User calls open(filename, options)
0804d070 <__libc_open>:
. . .
804d082:
cd 80
int
804d084:
5b
pop
. .Function
.
open executes system call instruction int
$0x80
%ebx
OS must find or create file, get it ready for reading or writing
Returns integer file descriptor
User Process
int
pop
OS
exception
Open file
return
Fault Example #1
Memory Reference
User writes to memory location
That portion (page) of user’s memory is currently on
disk
80483b7:
c7 05 10 9d 04 08 0d
movl
int a[1000];
main ()
{
a[500] = 13;
}
$0xd,0x8049d10
Page handler must load page into physical memory
Returns to faulting instruction
Successful on second try
User Process
event
movl
OS
page fault
return
Create page and load
into memory
Fault Example #2
int a[1000];
main ()
{
a[5000] = 13;
}
Memory Reference
User writes to memory location
Address is not valid
80483b7:
c7 05 60 e3 04 08 0d
movl
$0xd,0x804e360
Page handler detects invalid address
Sends SIGSEG signal to user process
User process exits with “segmentation fault”
User Process
event
movl
OS
page fault
Detect invalid address
Signal process
Exceptions in Intel Processors
Pentium system can have up to 256 different exception types
The first 32 exceptions (exception 0-31) are defined by the Pentium architecture
and identical to any Pentium-class system.
Exceptions 32-255 correspond to traps and interrupts defined by OS
Examples
Exception 0
Divide by 0
Fault
Unix does not attempt to recover from divide errors, opting instead to abort program.
Unix shell report divide errors as “floating point exceptions”.
Exception 13
General protection fault
Fault
Program references an undefined area of virtual memory, or write into read-only
segment. Unix shell report as “segmentation fault”.
Exception 14
Exception 18
32-127
128 (0x80)
Page fault
Machine check
OS-defined exceptions
System call
Fault
Abort
Interrupt or trap
Trap
System calls are provided via a trapping instruction called INT n, where n can be the
index of any of the 256 entries in the exception table (software interrupt).
129-255
OS-defined exceptions
Interrupt or trap
Processes
Def: A process is an instance of a program in execution.
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.
Private address space
Each program seems to have exclusive use of main memory.
How are these Illusions maintained?
Multitasking: process executions are interleaved
In reality, many other programs are running on the system.
Processes take turns in using the processor
Each time period that a process executes a portion of its flow is called a time slice
Virtual memory: memory system provides a private space for each process
The private space is also called the virtual address space, which is a linear array of
bytes, addressed by n bit virtual address (0, 1, 2, 3, … 2n-1)
Private Address Spaces
Each process has its own private address space.
0xffffffff
kernel virtual memory
(code, data, heap, stack)
0xc0000000
0x40000000
user stack
(created at runtime)
read/write segment
(.data, .bss)
0
%esp (stack pointer)
memory mapped region for
shared libraries
run-time heap
(managed by malloc)
0x08048000
memory
invisible to
user code
read-only segment
(.init, .text, .rodata)
unused
brk
loaded from the
executable file
Logical Control Flows
Each process has its own logical control flow
Process A
Time
Process B
Process C
Concurrent Processes
Concurrent processes
Two processes run concurrently (are concurrent) if their flows overlap in time.
Otherwise, they are sequential.
Process A
Examples:
Process B
Process C
Concurrent: A & B, A & C
Time
Sequential: B & C
Control flows for concurrent processes are physically disjoint in time.
However, we can think of concurrent processes as logically running in parallel
with each other.
Process A
Time
Process B
Process C
Context Switching
Processes are managed by a shared chunk of OS code called the kernel
Important: the kernel is not a separate process, but rather runs as part of some user
process
Processors typically provide this capability with a mode bit in some control register
User mode and kernel mode
If the mode bit is set, the process is running in kernel mode (supervisor mode), and
can execute any instruction and can access any memory location
If the mode bit is not set, the process is running in user mode and is not allowed to
execute privileged instructions
A process running application code is initially in user mode
The only way to change from user mode to kernel mode is via an exception and
exception handler runs in kernel mode
Context Switching
Context
The kernel maintains a context for each process
The context is the state of a process that the kernel needs to restart a preempted process
Consist of general purpose registers, FP registers, PC, user’s stack, status registers,
kernel’s stack, and various kernel data structures such as page table and file table
Context switching
The OS kernel implements multitasking using an exceptional control flow
At certain points during the execution of a process, the kernel decide to preempt
the current process and restart a previously preempted process
This is called scheduling and handled by code in the kernel called scheduler
Context switching
The kernel first saves the context of the current process
The kernel restores the context of some previously preempted process
Then, the kernel passes control to this newly restored process
Context Switching
Process A
code
read
Time
disk interrupt
return from
read
Process B
code
user code
kernel code
context switch
user code
kernel code
user code
context switch
fork: Creating new processes
Process control
Unix provides a number of system calls for manipulating processes from C program
Obtain Process ID, Create/Terminate Process, etc.
int fork(void)
Creates a new process (child process) that is identical to the calling process (parent
process)
Returns 0 to the child process
Returns child’s pid to the parent process
if (fork() == 0) {
printf("hello from child\n");
} else {
printf("hello from parent\n");
}
Fork is interesting
(and often confusing)
because it is called
once but returns twice
Fork Example #1
Parent and child both run the same code
Distinguish parent from child by return value from fork
Duplicate but separate address space
Start with same state, but each has private copy
Relative ordering of their print statements undefined
Shared files
Both parent and child print their output on the same screen
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
} else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
Fork Example #2
Both parent and child can continue forking
Process graph
Each horizontal arrow corresponds to a process
Each vertical arrow corresponds to the execution of a fork function
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
Bye
L1
Bye
Bye
L0
L1
Bye
Fork Example #3
Key Points
Both parent and child can continue forking
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
fork();
printf("Bye\n");
}
Bye
L2
Bye
Bye
L1
L2
Bye
Bye
L2
Bye
Bye
L0
L1
L2
Bye
Midterm Exam 3
void end(void)
{
printf(“2”);
}
int main()
{
if (fork() == 0) {
atexit (end);
if (fork() == 0) printf(“hello”);
}
if (fork() != 0)
printf(“0”);
else
printf(“1”);
printf(“bye”);
exit(0);
}
hello
1
bye
0
bye
1
bye
0
bye 2
1
bye
0
bye
Fork Example #4
Key Points
Both parent and child can continue forking
void fork4()
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
fork();
}
}
printf("Bye\n");
}
Bye
Bye
Bye
L0
L1
L2
Bye
Fork Example #5
Key Points
Both parent and child can continue forking
void fork5()
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
fork();
}
}
printf("Bye\n");
}
Bye
L2
L1
L0
Bye
Bye
Bye
exit: Destroying Process
void exit(int status)
Terminate a process with an exit status
Normally with status 0
atexit() registers functions to be executed upon exit
void cleanup(void) {
printf("cleaning up\n");
}
void fork6() {
atexit(cleanup);
fork();
exit(0);
}
A process can be terminated for one of three reasons
Call the exit function
Return from the main function
Receive a signal whose default action is to terminate the process
Reaping Child Process
Zombie
When a process terminates, the kernel does not remove it from the system immediately
The process is kept around in a terminated state until it is reaped by its parent
A terminated process that has not yet been reaped is called a zombie
Living corpse, half alive and half dead
Zombie still consumes system resources such as various tables maintained by OS
Reaping
Performed by the parent on a terminated child
Parent is given an exit status information
Kernel discards process
What if Parent Doesn’t Reap?
If any parent terminates without reaping a child, then child will be reaped by init pro
cess
The init process has a PID of 1 and is created by the kernel during system initialization.
Long-running programs such as shells or servers should always reap their zombie
children.
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
00:00:00 tcsh
6639 ttyp9
00:00:03 forks
6640 ttyp9
00:00:00 forks <defunct>
6641 ttyp9
00:00:00 ps
linux> kill 6639
[1]
Terminated
linux> ps
PID TTY
TIME CMD
6585 ttyp9
00:00:00 tcsh
6642 ttyp9
00:00:00 ps
ps shows child process as
“defunct”
Killing parent allows child to
be reaped
Nonterminating Child Example
void fork8()
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",
getpid());
while (1)
linux> ./forks 8
; /* Infinite loop */
} else {
Terminating Parent, PID = 6675
printf("Terminating Parent, PID = %d\n",
Running Child, PID = 6676
getpid());
linux> ps
exit(0);
PID TTY
TIME CMD
}
6585 ttyp9
00:00:00 tcsh
}
6676 ttyp9
00:00:06
6677 ttyp9
00:00:00
linux> kill 6676
linux> ps
PID TTY
TIME
6585 ttyp9
00:00:00
6678 ttyp9
00:00:00
forks
ps
CMD
tcsh
ps
Child process still active even though
parent has terminated
Must kill explicitly, or else will keep
running indefinitely
wait: Synchronizing with Children
int wait(int *child_status)
Suspends the current process until one of its children terminates
The terminated child is removed from the system.
Return value is the pid of the child process that terminated
if child_status != NULL, then it will be set to a status indicating why the
child process terminated
void fork9() {
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
}
else {
printf("HP: hello from parent\n");
wait(&child_status);
printf("CT: child has terminated\n");
}
printf("Bye\n");
exit();
}
HC Bye
HP
CT Bye
Wait Example
If multiple children completed, will take in arbitrary order
Can use macros WIFEXITED and WEXITSTATUS to get information about exit status
WIFEEXITED returns true if the child terminated normally
void fork10()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = 0; i < N; i++) {
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);
}
}
Waitpid
waitpid(pid, &status, options)
Can wait for specific process
Various options
void fork11()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = 0; i < N; i++) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
Wait/Waitpid Example Outputs
Using wait (fork10)
Child
Child
Child
Child
Child
3565
3564
3563
3562
3566
terminated
terminated
terminated
terminated
terminated
with
with
with
with
with
exit
exit
exit
exit
exit
status
status
status
status
status
103
102
101
100
104
Using waitpid (fork11)
Child
Child
Child
Child
Child
3568
3569
3570
3571
3572
terminated
terminated
terminated
terminated
terminated
with
with
with
with
with
exit
exit
exit
exit
exit
status
status
status
status
status
100
101
102
103
104
exec: Running new programs
int execl(char *path, char *arg0, char *arg1, …, 0)
Load and run executable at path with args arg0, arg1, …
path is the complete path of an executable
arg0 becomes the name of the process
typically arg0 is either identical to path, or else it contains only the executable filename
from path
“real” arguments to the executable start with arg1, etc.
list of args is terminated by a (char *)0 argument
returns -1 if error, otherwise doesn’t return!
Calls once but never returns
main() {
if (fork() == 0) {
execl("/usr/bin/cp", "cp", "foo", "bar", 0);
}
wait(NULL);
printf("copy completed\n");
exit();
}
exec: Running new programs
int execve(char *filename, char *arg
v[], char *envp[]);
Load and run the executable with
argument list argv and the environment
variable list envp
After execve loads the filename, it
calls the startup code, which set up the
stack and passes control to the main
routine
int main(int argc, char *argv[], char
*envp[]);
When main begins executing, the user
stack has the organization shown on
the right side
Null-terminated Bottom of stack
environment variable strings
Null-terminated
command-line arg strings
(Unused)
envp[n] == NULL
envp[n-1]
...
envp[0]
argv[argc] = NULL
argv[argc-1]
environ
...
argv[0]
(Dynamic linker variables)
envp
argv
argc
Stack frame for
main
Top of stack
Environment Variables
Environment variables
A set of dynamic named values that can affect the way running processes will
behave on a computer.
Create the operating environment in which a process runs.
All Unix operating system flavors, MS-DOS, and Microsoft Windows have
environment variables; however, they do not all use the same variable names.
Examples of environment variables
PATH - lists directories the shell searches for the commands
HOME - indicate where a user's home directory is located in the file system
TERM - specifies the type of computer terminal (e.g., vt100).
PS1 - specifies how the prompt is displayed
MAIL - used to indicate where a user's mail is to be found.
TEMP - location where processes can store temporary files
The World of Multitasking
System Runs Many Processes Concurrently
Process: executing program
State consists of memory image + register values + program counter
Continually switches from one process to another
Suspend process when it needs I/O resource or timer event occurs
Resume process when it is given scheduling priority
Appears to user(s) as if all processes executing simultaneously
Even though most systems can only execute one process at a time
Except possibly with lower performance than if running alone
Programmer’s Model of Multitasking
Basic Functions
fork() spawns new process
Called once, returns twice
exit() terminates own process
Called once, never returns
Puts it into “zombie” status
wait() and waitpid() wait for and reap terminated children
execl() and execve() run a new program in an existing process
Called once, (normally) never returns
Programming Challenge
Understanding the nonstandard semantics of the functions
Avoiding improper use of system resources
E.g. “Fork bombs” can disable a system.
Unix Process Hierarchy
[0]
init [1]
Daemon
e.g. httpd
Login shell
Child
Child
Grandchild
Child
Grandchild
Unix Startup: Step 1
1. Pushing reset button loads the PC with the address of a small
bootstrap program.
2. Bootstrap program loads the boot block (disk block 0).
3. Boot block program loads kernel binary (e.g., /boot/vmlinux)
4. Boot block program passes control to kernel.
5. Kernel handcrafts the data structures for process 0.
[0]
Process 0: handcrafted kernel process
Process 0 forks child process 1
init [1]
Child process 1 execs /sbin/init
Unix Startup: Step 2
[0]
/etc/inittab
Daemons
e.g. ftpd, httpd
init [1]
getty
init forks and execs
daemons per
/etc/inittab, and forks
and execs a getty program
for the console
Unix Startup: Step 3
[0]
init [1]
login
The getty process
execs a login
program
Unix Startup: Step 4
[0]
init [1]
tcsh
login reads login and passwd.
if OK, it execs a shell.
if not OK, it execs another getty
Shell Programs
A shell is an interactive application-level program that runs other programs
on behalf of the user.
sh – Original Unix Bourne Shell
csh – BSD Unix C Shell, tcsh – Enhanced C Shell
bash –Bourne-Again Shell
int main()
{
char cmdline[MAXLINE];
while (1) {
/* read */
printf("> ");
Fgets(cmdline, MAXLINE, stdin);
if (feof(stdin))
exit(0);
/* evaluate */
eval(cmdline);
}
}
Execution is a sequence of read/
evaluate steps
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* argv for execve() */
int bg;
/* should the job run in bg or fg? */
pid_t pid;
/* process id */
bg = parseline(cmdline, argv); /* build the argv vector */
if (!builtin_command(argv)) {
if ((pid = Fork()) == 0) {
/* child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
if (!bg) {
/* parent waits for fg job to terminate */
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
/* otherwise, don’t wait for bg job */
printf("%d %s", pid, cmdline);
}
}
Problem with Simple Shell Example
Shell correctly waits for and reaps foreground jobs.
But what about background jobs?
Will become zombies when they terminate.
Will never be reaped because shell (typically) will not terminate.
Creates a memory leak that will eventually crash the kernel when it runs out of me
mory.
Solution: Reaping background jobs requires a mechanism called a signal.
Signals
A signal is a small message that notifies a process that an event of some type
has occurred in the system.
Signals provide a mechanism for exposing the occurrence of such exceptions to user
processes.
Kernel abstraction for exceptions and interrupts.
Sent from the kernel (sometimes at the request of another process) to a process.
Different signals are identified by small integer ID’s
Each signal type corresponds to some kind of system event
The only information in a signal is its ID and the fact that it arrived.
ID
Name
Default Action
Corresponding Event
2 SIGINT
Terminate
Interrupt from keyboard (ctl-c)
9 SIGKILL
Terminate
Kill program (cannot override or ignore)
11 SIGSEGV
Terminate & Dump
Segmentation violation
14 SIGALRM
Terminate
Timer signal
17 SIGCHLD
Ignore
Child stopped or terminated
Signal Concepts
Sending a signal
Kernel sends (delivers) a signal to a destination process by updating some state in
the context of the destination process.
Kernel sends a signal for one of the following reasons:
Kernel has detected a system event such as divide-by-zero (SIGFPE) or the termination
of a child process (SIGCHLD)
Another process has invoked the kill system call to explicitly request the kernel to se
nd a signal to the destination process.
Receiving a signal
A destination process receives a signal when it is forced by the kernel to react in s
ome way to the delivery of the signal.
Three possible ways to react:
Ignore the signal (do nothing)
Terminate the process.
Catch the signal by executing a user-level function called a signal handler.
Akin to a hardware exception handler being called in response to an asynchronous interrupt.
Signal Concepts (cont)
A signal is pending if it has been sent but not yet received.
There can be at most one pending signal of any particular type.
Important: Signals are not queued
If a process has a pending signal of type k, then subsequent signals of type k that are sent
to that process are discarded.
A process can block the receipt of certain signals.
Blocked signals can be delivered, but will not be received until the signal is unblock
ed.
Kernel maintains pending and blocked bit vectors in the context of eac
h process.
pending – represents the set of pending signals
Kernel sets bit k in pending whenever a signal of type k is delivered.
Kernel clears bit k in pending whenever a signal of type k is received
blocked – represents the set of blocked signals
Can be set and cleared by the application using the sigprocmask function.
Process Groups
Every process belongs to exactly one process group
pid=10
pgid=10
pid=20
pgid=20
Foreground
job
Shell
Background
job #1
pid=32
pgid=32
Background
process group 32
Child
Child
pid=21
pgid=20
pid=22
pgid=20
Foreground
process group 20
Background
job #2
pid=40
pgid=40
Background
process group 40
getpgrp() – Return process group of cu
rrent process
setpgid() – Change process group of
a process
Sending Signals with kill Program
kill program sends arbitrary si
gnal to a process or process group
Examples
kill –9 24818
Send SIGKILL to process
24818
kill –9 –24817
Send SIGKILL to every p
rocess in process group
24817.
linux> ./forks 16
linux> Child1: pid=24818 pgrp=24817
Child2: pid=24819 pgrp=24817
linux> ps
PID TTY
TIME CMD
24788 pts/2
00:00:00 tcsh
24818 pts/2
00:00:02 forks
24819 pts/2
00:00:02 forks
24820 pts/2
00:00:00 ps
linux> kill -9 -24817
linux> ps
PID TTY
TIME CMD
24788 pts/2
00:00:00 tcsh
24823 pts/2
00:00:00 ps
linux>
Sending Signals from the Keyboard
Typing ctrl-c (ctrl-z) sends a SIGINT (SIGTSTP) to every process in the foregroun
d process group.
SIGINT – termination signal; default action is to terminate each process
SIGTSTP – stop signal; default action is to stop (suspend) each process
Job: abstraction to represent the processes that are created by a command line.
There is at most one foreground job and more background jobs
pid=10
pgid=10
pid=20
pgid=20
Foreground
job
Child
Child
pid=21
pgid=20
pid=22
pgid=20
Foreground
process group 20
Shell
Background
job #1
pid=32
pgid=32
Background
process
group 32
Background
job #2
pid=40
pgid=40
Background
process
group 40
Example of ctrl-c and ctrl-z
linux> ./forks 17
Child: pid=24868 pgrp=24867
Parent: pid=24867 pgrp=24867
<typed ctrl-z>
Suspended
linux> ps a
PID TTY
STAT
TIME COMMAND
24788 pts/2
S
0:00 -usr/local/bin/tcsh -i
24867 pts/2
T
0:01 ./forks 17
24868 pts/2
T
0:01 ./forks 17
24869 pts/2
R
0:00 ps a
bass> fg
./forks 17
<typed ctrl-c>
linux> ps a
PID TTY
STAT
TIME COMMAND
24788 pts/2
S
0:00 -usr/local/bin/tcsh -i
24870 pts/2
R
0:00 ps a
Sending Signals with kill Function
void fork12()
{
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
while(1); /* Child infinite loop */
/* Parent terminates the child processes */
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}
/* Parent reaps terminated children */
for (i = 0; i < N; i++) {
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 terminated abnormally\n", wpid);
}
}
Receiving Signals
Suppose kernel is returning from exception handler and is ready to pass
control to process p.
Kernel computes pnb = pending & ~blocked
The set of pending nonblocked signals for process p
If (pnb == 0)
Pass control to next instruction in the logical flow for p.
Else
Choose least nonzero bit k in pnb and force process p to receive signal k.
The receipt of the signal triggers some action by p
Repeat for all nonzero k in pnb.
Pass control to next instruction in logical flow for p.
Default Actions
Each signal type has a predefined default action, which is one of:
The process terminates
The process terminates and dumps core.
The process stops until restarted by a SIGCONT signal.
The process ignores the signal.
Installing Signal Handlers
The signal function modifies the default action associated with the receipt
of signal signum:
handler_t *signal(int signum, handler_t *handler)
Different values for handler:
SIG_IGN: ignore signals of type signum
SIG_DFL: revert to the default action on receipt of signals of type signum.
Otherwise, handler is the address of a user-defined function called signal handler
Called when process receives signal of type signum
Changing the default action by passing the address of a handler to the signal function is
known as “installing” the handler.
The invocation of the handler is called “catching” the signal
The execution of the handler is referred as “handling” the signal.
When the handler executes its return statement, control passes back to instruction in the co
ntrol flow of the process that was interrupted by receipt of the signal.
Signal Handling Example
#include "csapp.h"
void handler(int sig) {
static int beeps = 0;
printf("BEEP\n");
if (++beeps < 5) Alarm(1); /* next SIGALRM
will be delivered in 1s */
else {
printf("BOOM!\n");
exit(0);
}
}
int main() {
Signal(SIGALRM, handler); /* install
SIGALRM handler */
Alarm(1); /* next SIGALRM will be delivered
in 1s */
while (1) { ; /* signal handler returns
control here each time */ }
exit(0);
}
linux> ./alarm
BEEP
BEEP
BEEP
BEEP
BEEP
BOOM!
linux>
Signal Handler Funkiness
int ccount = 0;
void child_handler(int sig)
{
int child_status;
pid_t pid = wait(&child_status);
ccount--;
printf("Received signal %d from process %d\n",
sig, pid);
}
Pending signals are blocked
void fork14()
{
pid_t pid[N];
int i, child_status;
ccount = N;
signal(SIGCHLD, child_handler);
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
/* Child: Exit */
exit(0);
}
while (ccount > 0)
pause();/* Suspend until signal occurs */
}
Pending signals are not queued
While the handler is processing
the first signal, the second
signal is delivered but cannot be
received since SIGCHLD
signals are blocked by the
handler.
For each signal type, there can
be at most one pending signal o
a particular type.
Meanwhile, the third SIGCHLD
signal arrives but since there is
already a pending SIGCHLD
signal and the third signal is
discarded!
Living With Nonqueuing Signals
To fix the problem, need to handle as many zombie children as possible each
time the handler is invoked.
Typically implemented with a while loop with wait
void child_handler2(int sig)
{
int child_status;
pid_t pid;
while ((pid = wait(&child_status)) > 0) {
ccount--;
printf("Received signal %d from process %d\n", sig,
pid);
}
}
void fork15()
{
. . .
signal(SIGCHLD, child_handler2);
. . .
}
Nonlocal Jumps: setjmp/longjmp
Powerful (but dangerous) user-level mechanism for transferring control to an arb
itrary location.
Controlled to way to break the procedure call/return discipline
Useful for error recovery and signal handling
Immediate return from a deeply nested function call (as a result of detecting some error condition)
Branch out of a signal handler to a specific code location, rather than returning to the instruction
interrupted
int setjmp(jmp_buf j)
Must be called before longjmp
Save the current stack context in the buffer j, for later use by longjump
Identify a return site for a subsequent longjmp.
Remember where you are by storing the current register context, stack pointer, and PC value in the
buffer j
Return 0 initially
Called once, returns one or more times
void longjmp(jmp_buf j, int i)
Restore the stack context from the j buffer and then return to the most recent setjmp call
Jump to the location indicated by the PC stored in jump buf j.
Provide a non-zero return value I to the setjmp
Called once, but never returns
setjmp/longjmp Example
#include <setjmp.h>
jmp_buf buf;
main() {
if (setjmp(buf) != 0) {
printf("back in main due to an error\n");
else
printf("first time through\n");
p1(); /* p1 calls p2, which calls p3 */
}
...
p3() {
<error checking code>
if (error)
longjmp(buf, 1)
}
Putting It All Together: A Program
That Restarts Itself When ctrl-c’d
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
sigjmp_buf buf;
void handler(int sig) {
siglongjmp(buf, 1);
}
main() {
signal(SIGINT, handler);
if (!sigsetjmp(buf, 1))
printf("starting\n");
else
printf("restarting\n");
while(1) {
sleep(1);
printf("processing...\n");
}
}
bass> a.out
starting
processing...
processing...
restarting
processing...
processing...
processing...
restarting
processing...
restarting
processing...
processing...
Ctrl-c
Ctrl-c
Ctrl-c