- Forward


Signals
An Introduction


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu

Print

Review
Back SMYC Forward
  • Program:
    • A file containing machine language instructions, the entry-point address, data, symbol and relocation tables, and other information
  • Process:
    • An instance of a program execution
Overview
Back SMYC Forward
  • Purpose of Signals:
    • Interrupt the normal flow of execution
  • Use:
    • Notify a process that an event has occurred
  • A Hardware Analogy:
    • Exceptions/Interrupts
Overview (cont.)
Back SMYC Forward
  • Parties:
    • Kernel and and process
    • Two processes
    • One process (i.e., sending a signal to itself)
  • Identification:
    • Each signal is a unique small integer (defined in <signal.h> signal.h )
  • Categories:
    • Standard - numbered from 1 to 31
    • Realtime - all others
Signals from the Kernel
Back SMYC Forward
  • Resulting from Hardware Exceptions:
    • Divide by zero
    • Referencing inaccessible memory
  • Special User Input:
    • Interrupt (usually Ctrl+C)
    • Suspend (usually Ctrl+Z)
  • Software Events:
    • Input became available on a file descriptor
    • A child terminated
    • A terminal window was resized
Terminology
Back SMYC Forward
  • Generation:
    • Signals are said to be generated by an event
  • Delivery:
    • Signals are said to be delivered to a process
  • Pending:
    • Signals are said to be pending between the time they are generated and the time they are delivered
Delivery
Back SMYC Forward
  • Normally:
    • A pending signal is delivered as soon as the receiving process is next scheduled to run
  • Blocking Delivery:
    • To ensure that a block of code is not interrupted by the delivery of a particular signal we can add that signal to the process's signal mask (in which case it will remain pending until it is later removed from the mask)
Things You Might Not Expect
Back SMYC Forward
  • Signals Are Not Queued:
    • The set of pending signals is represented by a bit vector (where the bits correspond to the signal numbers) which means there can be only one pending signal with a particualr number at a time
  • Interrupting Handlers:
    • Handlers can, themselves, be interrupted by signals
  • Implicit Blocking:
    • By default, the kernel blocks pending signals of the type currently being processed
Default Effect on the Process
Back SMYC Forward
  • Ignored:
    • The process never knows the signal was generated
  • Terminated:
    • The process is killed (called abnormal termination) with or without a core dump
  • Stopped:
    • Execution of the process is suspended
  • Resumed:
    • Execution of the process is resumed (after having been stopped previously)
Standard Signals
Back SMYC Forward
unix_signals
Changing the Disposition
Back SMYC Forward
  • An Observation:
    • The default effect is not always what is desired
  • Possible Dispositions:
    • The default
    • The signal is ignored
    • A signal handler (i.e., a function written for the purpose of handling the signal) is executed
More Terminology
Back SMYC Forward
  • Installing/Establishing:
    • Notifying the kernel that a signal handler should be invoked
  • Handling/Catching:
    • Invoking a signal handler
Changing the Disposition: signal() signal
Back SMYC Forward
void ( *signal(int num, void (*handler)(int)) ) (int);
Purpose:
Set the disposition of a signal
Details:
num The signal number
handler The signal handler
Return The previous disposition on success; SIG_ERR on error
#include <signal.h> signal.h

Understanding the Signature:

handler is a pointer to a function that returns nothing and is passed an int.

signal() itself returns a pointer to a function that returns nothing and is passed an int.

Improving the Readability of signal()
Back SMYC Forward
  • Add a Type Definition:
    • typedef void (*signalhandler_t)(int);
  • The "New" Prototype for signal():
    • sighandler_t signal(int num, sighandler_t handler);
"Special" Signal Handlers
Back SMYC Forward
  • SIG_DFL:
    • Reset the disposition of the signal to its default
  • SIG_IGN:
    • Ignore the signal
Going Further: Portable Signal Handling
Back SMYC Forward
  • Non-Portability of signal():
    • Some systems restore the action for a signal to its default after it has been caught by a handler
    • On some systems, slow system calls (e.g., read(), wait()) that are interrupted by a signal do not resume when the handler returns (instead they return immediately and set errno to EINTR)
  • The Posix Standard:
    • sigaction() allows the caller to specify all behaviors in a standard way (but it is complicated to use)
