0% found this document useful (0 votes)
17 views

Class17 cs230s23

This document contains a summary of a lecture on exceptional control flow, including signals and non-local jumps. It discusses topics like exceptions, processes, spawning processes, process completion, loading and running programs. It also discusses a simple shell implementation with a main loop that reads commands, evaluates them by parsing, executing built-in commands or forking and executing other programs. The eval function details how it handles built-in vs external commands, forking for child processes and waiting on their completion.

Uploaded by

Z ero
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views

Class17 cs230s23

This document contains a summary of a lecture on exceptional control flow, including signals and non-local jumps. It discusses topics like exceptions, processes, spawning processes, process completion, loading and running programs. It also discusses a simple shell implementation with a main loop that reads commands, evaluates them by parsing, executing built-in commands or forking and executing other programs. The eval function details how it handles built-in vs external commands, forking for child processes and waiting on their completion.

Uploaded by

Z ero
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 62

KAIST - CS230

Exceptional Control Flow:


Signals and Nonlocal Jumps
Spring, 2023

Topics

Exceptional Control Flow


Exceptions
Processes
Process Control

class17.ppt Introduction to Computer Systems, CMU (Authors’) CS 230 S ‘23


Review from last lecture
Exceptions
 Events that require nonstandard control flow
 Generated externally (interrupts) or internally (traps and
faults)

Processes
 At any given time, system has multiple active processes
 Only one can execute at a time on any single core
 Each process appears to have total control of
processor + private memory space

CS 230 S ‘23
Review (cont.)
Spawning processes
 Call fork
 One call, two returns

Process completion
 Call exit
 One call, no return

Reaping and waiting for processes


 Call wait or waitpid

Loading and running programs


 Call execve (or variant)
 One call, (normally) no return

CS 230 S ‘23
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

Called once and never returns


 …except if there is an error

CS 230 S ‘23
ECF Exists at All Levels of a System
Exceptions
 Hardware and operating system kernel software
Previous Lecture
Process Context Switch
 Hardware timer and kernel software

Signals
This Lecture
 Kernel software and application software

Nonlocal jumps
Textbook and
 Application code supplemental slides

CS 230 S ‘23
(partial) Taxonomy Handled in kernel
Handled in user process

ECF

Asynchronous
Synchronous

Interrupts Traps Faults Aborts

Signals

CS 230 S ‘23
Today
Shells
Signals
Nonlocal jumps

CS 230 S ‘23
Linux Process Hierarchy

[0]

init [1]

Daemon …
e.g. httpd Login shell Login shell

Child Child Child

Grandchild Grandchild Note: you can view the


hierarchy using the Linux
pstree command
CS 230 S ‘23
Shell Programs
A shell is an application program that runs programs on
behalf of the user
 sh Original Unix shell (Stephen Bourne, AT&T Bell
Labs, 1977)
 csh/tcsh BSD Unix C shell
 bash “Bourne-Again” Shell (default Linux shell)

Simple shell
 Described in the textbook, starting at p. 753
 Implementation of a very elementary shell
 Purpose
 Understand what happens when you type commands
 Understand use and operation of process control operations

CS 230 S ‘23
Simple Shell Example
linux> ./shellex
> /bin/ls -l csapp.c Must give full pathnames for programs
-rw-r--r-- 1 bryant users 23053 Jun 15 2015 csapp.c
> /bin/ps
PID TTY TIME CMD
31542 pts/2 00:00:01 tcsh
32017 pts/2 00:00:00 shellex
32019 pts/2 00:00:00 ps
> /bin/sleep 10 & Run program in background
32031 /bin/sleep 10 &
> /bin/ps
PID TTY TIME CMD
31542 pts/2 00:00:01 tcsh
32024 pts/2 00:00:00 emacs
32030 pts/2 00:00:00 shellex
32031 pts/2 00:00:00 sleep Sleep is running
32033 pts/2 00:00:00 ps in background
> quit

