previous
 next 
CS 3733 Operating Systems Notes: USP Chapter 8 - Signals
(change semester)

  Files are available by executing ~classque/usp-08 on the CS network.
 
 

A signal is a software notification to a process of an event.

Terminology:



SignalDescriptiondefault action
SIGABRT process abort implementation dependent 
SIGALRM alarm clock abnormal termination
SIGBUS access undefined part of memory object implementation dependent
SIGCHLD child terminated, stopped or continued ignore
SIGCONT execution continued if stopped continue
SIGFPE error in arithmetic operation as in division by zero implementation dependent
SIGHUP hang-up (death) on controlling terminal (process) abnormal termination
SIGILL invalid hardware instruction implementation dependent
SIGINT interactive attention signal (usually ctrl-C) abnormal termination
SIGKILL terminated (cannot be blocked, caught or ignored) abnormal termination
SIGPIPE write on a pipe with no readers abnormal termination
SIGQUIT interactive termination: core dump (usually ctrl-|) implementation dependent
SIGSEGV invalid memory reference implementation dependent
SIGSTOP execution stopped (cannot be blocked, caught or ignored)    stop
SIGTERM termination abnormal termination
SIGTSTP terminal stop stop
SIGTTIN background process attempting to read stop
SIGTTOU background process attempting to write stop
SIGURG high bandwidth data available at a socket ignore
SIGUSR1 user-defined signal 1 abnormal termination
SIGUSR2 user-defined signal 2 abnormal termination
Table 8.1: The POSIX required signals.



You can send a signal to a process from the command line using kill
kill -l will list the signals the system understands
kill [-signal] pid will send a signal to a process.
The optional argument may be a name or a number.
The default is SIGTERM.
To unconditionally kill a process, use:
kill -9 pid
kill -KILL pid.


From a program you can use the kill system call:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);


Example 8.4: send SIGUSR1 to process 3423:
   if (kill(3423, SIGUSR1) == -1)
      perror("Failed to send the SIGUSR1 signal");


Example 8.5: a child kills its parent:
if (kill(getppid(), SIGTERM) == -1)
    perror ("Failed to kill parent");


Example 8.5: a process sends a signal to itself:
if (raise(SIGUSR1) != 0)
   perror("Failed to raise SIGUSR1");


Example 8.8: kill an infinite loop after 10 seconds:
int main(void) {
   alarm(10);
   for ( ; ; ) ;
}



Signal Mask and Signal Sets
How do you deal with a set of signals?
Originally, an int would hold a collection of bits, one per signal.
Now, signal sets of type sigset_t are used.
Here are the routines for handling sets of signals.
You should compare these to the way select handles sets of file descriptors.

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);


sigemptyset initializes the set to contain no signals
sigfillset puts all signals in the set
sigaddset adds one signal to the set
sigdelset removes one signal from the set
sigismember tests to see if a signal is in the set
The process signal mask is a set of signals that are blocked.
A blocked signal remains pending after it is generated until the signal is unblocked.
The process signal mask is modified with the sigprocmask system call.
   #include <signal.h>
   int sigprocmask(int how, const sigset_t *restrict set,
                   sigset_t *restrict oset);

The how can be:
Either set or oset may be NULL.

Example 8.9: initialize a signal set:
if ((sigemptyset(&twosigs) == -1) ||
    (sigaddset(&twosigs, SIGINT) == -1)  ||
    (sigaddset(&twosigs, SIGQUIT) == -1))
    perror("Failed to set up signal mask");


Example 8.10: add SIGINT to the set of blocked signals:
sigset_t newsigset;
 
if ((sigemptyset(&newsigset) == -1) ||
    (sigaddset(&newsigset, SIGINT) == -1))
   perror("Failed to initialize the signal set");
else if (sigprocmask(SIG_BLOCK, &newsigset, NULL) == -1)
   perror("Failed to block SIGINT");


Program 8.1 on page 263 blocks and unblocks SIGINT
Program 8.2 on page 264 blocks a signal while creating two pipes.
Program 8.3 on page 265 blocks all signals before a fork and exec.
Program 8.4 on page 266 blocks some signals while getting a password.
The process signal mask is inherited by the child process.

Signal Activity: Block All Signals



Catching and Ignoring Signals: sigaction

#include <signal.h>

