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

  • we want to avoid busy waiting (e.g., while ( ... );) when waiting on a particular signal
  • signals provide a mechanisms to avoid busy waiting
  • pause: suspends calling thread until the delivery of a signal
  • first example:
      static volatile sig_atomic_t sigreceived = 0;
      
      while (sigreceived == 0)
         pause();
      
    • here embedding pause inside a while loop simulates a wait for a particular signal; because the signal handler for that particular signal will set sigreceived
    • what happens if the desired signal is received between the test and call to pause? the handler sets sigreceived, but pause still causes the thread to wait until that signal or another is received
  • 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.

Return Home