CS 230 S ‘20
Simple Shell Implementation
Basic loop
 Read line from command line
 Execute the requested operation
 Built-in command (only one implemented is quit)
 Load and execute program from file

int main(int argc, char** argv)


{ Execution is a
char cmdline[MAXLINE]; /* command line */ sequence of
while (1) { read/evaluate
/* read */ steps
printf("> ");
Fgets(cmdline, MAXLINE, stdin);
if (feof(stdin))
exit(0);

/* evaluate */
eval(cmdline);
}
... shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
parseline will parse ‘buf’ into ‘argv’ and
if (!builtin_command(argv)) { not input line ended in ‘&’
return whether or
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);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */ Ignore empty lines.
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);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
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 it is a ‘built in’ command, then handle
it
here in this program. Otherwise fork/exec
/* Parent waits for foreground job to terminate */
if (!bg) { the program specified in argv[0]
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
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);
}
} Create child
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
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);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
Start argv[0].
unix_error("waitfg: waitpid error");
} Remember execve only returns on error.
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
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);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error");
}
else If running child in
printf("%d %s", pid, cmdline);
foreground, wait until it is
}
return; done.
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
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);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status; If running child in
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error"); background, print pid and
} continue doing other stuff.
else
printf("%d %s", pid, cmdline);
}
return;
}
shellex.c
CS 230 S ‘23
Simple Shell eval Function
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; /* Ignore empty lines */
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);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
unix_error("waitfg: waitpid error"); Oops. There is a
}
else
printf("%d %s", pid, cmdline);
problem with
}
return; this code.
}
shellex.c
CS 230 S ‘23
Problem with Simple Shell Example
Shell designed to run indefinitely
 Should not accumulate unneeded resources
 Memory
 Child processes
 File descriptors

Our 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
 Will create a memory leak that could run the kernel out of
memory
CS 230 S ‘23
ECF to the Rescue!
Solution: Exceptional control flow
 The kernel will interrupt regular processing to alert us when a
background process completes
 In Unix, the alert mechanism is called a signal

CS 230 S ‘23
Today
Shells
Signals
Nonlocal jumps

CS 230 S ‘23
Signals
A signal is a small message that notifies a process that
an event of some type has occurred in the system
 Akin to exceptions and interrupts
 Sent from the kernel (sometimes at the request of another
process) to a process
 Signal type is identified by small integer ID’s (1-30)
 Only information in a signal is its ID and the fact that it arrived

ID Name Default Action Corresponding Event


2 SIGINT Terminate User typed ctrl-c
9 SIGKILL Terminate Kill program (cannot override or ignore)
11 SIGSEGV Terminate Segmentation violation
14 SIGALRM Terminate Timer signal
17 SIGCHLD Ignore Child stopped or terminated

CS 230 S ‘23
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 send a signal to the destination process

CS 230 S ‘23
Signal Concepts: Sending a Signal
User level

Process B

Process A

Process C

kernel

Pending for A Blocked for A


Pending for B Blocked for B
Pending for C Blocked for C
CS 230 S ‘23
Signal Concepts: Sending a Signal
User level

Process B

Process A

Process C

kernel

Pending for A Blocked for A


Pending for B Blocked for B
Pending for C Blocked for C
CS 230 S ‘23
Signal Concepts: Sending a Signal
User level

Process B

Process A

Process C

kernel

Pending for A Blocked for A


Pending for B Blocked for B
1 Pending for C Blocked for C
CS 230 S ‘23
Signal Concepts: Sending a Signal
User level

Process B

Process A

Process C

kernel

Pending for A Blocked for A


Pending for B Blocked for B
1 Pending for C Blocked for C
CS 230 S ‘23
Signal Concepts: Sending a Signal
User level

Process B

Process A

Process C

kernel

Pending for A Blocked for A


