CS 3733 Operating Systems Notes: USP Chapter 8 - Signals



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

Terminology:

  • A signal is generated when the event that causes the signal occurs.
  • A signal is delivered when the process takes action based on the signal.
  • The lifetime of a signal is the interval between its generation and delivery.
  • A signal that has been generated but not yet delivered is pending.
  • A process catches a signal if it executes a signal handler when the signal is delivered.
  • Alternatively, a process can ignore as signal when it is delivered, that is to take no action.
  • The function sigaction is used to specify what is to happen to a signal when it is delivered.
  • The signal mask determines the action to be taken when the signal is generated. It contains a list of signals to be blocked.
  • A blocked signal is not delivered to a process until it is unblocked.
  • The function sigprocmask is used to modify the signal mask.
  • Each signal has a default action which is usually to terminate the process.


    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 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 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 which is
    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");
    
    Exampel 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.


    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 not 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;
    }
    

    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.

    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.
      • Only certain system calls and library functions may be used safely in a signal handler.
      • The strtok function is an example of a function that might have a problem.
      • In fact, the POSIX standard only graruantees that a small number of functions are async-signal safe, that is safe to use in a signal handler and the main program.
      • Other functions may be async-signal safe in some implementations.
      • Almost none of the functions in the standard C library are on the list.
    _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.