CPS 445 Lecture notes: Signals
Coverage: [USP] Chapter 8
Orientation
- concurrency and communication have been the two predominant
themes in this course
- we have used functions such as fork, wait,
and exec to achieve concurrency
- we have used input and output through pipes as our main
interprocess communication mechanism
- signals will provide an additional way to
foster interprocess communication, and it will be asynchronous
Signals: the essentials
- a signal is a software notification to a
process of an event
- the event generates the signal ([USP] §8.2)
- the signal is delivered when the process takes action
([USP] §8.5); the process must be in the running state
- the lifetime of the signal is the time
between the signal generation and delivery;
a signal which has been generated, but not yet
delivered, is said to be pending
- what is the difference between a signal and an interrupt?
- every signal is associated with a mnemonic starting with SIG
- represent small integers greater than 0
- defined in signal.h
- SIGUSR1 and SIGUSR2 are reserved for users
- the process signal mask
is the list of signals which the process is currently blocking
([USP] §8.3)
- blocking != ignoring
- a blocked signal is not thrown away
- it is delivered when unblocked
- use sigprocmask to change the
process signal mask (block the signal)
- a process can catch a signal by executing a
signal handler on signal delivery ([USP] §8.4)
- a program installs a signal handler by calling
sigaction with the name of a user-defined
function; or with
- SIG_DFL: default action
- SIG_IGN: ignore signal
neither are really considered to be catching the signal
- a signal handler is called a callback since you are
asking the handler to `call you back' whenever a signal arrives (akin to
event handling in user interface libraries)
Generating signals
- use the UNIX command kill
- kill -s signame <PID> or kill [-signame] <PID>
(omit the leading SIG in the name)
- kill [-signum] <PID>
- -2 (SIGINT; same as <ctrl-c>)
- -3 (SIGQUIT; same as <ctrl-\>)
- -9 (SIGKILL;
sure kill; process cannot inoculate itself against this signal)
- -15 (SIGTERM; default)
- kill -l # lists the available symbolic signal names
- int kill (pid_t pid, int sig);
a user may only send a signal to processed that s/he owns;
how can a child process kill its parent?
- int raise (int sig);
used to send a signal to yourself;
why might one ever desire to do this?
- unsigned alarm (unsigned seconds);
- requests to alarm are not stacked
- alarm never reports an error
Blocking signals
- blocked signals do not affect the behavior of the
process until they are delivered
- signal mask gives the set of signals
which are currently blocked
(type sigset_t)
- manipulated through 5 functions:
sigaddset, sigdelset, sigemptyset,
sigfillset, sigismember
- use sigprocmask to examine and/or modify
its process signal mask
- should only be used in a single-threaded program
- how parameter specifics how the
signal mask is to be modified
- SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK
- recall that SIGSTOP and SIGKILL cannot be blocked
- forked processes inherit the parent signal mask
- examples
- blocktest.c:
a program which blocks and unblocks SIGINT
- password.c: a function which blocks some
signals which, if delivered, might put the terminal
in an unusable state, during password entry
the sigaction Function
- used to get and/or set the action associated with a signal
- used like sigprocmask
- struct sigaction (uses pointers to functions
- in POSIX, a signal handler is an ordinary function which
returns void and has one integer parameter;
- on delivery, parameter set to the signal number
- two special values of sa_handler are
SIG_DFL and SIG_IGN
- SIG_DFL specifies that sigaction
should restore the default action
- SIG_IGN specifies that the
process should handle the signal by ignoring (discarding) it
- use write in signal handlers rather than
fprintf or strlen as
write is async-signal-safe
- means that it can be called safely from inside a
handler
- no such guarantees for fprintf or strlen
Basic signal review
- blocking != ignoring
- ignoring is a special case of catching and handling
- so really, we should say, blocking != catching
- process signal mask gives currently blocked signals
- sigset_t, a signal set, how, set, oset
- to block signals, use sigprocmask
- SIG_BLOCK, SIG_UNBLOCK, and SIG_SETMASK
- sigaddset, sigdelset, sigemptyset,
sigfillset, and sigismember
- SIGSTOP and SIGKILL (9) cannot be blocked
- to catch and handle signals, use sigaction
(and not signal, which is unreliable)
- sig, act, and oact
- SIG_DFL and SIG_IGN
- a user-defined handler is a void function which takes one
integer argument
Examples of blocking signals
- blocktest.c: a program which blocks and unblocks SIGINT
- makepair.c: a function which blocks all signals
while creating two named pipes;
intent is to ensure the deallocation of both
pipes should an error occur
- blockchild.c: a program which blocks signals
before calling fork and execl;
child cannot restore original mask after exec, why?
- password.c: a function which blocks some
signals which, if delivered, might put the terminal
in unusable state, during password entry
More examples of handling signals
- handlectrlc.c: a program which sets up a signal
handler for <ctrl-c>
- notice that execution resumes at the statement
immediately following a sleep
interrupted by the signal;
add another sleep to verify that the <ctrl-c>
is actually being handled
- order in which blocked signals are delivered is arbitrary
and only one of a kind is delivered
- signalterminate.c:
a program which which terminates gracefully when it
receives a <ctrl-c>;
accesses to doneflag is a critical section
- handler should not modify it while main is examining it
- sig_atomic_t: an integral type that ensures
atomic access
- use of volatile qualifier suppresses compiler
optimization, such as only
evaluating the loop condition before the first
iteration, that might otherwise occur
- in this case, the use of volatile
informs the compiler that doneflag may
be changed asynchronously to the program's execution
- averagesin.c: a program to estimate the
average values of sin(x) over the interval from 0 to 1
- access to the results string is the critical section
of this program;
we do not want main modifying the string
while the handler is writing it to standard output;
thus, main blocks SIGUSR1 while
modifying the results string
- example of case where we might want to send a signal
to ourself
Introduction to waiting for signals efficiently
second example:
static volatile sig_atomic_t sigreceived = 0;
int signum;
sigset_t sigset;
sigemptyset (&sigset);
sigaddset (&sigset, signum);
sigprocmask (SIGBLOCK, &sigset, NULL);
while (sigreceived == 0)
pause();
here, we are trying to prevent the delivery of
a signal between the test and call to pause
by blocking
problem: the desired signal is blocked; pause will
never return
so why not unblock it immediately before the call to pause?
catch-22; see example 1
we really want to perform two operations (unblocking and the call to
pause) in one stroke
there must be a solution? what to do? use sigsuspend;
to be continued ....
sigsuspend function
- blocks and waits atomically
- returns when signal handler returns
- on return, resets the mask back to what is was before the call
- example 1: code to wait for a particular signal
- example 2: code to wait for a particular signal while not
blocking other signals;
notice that when sigsuspend returns, the
process signal mask is reset to the value it had
before sigsuspend was called
- example 3: cleaner version of code from example 2
C object implementations for signal-based control
- simplesuspend.c: object which safely blocks on a specific signal;
simplesuspendtest.c: driver that waits for SIGUSR1
- notifyonoff.c: object which provides two-signal control
for turning a service on or off
- uses 2 signals to control the setting/clearing of a flag
- initnotify: takes two signals
- signo1 - sets the notify flag
- signo2 - clears the notify flag
- waitnotifyon: waits until notify flag is set on by
delivery of signo1
- biff.c: a safe implementation of biff based on
notifyonoff.c
- biff y: sends SIGUSR1
to enable notifications
- biff n: sends SIGUSR2
to disable notifications
sigwait function
- the complement of sigsuspend
- both take a signal set
- sigwait takes those signals that are to be waited for,
while sigsuspend takes those that are to be blocked
- signals in the set passed to sigwait should be
blocked prior to the call
- the number of the pending signal removed from the pending
signals is pointed to by signo
- sigwait does not change the process mask, while
sigsuspend does
- countsignals.c: a program which uses sigwait
and counts the number of SIGUSR1 signals received
Important issues in the handling of signals
- should we restart POSIX library functions interrupted by signals?
- it depends on the particular call
- slow POSIX library calls are those interrupted by signals
- they return when the signal handler returns
- terminal i/o and reads from a pipe are considered slow,
i.e., they can block a process for an undetermined period of time
- disk i/o blocks for shorter periods
- functions like getpid do not block at all
- errno will get set to EINTR if function
interrupted by a signal
- program must handle this error and possibly restart the system
call
- no logical way to determine, must see man page
- should signal handlers call non-reentrant functions (e.g., strtok)?
- in this context, asynchronous means `at any time'
- signals add concurrency to a program, why?
since they occur asynchronously, a process may catch a
signal while executing a library function!
e.g., suppose a signal, whose handler calls strtok, interrupts
the execution of strtok
- a function is async-signal safe if it can be
safely called from a signal handler
- many POSIX calls are not async-signal safe due to the use of
- static data structures
- malloc or free
- global data structures
- bottom line: you must exercise caution when calling
library functions from within signal handlers
- see table of async-signal safe library functions on p. 285
- how to handle errors that use errno?
- problem is errors in signal handler can interfere
with error handling of rest of program
- example:
- part of a program returns -1 and sets errno
- now a signal is caught (and handled) before the error message
is printed
- if the signal handler calls a function which causes errno
to be changed, an incorrect error might be reported
- simple solution: make signal handlers save and restore errno
if they use functions that might change it
- follow signal-handling guidelines at top of p.286
Sophisticated program control: sigsetjmp and siglongjmp
- consider the following control flow problems:
- if blocking a ctrl-c during a complex calculation,
where to transfer program control when the calculation is complete?
- how to restart a dialog when the user enters
an erroneous response somewhere several nested-menus deep?
- C programs use signals (indirectly or directly) to address these
situations
- resembles exception-handling in OO languages and systems
- also similar to the call/cc construct of Scheme
- indirect approach
- signal handler sets a flag
- program tests that flag in strategic places
- complex: program might have to return through several layers of
functions
- direct approach: use sigsetjmp and siglongjmp
- sigsetjump is like a statement label
- siglongjmp is like a goto
- difference is that this pair of functions cleans up (unravels) the
program stack and signal states in addition to transferring control
- sigsetjump takes an env parameter of type
sigjmp_buf to store the context
- siglongjump takes env to restore the context
and an int val to simulate the return of sigsetjump
Overview of using asynchronous I/O
- recall system calls block the current process
e.g., read and write usually block a process
until the i/o completes (synchronous)
- what if we want to continue executing?
use POSIX:AIO Asynchronous I/O Extension
- relevant functions
- aio_read
- aio_write
- aio_return
- aio_error
- aio_cancel
- main idea is that a signal notifies the process when
the i/o is complete
- struct aiocb (asynchronous i/o control block)
contains
- fields which specify how to read/write (these are
the same as the parameters to read and write)
- int aio_fildes
- volatile void *aio_buf
- size_t aio_nbytes
- a field (struct sigevent aio_sigevent) to
specify the signal number and handler used during the notification
of i/o completion
- aio_sigevent.sigev_notify;
if this variable contains SIGEV_NONE,
then the OS does not generate a signal when i/o
completes (use polling instead; see below)
- aio_sigevent.sigev_signo
- example: consider a program that
reads from a pipe and writes to an ordinary file
- may want to use asynchronous i/o for the pipe since
since it might block for a long time
- may want to use regular i/o (with blocking) for the write to the file
- see asynsignalmain.c ([USP] Program 8.13) and
asyncmonitorsignal.c ([USP] Program 8.14)
- another example (courtesy [USP] §8.10, pp. 299-300):
reading from a disk file and writing to a slow device
- main calls aio_read
- read signal handler calls aio_write
- write signal handler calls aio_read
- can we do asynchronous i/o without signals?
- sure, use what is called polling
- break up other work into small pieces
- call aio_error after each piece
to check if the i/o operation completed
- what should we do if the i/o is
still not finished, yet we have no more work to do?
- there are several options (see bottom of [USP] p. 297)
involving functions like select, pause,
sigsuspend, and sigwait
- use aio_suspend
References
| [USP] |
K.A. Robbins and S. Robbins.
UNIX Systems Programming: Concurrency, Communication, and Threads.
Prentice Hall, Upper Saddle River, NJ, Second edition, 2003.
|
|