Pending for B Blocked for B
0 Pending for C Blocked for C
CS 230 S ‘23
Signal Concepts: Receiving a Signal
A destination process receives a signal when it is
forced by the kernel to react in some way to the
delivery of the signal
Some possible ways to react:
 Ignore the signal (do nothing)
 Terminate the process (with optional core dump)
 Catch the signal by executing a user-level function called
signal handler
 Akin to a hardware exception handler being called in response
to an asynchronous interrupt:
(1) Signal received (2) Control passes
by process to signal handler
Icurr
Inext (3) Signal
handler runs
(4) Signal handler
returns to
next instruction
CS 230 S ‘23
Signal Concepts: Pending and
Blocked Signals
A signal is pending if 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 unblocked

A pending signal is received at most once


CS 230 S ‘23
Signal Concepts: Pending/Blocked
Bits
Kernel maintains pending and blocked bit vectors in
the context of each process
 pending: represents the set of pending signals
 Kernel sets bit k in pending when a signal of type k is delivered
 Kernel clears bit k in pending when a signal of type k is received

 blocked: represents the set of blocked signals


 Can be set and cleared by using the sigprocmask function
 Also referred to as the signal mask.

CS 230 S ‘23
Signal Concepts: Sending a Signal
User level

Process B

Process A

Process C

kernel

Pending for A Blocked for A


Pending for B Blocked for B
1 Pending for C Blocked for C
CS 230 S ‘23
Sending Signals: Process
Groups
Every process belongs to exactly one process
group
pid=10
pgid=10 Shell

pid=20 Fore- Back- Back-


pid=32 pid=40
pgid=20 ground ground pgid=32 ground pgid=40
job job #1 job #2

Background Background
process group 32 process group 40
Child Child

pid=21 pid=22 getpgrp()


pgid=20 pgid=20 Return process group of current process
Foreground setpgid()
process group 20 Change process group of a process (see
text for details)
CS 230 S ‘23
Sending Signals with /bin/kill
Program
/bin/kill program
linux> ./forks 16
sends arbitrary signal Child1: pid=24818 pgrp=24817
to a process or Child2: pid=24819 pgrp=24817

process group linux> ps


PID TTY TIME CMD
24788 pts/2 00:00:00 tcsh
24818 pts/2 00:00:02 forks
Examples 24819 pts/2 00:00:02 forks
 /bin/kill –9 24818 24820 pts/2 00:00:00 ps
Send SIGKILL to process linux> /bin/kill -9 -24817
24818 linux> ps
PID TTY TIME CMD
24788 pts/2 00:00:00 tcsh
 /bin/kill –9 –24817 24823 pts/2 00:00:00 ps
linux>
Send SIGKILL to every
process in process group
24817

CS 230 S ‘23
Sending Signals from the Keyboard
Typing ctrl-c (ctrl-z) causes the kernel to send a SIGINT (SIGTSTP)
to every job in the foreground process group
 SIGINT – default action is to terminate each process
 SIGTSTP – default action is to stop (suspend) each process
pid=10
pgid=10 Shell

pid=20 Fore- Back- Back-


pid=32 pid=40
pgid=20 ground ground pgid=32 ground pgid=40
job job #1 job #2

Background Background
process group 32 process group 40
Child Child

pid=21 pid=22
pgid=20 pgid=20

Foreground
process group 20 CS 230 S ‘23
Example of ctrl-c and ctrl-z
bluefish> ./forks 17 STAT (process state) Legend:
Child: pid=28108 pgrp=28107
Parent: pid=28107 pgrp=28107 First letter:
<types ctrl-z> S: sleeping
Suspended T: stopped
bluefish> ps w R: running
PID TTY STAT TIME COMMAND
27699 pts/8 Ss 0:00 -tcsh
28107 pts/8 T 0:01 ./forks 17 Second letter:
28108 pts/8 T 0:01 ./forks 17 s: session leader
28109 pts/8 R+ 0:00 ps w +: foreground proc group
bluefish> fg
./forks 17 See “man ps” for more
<types ctrl-c> details
bluefish> ps w
PID TTY STAT TIME COMMAND
27699 pts/8 Ss 0:00 -tcsh
28110 pts/8 R+ 0:00 ps w