int sigaction(int signo, const struct sigaction *act,
                          struct sigaction *oact);
struct sigaction {
   void (*sa_handler)(int); /* SIG_DFL, SIG_IGN or pointer to function */
   sigset_t sa_mask;        /* additional signals to be blocked
                                  during execution of handler */
   int sa_flags;            /* special flags and options */
   void(*sa_sigaction) (int, siginfo_t *, void *); /* realtime handler */
};


Either act or oact may be NULL.
If the SA_SIGINFO flag of the sa_flags field is clear, sa_handler specifies the action to be taken.

void (*sa_handler)() means a pointer to a function that has no return value.

Example 8.15: ignore SIGINT if the default action is set:
struct sigaction act;
 
if (sigaction(SIGINT, NULL, &act) == -1)  /* Find current SIGINT handler */
   perror("Failed to get old handler for SIGINT");
else if (act.sa_handler == SIG_DFL) {    /* if SIGINT handler is default */
   act.sa_handler = SIG_IGN;         /* set new SIGINT handler to ignore */
   if (sigaction(SIGINT, &act, NULL) == -1)
      perror("Failed to ignore SIGINT");
}


Example 8.16: set up a signal handler for SIGINT:
void catchctrlc(int signo) {
   char handmsg[] = "I found Ctrl-C\n";
   int msglen = sizeof(handmsg);
 
   write(STDERR_FILENO, handmsg, msglen);
}
...
struct sigaction act;
act.sa_handler = catchctrlc;
act.sa_flags = 0;
if ((sigemptyset(&act.sa_mask) == -1) ||
    (sigaction(SIGINT, &act, NULL) == -1))
   perror("Failed to set SIGINT to handle Ctrl-C");


Example 8.18: set the action of SIGINT to the default:
   struct sigaction newact;
 
   newact.sa_handler = SIG_DFL;    /* new handler set to default */
   newact.sa_flags = 0;            /* no special options */
   if ((sigemptyset(&newact.sa_mask) == -1) ||
       (sigaction(SIGINT, &newact, NULL) == -1))
      perror("Failed to set SIGINT to the default action");

Example 8.19: see if a signal is ignored:
int testignored(int signo) {
   struct sigaction act;
   if ((sigaction(signo, NULL, &act) == -1) || (act.sa_handler != SIG_IGN))
      return 0;
   return 1;
}

An incorrect implementation of Program 8.5
Program 8.5: A program that terminates gracefully on ctrl-C
Program 8.6: A program to estimate the average value of sin(x)


Waiting for signals
Old way: pause
#include <unistd.h>
int pause(void);


Exercise 8.21: an incorrect way to wait for a signal:
static volatile sig_atomic_t sigreceived = 0;

while(sigreceived == 0)
    pause();


Exercise 8.22: another incorrect way to wait for a signal:
static volatile sig_atomic_t sigreceived = 0;

int signum;
sigset_t sigset;
 
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigprocmask(SIG_BLOCK, &sigset, NULL);
while(sigreceived == 0)
   pause();


What you need to do: atomically unblock the signal and suspend the process.
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);


Sets the signal mask to the one pointed to by sigmask and suspends the process until a signal is received.
When it returns the signal mask is restored to what it was before it was called.
This function always returns -1.
Simple wait for signal: Suppose we have:
static volatile sig_atomic_t sigreceived = 0;

and have set up the following signal handler for SIGUSR1:
void catch_signal(int signo) {
   sigreceived = 1;
}


How to wait for a SIGUSR1 signal:
set a sigset_t, emptymask, to contain no signals
block SIGUSR1
while(sigreceived == 0)
   sigsuspend(&emptymask);

Here is a simple implementation:
 1. sigset_t emptymask, sigmask;
 2. sigemptyset(&emptymask);
 3. sigemptyset(&sigmask);
 4. sigaddset(&sigmask, SIGUSR1);
 5. sigprocmask(SIG_BLOCK, &sigmask, NULL);
 6. while(sigreceived == 0)
 7.    sigsuspend(&emptymask);


Which of the above lines always change the process signal mask?