Going Further: Portability: sigaction() sigaction
Back SMYC Forward
int sigaction(int num, const struct sigaction *act, struct sigaction *oact)
Purpose:
Examine and change a signal action
Details:
num The signal number
act The action to be assoicated with the signal (or NULL)
oact The previous action assoicated with the signal (or NULL)
Return 0 on success; -1 on error
#include <signal.h> signal.h

Note: struct sigaction contains a pointer to the signal handler, the additional signals to be blocked, flags, etc...

Sending Signals from the Shell: kill kill
Back SMYC Forward
  • What it Does:
    • Sends a signal to a process (if the ID is positive) or process group (if the ID is negative)
  • Examples:
    • kill -9 22807 sends signal 9 (i.e., SIGKILL) to process 22807
    • kill -19 -11975 sends signal 19 (i.e., SIGSTOP) to process group 11975
Sending Signals from the Shell (cont.)
Back SMYC Forward
  • Recall:
    • Terminals are responsible for the handling of some special characters
  • Some Important Examples:
    • Ctrl+C sends a SIGINT to every process in the foreground process group (which, unless the default action has been modified, terminates the process)
    • Ctrl+Z sends a SIGTSTP to every process in the foreground process group (which, unless the default action has been modified, suspends the process)
Sending Signals: kill() kill
Back SMYC Forward
int kill(pid_t pid, int num)
Purpose:
Send a signal to a process of process group
Details:
pid The destination of the signal, specifically the process ID (if > 0), all processes in the group of the sender (if 0), all processes (if -1), all processes in the absolute value of the given group ID (if < -1)
num Is the signal number
Return 0 on success; -1 on error
#define _POSIX_SOURCE
#include <sys/types.h> sys/types.h
#include <signal.h> signal.h

Note: A signal will only be sent to processes for which the calling process has the appropriate permission

Good Uses of Signals
Back SMYC Forward
  • In the Abstract:
    • When one process needs to notify another process of an event and coordination is unimportant
  • Examples:
    • Changing the volume of a media player
    • Stopping an iterative calculation
    • Informing a process of a state change
Reasonable Uses of Signals
Back SMYC Forward
  • In the Abstract:
    • When one process needs to send a single bit of information to another process (i.e., an "alert")
  • Examples:
    • Do/Don't calculate some value
    • Something does/doesn't exist
An Example of "Alerting"
Back SMYC Forward
  • The Setting:
    • A point of sales system that needs to calculate the total sales and the sales tax
    • Some customers are "tax exempt"
  • A Conventional System:
    • Calculate the tax status first (which takes time), then calculate the total sales (which takes time), then calculate the sales tax (which is essentially instantaneous)
An Example of "Alerting" - A Conventional System
Back SMYC Forward
unixexamples/signals/tax0.c
 
An Example of "Alerting" - A Concurrent System
Back SMYC Forward
  • An Observation:
    • The conventional system always requires additive time
  • Using Concurrency:
    • Calculate the tax status using a child process
    • Calculate the total sales in the parent process
    • After both of the above are done, calculate the tax in the parent process
  • Possible Advantage:
    • If the parent doesn't have to wait for the child then the system only requires as much time as the parent
    • If the parent has to wait for the child then the system only requires as much time as the child
An Example of "Alerting" - A Defective Concurrent System
Back SMYC Forward
unixexamples/signals/tax1.c
 
  • Understanding the Defect:
    • What value will should have in the two processes? Why won't giving should file scope fix it?
      Expand
An Example of "Alerting" - Using a Signal
Back SMYC Forward
  • An Observation:
    • The child process needs to communicate information to the parent
  • Why a Signal Can Work:
    • Since the information to be communicated is binary (i.e., tax exempt or not), a (single) signal can be used
An Example of "Alerting" - Using a Signal (cont.)
Back SMYC Forward
unixexamples/signals/tax2.c
 
Signals and Coordination
Back SMYC Forward
  • An Observation:
    • We could use a simple coordination protocol in the previous example because the parent could wait until the process terminated
  • Other Situations:
    • This isn't the case either for efficiency reasons or for other reasons
  • The Implication:
    • In general, because they are transient, signals are not a good tool for coordination
A "Real World" Example
Back SMYC Forward
  • A Small Change to the Earlier Example:
    • After the child process calculates the status it must perform another task
    • This other task takes a long time and the parent doesn't need the result
  • The Implication:
    • There is no reason for the parent to wait() for the child to terminate