CS 230 S ‘23
Sending Signals with kill Function
void fork12()
{
pid_t pid[N];
int i;
int child_status;

for (i = 0; i < N; i++)


if ((pid[i] = fork()) == 0) {
/* Child: Infinite Loop */
while(1)
;
}

for (i = 0; i < N; i++) {


printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}

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);
}
} forks.c
CS 230 S ‘23
Receiving Signals
Suppose kernel is returning from an exception
handler and is ready to pass control to process p

Process q Process p

user code

kernel code context switch


Time
user code

kernel code context switch

user code

CS 230 S ‘23
Receiving Signals
Suppose kernel is returning from an 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
CS 230 S ‘23
Default Actions
Each signal type has a predefined default action, which
is one of:
 The process terminates
 The process stops until restarted by a SIGCONT signal
 The process ignores the signal

CS 230 S ‘23
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-level signal handler
 Called when process receives signal of type signum
 Referred to as “installing” the handler
 Executing handler is called “catching” or “handling” the signal
 When the handler executes its return statement, control passes
back to instruction in the control flow of the process that was
interrupted by receipt of the signal CS 230 S ‘23
Signal Handling
Example
void sigint_handler(int sig) /* SIGINT handler */
{
printf("So you think you can stop the bomb with ctrl-c, do you?\n");
sleep(2);
printf("Well...");
fflush(stdout);
sleep(1);
printf("OK. :-)\n");
exit(0);
}

int main(int argc, char** argv)


{
/* Install the SIGINT handler */
if (signal(SIGINT, sigint_handler) == SIG_ERR)
unix_error("signal error");

/* Wait for the receipt of a signal */


pause();

return 0;
} sigint.c
CS 230 S ‘23
Signals Handlers as Concurrent
Flows
A signal handler is a separate logical flow (not process)
that runs concurrently with the main program
But, this flow exists only until returns to main program

Process A Process A Process B

while (1) handler(){


; …
}

Time

CS 230 S ‘23
Another View of Signal Handlers as
Concurrent Flows

Process A Process B

Signal delivered Icurr user code (main)


to process A context switch
kernel code

user code (main)

kernel code context switch


Signal received
by process A user code (handler)

kernel code
Inext
user code (main)

CS 230 S ‘23
Nested Signal Handlers
Handlers can be interrupted by other handlers

Main program Handler S Handler T

(2) Control passes


(1) Program Icurr to handler S
catches signal s (4) Control passes
(3) Program to handler T
(7) Main program Inext catches signal t
resumes
(5) Handler T
(6) Handler S
returns to
returns to
handler S
main
program

CS 230 S ‘23
Blocking and Unblocking Signals
Implicit blocking mechanism
 Kernel blocks any pending signals of type currently being
handled
 e.g., a SIGINT handler can’t be interrupted by another SIGINT

Explicit blocking and unblocking mechanism


 sigprocmask function

Supporting functions
 sigemptyset – Create empty set
 sigfillset – Add every signal number to set
 sigaddset – Add signal number to set
 sigdelset – Delete signal number from set
CS 230 S ‘23
Temporarily Blocking
Signals
sigset_t mask, prev_mask;

Sigemptyset(&mask);
Sigaddset(&mask, SIGINT);

/* Block SIGINT and save previous blocked set */


Sigprocmask(SIG_BLOCK, &mask, &prev_mask);

/* Code region that will not be interrupted by SIGINT */


/* Restore previous blocked set, unblocking SIGINT */


Sigprocmask(SIG_SETMASK, &prev_mask, NULL);