The following is a more robust version.
Example 8.25: a correct way to wait for a signal:
 1. static volatile sig_atomic_t sigreceived = 0;

 3. sigset_t maskblocked, maskold, maskunblocked;
 4. int signum = SIGUSR1;

 6. sigprocmask(SIG_SETMASK, NULL, &maskblocked);
 7. sigprocmask(SIG_SETMASK, NULL, &maskunblocked);
 8. sigaddset(&maskblocked, signum);
 9. sigdelset(&maskunblocked, signum);
10. sigprocmask(SIG_BLOCK, &maskblocked, &maskold);
11. while(sigreceived == 0)
12.    sigsuspend(&maskunblocked);
13. sigprocmask(SIG_SETMASK, &maskold, NULL);


Program 8.7, simplesuspend, shows how to safely block on a specific signal.
Program 8.8 shows how to use Program 8.7.
Programs 8.9 and 8.10 implement a simple biff.


sigwait
This provides an alternative way to wait for signals.
Idea:
#include <signal.h>

int sigwait(const sigset_t *restrict sigmask, int *restrict signo);


Look at Program 8.11 on page 283 that counts signals.


Errors and async-signal safety

Three issues in handling signals:
  1. Certain blocking system calls will return -1 with errno set to EINTR if a signal is caught while the function is blocked.
    See the man page to see of a system call can set errno to EINTR.
    In this case you should usually restart the function since a real error has not occurred.
    The restart library handles this for many of the most important system calls.
    Look at the functions in the restart library.

  2. Handling errors in signal handlers.
    If you use a function that can modify errno in a signal handler, you must make sure that it does not interfere with error handling in the main part of the program.
    There is only one errno and it is used by both the main program and by the signal handler.
    Solution: in the signal handler, save and restore errno.
    void myhandler(int signo) {
       int esaved;
       esaved = errno;
       write(STDOUT_FILENO, "Got a signal\n", 13);
       errno = esaved;
    }
    


  3. Async-signal safety.

_Exit execve lseek sendto stat
_exit fchown lstat setgid symlink
accept fcntl mkdir setpgid sysconf
access fdatasync mkfifo setsid tcdrain
aio_error fork open setsockopt tcflow
aio_return fpathconf pathconf setuid tcflush
aio_suspend fstat pause shutdown tcgetattr
alarm fsync pipe sigaction tcgetpgrp
bind ftruncate poll sigaddset tcsendbreak
cfgetispeed getegid posix_trace_event sigdelset tcsetattr
cfgetospeed geteuid pselect sigemptyset tcsetpgrp
cfsetispeed getgid raise sigfillset time
cfsetospeed getgroups read sigismember timer_getoverrun
chdir getpeername readlink signal timer_gettime
chmod getpgrp recv sigpause timer_settime
chown getpid recvfrom sigpending times
clock_gettime getppid recvmsg sigprocmask umask
close getsockname rename sigqueue uname
connect getsockopt rmdir sigset unlink
creat getuid select sigsuspend utime
dup kill sem_post sleep wait
dup2 link send socket waitpid
execle listen sendmsg socketpair write

Functions that POSIX guarantees to be async-signal safe.




Realtime Signals: From Section 9.4
Recall the sigaction structure:
struct sigaction {
   void (*sa_handler)(int); /* SIG_DFL, SIG_IGN or pointer to function */
   sigset_t sa_mask;        /* additional signals to be blocked
                                  during execution of handler */
   int sa_flags;            /* special flags and options */
   void(*sa_sigaction) (int, siginfo_t *, void *); /* realtime handler */
};

If the SA_SIGINFO flag of the sa_flags field is set, sa_sigaction specifies the action to be taken.
This defines a new type a signal handler that takes three parameters instead of one.
It also defines a new data type: siginfo_t which is a structure containing at least the following:
int si_signo;            /* signal number */
int si_code;             /* cause of the signal */
union sigval si_value;   /* signal value */

where the union sigval contains at least:
int sival_int;
void *sival_ptr;


Signals set up with this type of signal handler are queued.
The system call to send one of these signals is called sigqueue rather than kill:

#include <signal.h>

int sigqueue(pid_t pid, int signo, const union sigval value);


On some systems, only certain signals can be queued, those between SIGRTMIN and SIGRTMAX.
Program 9.9, page 323 shows a program to send a queued signal to a process. Note that there is no command line function similar to kill.
Program 9.10, page 324, gives an example program that can receive SIGUSR1 signals.
Next Notes

Back to CS 3733 Notes Table of Contents