A "Real World" Example (cont.)
Back SMYC Forward
  • Ten Thousand Monkeys at Ten Thousand Keyboards:
    • Remove the call to wait()
  • The Defect:
    • The parent may finish calculating the total sales before the signal has been sent
  • What's Needed:
    • Put the parent in the stopped state until it receives the signal (rather than until the child terminates)
Signals and Coordination: pause() pause
Back SMYC Forward
int pause(void)
Purpose:
Put a calling process in the stopped state until any signal is received
Details:
Return -1
#include <unistd.h> unistd.h

Note: The sleep() sleep function puts the calling process in the stopped state until a given amount of time passes or it is interrupted by a signal.

Signals and Coordination Revisited
Back SMYC Forward

The Goal: Sequence Task 1 Before Task 2

unixexamples/signals/sequence.c
 

The Race Condition: The child process might call kill() before the parent process calls pause().

A "Real World" Example Revisited
Back SMYC Forward
  • What We Have:
    • A mechanism for "pausing" the parent
  • What We Don't Have:
    • A correct coordination protocol
A "Real World" Example - An Incorrect Coordination Protocol
Back SMYC Forward
unixexamples/signals/tax3.c
 
  • The Race Condition:
    • If it takes the parent longer to calculate the total sales than it takes the child to calculate the status then the signal will be generated and delivered before the call to pause() and pause() will never return.
      Expand
Another Important Situation
Back SMYC Forward
  • The Situation:
    • A signal interrupts a block of code that shouldn't be interrupted (i.e., a critical section)
  • What We Can't Do:
    • Prevent the signal from being generated
  • What We Can Do:
    • Prevent the signal from being delivered
Explicit Blocking: sigprocmask() sigprocmask
Back SMYC Forward
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
Purpose:
Change the set of currently blocked signals
Details:
how The change to perform
set The set of signals
oldset Will hold the old set of signals (if non-NULL)
Return 0 on success; -1 on error
#include <signal.h> signal.h

how can be SIG_BLOCK to make blocked = blocked | set, SIG_UNBLOCK to make blocked = blocked & ~set, or SIG_SETMASK to make blocked = set.

Explicit Blocking (cont.)
Back SMYC Forward
  • Initializing Signal Sets:
    • sigemptyset() sigemptyset initializes the given set to the empty set
    • sigfillset() sigfillset adds every signal to the given set
  • Manipulating Signal Sets:
    • sigaddset() sigaddset adds a given signal number to the given set
    • sigdelset() sigdelset removes a given signal number to the given set
A "Real World" Example - Another Incorrect Coordination Protocol
Back SMYC Forward
unixexamples/signals/tax4.c
 
  • The Race Condition:
    • The signal could be delivered to the parent process before pause() is invoked, in which case pause() will never return.
      Expand
Block-Suspend-Reset
Back SMYC Forward
  • A Common Mistake:
    • Using the following three statements
      sigprocmask(SIG_BLOCK, &set, &oldset); pause(); sigprocmask(SIG_BLOCK, &oldset, NULL);
  • The Race Condition:
    • A signal might be received after the first call to sigprocmask() but before the call to pause()
Block-Suspend-Reset: sigsuspend() sigsuspend
Back SMYC Forward
int sigsuspend(const sigset_t *set)
Purpose:
Temporarily replace the current blocked set and suspend the process
Details:
set The signal set to block
Return -1
#include <signal.h> signal.h

sigsuspend() is essentially the same as the three calls above, but the first two calls are atomic.

Signal Handling Patterns
Back SMYC Forward
  • Keep Handlers Simple:
    • Often this means that the handler only sets a flag and returns (in a sense, providing a persistence mechanism)
  • Only Call "Signal Safe" Functions:
    • See signal signal for a list (which, notably, does not include printf() or exit())
  • Protect Access to Shared Global Data:
    • Temporarily block all signals while using such data
Signal Handling Patterns (cont.)
Back SMYC Forward
  • Declare Global Variables with volatile:
    • Forces the compiler to read the value from memory (not a cache) each time the variable is referenced
  • Declare Flags with sig_atomic_t:
    • Guarantees reads and writes are atomic
What Makes a Function "Signal Safe"?
Back SMYC Forward
  • Not Interruptible:
    • For obvious reasons, it can't calls functions that can be interrupted by a signal
  • Reentrancy:
    • One Definition - A function is reentrant if its execution can be interrupted and it can be called again (before the previous invocation is completed)
    • Another Definition - A function is reentrant if its instructions are invariant and, hence, can be shared across instances
There's Always More to Learn
Back -