CS 230 S ‘23
Safe Signal Handling
Handlers are tricky because they are concurrent with
main program and share the same global data
structures
 Shared data structures can become corrupted.

We’ll explore concurrency issues later in the term

For now here are some guidelines to help you avoid


trouble.

CS 230 S ‘23
Guidelines for Writing Safe
Handlers
G0: Keep your handlers as simple as possible
 e.g., set a global flag and return
G1: Call only async-signal-safe functions in your handlers
 printf, sprintf, malloc, and exit are not safe!
G2: Save and restore errno on entry and exit
 So that other handlers don’t overwrite your value of errno
G3: Protect accesses to shared data structures by
temporarily blocking all signals
 To prevent possible corruption
G4: Declare global variables as volatile
 To prevent compiler from storing them in a register
G5: Declare global flags as volatile sig_atomic_t
 flag: variable that is only read or written (e.g. flag = 1, not flag++)
 Flag declared this way does not need to be protected like other
globals

CS 230 S ‘23
Async-Signal-Safety
Function is async-signal-safe if either reentrant (e.g., all variables
stored on stack frame, CS:APP3e 12.7.2) or non-interruptible by
signals
Posix guarantees 117 functions to be async-signal-safe
 Source: “man 7 signal-safety”
 Popular functions on the list:
 _exit, write, wait, waitpid, sleep, kill
 Popular functions that are not on the list:
 printf, sprintf, malloc, exit
 Unfortunate fact: write is the only async-signal-safe output function

CS 230 S ‘23
Safe Formatted Output: Option #1
Use the reentrant SIO (Safe I/O library) from csapp.c in
your handlers
 ssize_t sio_puts(char s[]) /* Put string */
 ssize_t sio_putl(long v) /* Put long */
 void sio_error(char s[]) /* Put msg & exit */

void sigint_handler(int sig) /* Safe SIGINT handler */


{
Sio_puts("So you think you can stop the bomb"
" with ctrl-c, do you?\n");
sleep(2);
Sio_puts("Well...");
sleep(1);
Sio_puts("OK. :-)\n");
_exit(0);
} sigintsafe.c
CS 230 S ‘23
Safe Formatted Output: Option #2
Use the new & improved reentrant sio_printf!
 Handles restricted class of printf format strings
 Recognizes: %c %s %d %u %x %%
 Size designators ‘l’ and ‘z’

void sigint_handler(int sig) /* Safe SIGINT handler */


{
Sio_printf("So you think you can stop the bomb"
" (process %d) with ctrl-%c, do you?\n",
(int) getpid(), 'c');
sleep(2);
Sio_puts("Well...");
sleep(1);
Sio_puts("OK. :-)\n");
_exit(0);
}

sigintsafe.c
CS 230 S ‘23
volatile int ccount = 0;
Correct Signal
void child_handler(int sig) {
int olderrno = errno; Handling
pid_t pid; Pending signals are
if ((pid = wait(NULL)) < 0)
Sio_error("wait error"); not queued
ccount--;  For each signal type,
Sio_puts("Handler reaped child ");
Sio_putl((long)pid); one bit indicates
Sio_puts(" \n"); whether or not signal
sleep(1); is pending…
errno = olderrno;
}  …thus at most one
This code is incorrect! pending signal of any
void fork14() { particular type.
pid_t pid[N];
int i;
ccount = N;
N == 5 You can’t use
Signal(SIGCHLD, child_handler); signals to count
for (i = 0; i < N; i++) { events, such as
if ((pid[i] = Fork()) == 0) { children terminating.
Sleep(1); whaleshark> ./forks 14
exit(0); /* Child exits */
Handler reaped child 23240
}
}
Handler reaped child 23241
while (ccount > 0) /* Parent spins */ . . .(hangs)
;
} forks.c CS 230 S ‘23
E

Correct Signal Handling


Must wait for all terminated child processes
 Put wait in a loop to reap all terminated children
void child_handler2(int sig)
{
int olderrno = errno;
pid_t pid;
while ((pid = wait(NULL)) > 0) {
ccount--;
Sio_puts("Handler reaped child ");
Sio_putl((long)pid);
Sio_puts(" \n");
}
if (errno != ECHILD)
Sio_error("wait error");
errno = olderrno; whaleshark> ./forks 15
}
Handler reaped child 23246
Handler reaped child 23247
Handler reaped child 23248
Handler reaped child 23249
Handler reaped child 23250
whaleshark>
CS 230 S ‘23
Nonlocal Jumps: setjmp/longjmp
Powerful (but dangerous) user-level mechanism for
transferring control to an arbitrary location
 Controlled to way to break the procedure call / return
discipline
 Useful for error recovery and signal handling

int setjmp(jmp_buf j)
 Must be called before longjmp
 Identifies a return site for a subsequent longjmp
 Called once, returns one or more times

Implementation:
 Remember where you are by storing the current register
context, stack pointer, and PC value in jmp_buf
 Return 0 CS 230 S ‘23
setjmp/longjmp (cont)
void longjmp(jmp_buf j, int i)
 Meaning:
 return from the setjmp remembered by jump buffer j again ...
 … this time returning i instead of 0
 Called after setjmp
 Called once, but never returns

longjmp Implementation:
 Restore register context (stack pointer, base pointer, PC value)
from jump buffer j
 Set %eax (the return value) to i
 Jump to the location indicated by the PC stored in jump buf j

CS 230 S ‘23
setjmp/longjmp Example
Goal: return directly to original caller from a deeply-
nested function

/* Deeply nested function foo */


void foo(void)
{
if (error1)
longjmp(buf, 1);
bar();
}

void bar(void)
{
if (error2)
longjmp(buf, 2);
}

CS 230 S ‘23
jmp_buf buf;

int error1 = 0; setjmp/longjmp


int error2 = 1;
Example (cont)
void foo(void), bar(void);

int main()
{
switch(setjmp(buf)) {
case 0:
foo();
break;
case 1:
printf("Detected an error1 condition in foo\n");
break;
case 2:
printf("Detected an error2 condition in foo\n");
break;
default:
printf("Unknown error condition in foo\n");
}
exit(0);
}

CS 230 S ‘23
Limitations of Nonlocal Jumps
Works within stack discipline
 Can only long jump to environment of function that has been
called but not yet completed
Before longjmp After longjmp
jmp_buf env; env
P1 P1
P1()
{
if (setjmp(env)) { P2
/* Long Jump to here */
} else {
P2();
P2
}
} P2
P2()
{ . . . P2(); . . . P3(); } P3
P3()
{
longjmp(env, 1);
}
CS 230 S ‘23
Limitations of Long Jumps (cont.)
Works within stack discipline
Can only long jump to environment of function that has been
called but not yet completed
jmp_buf env;
P1

P1() P2
{ env
P2(); P3(); At setjmp
}

P2() P1
{
if (setjmp(env)) { env
/* Long Jump to here */ X P2
}
} P2 returns P1
P3() env
{ X P3
longjmp(env, 1);
} At longjmp
CS 230 S ‘23
Putting It All Together: A Program
That Restarts Itself When ctrl-c’d
#include "csapp.h"

sigjmp_buf buf;
greatwhite> ./restart
void handler(int sig) starting
{ processing...
siglongjmp(buf, 1); processing...
} processing...
int main()
restarting
Ctrl-c
{ processing...
if (!sigsetjmp(buf, 1)) { processing...
Signal(SIGINT, handler); restarting
Sio_puts("starting\n"); processing... Ctrl-c
} processing...
else processing...
Sio_puts("restarting\n");

while(1) {
Sleep(1);
Sio_puts("processing...\n");
}
exit(0); /* Control never reaches here */
} restart.c CS 230 S ‘23

You might also like