10.4 System Calls Related to Signal Handling

As stated in the introduction of this chapter, programs running in User Mode are allowed to send and receive signals. This means that a set of system calls must be defined to allow these kinds of operations. Unfortunately, for historical reasons, several system calls exist that serve essentially the same purpose. As a result, some of these system calls are never invoked. For instance, sys_sigaction( ) and sys_rt_sigaction( ) are almost identical, so the sigaction( ) wrapper function included in the C library ends up invoking sys_rt_sigaction( ) instead of sys_sigaction( ). We shall describe some of the most significant POSIX system calls.

10.4.1 The kill( ) System Call

The kill(pid,sig) system call is commonly used to send signals; its corresponding service routine is the sys_kill( ) function. The integer pid parameter has several meanings, depending on its numerical value:

pid > 0

The sig signal is sent to the process whose PID is equal to pid.

pid = 0

The sig signal is sent to all processes in the same group as the calling process.

pid = -1

The signal is sent to all processes, except swapper (PID 0), init (PID 1), and current.

pid < -1

The signal is sent to all processes in the process group -pid.

The sys_kill( ) function sets up a minimal siginfo_t table for the signal, and then invokes kill_something_info( ):

info.si_signo = sig;
info.si_errno = 0;
info.si_code = SI_USER;
info._sifields._kill._pid = current->pid;
info._sifields._kill._uid = current->uid;
return kill_something_info(sig, &info, pid);

The kill_something_info( ) function, in turn, invokes either send_sig_info( ) (to send the signal to a single process), or kill_pg_info( ) (to scan all processes and invoke send_sig_info( ) for each process in the destination group).

The kill( ) system call is able to send any signal, even the so-called real-time signals that have numbers ranging from 32 to 63. However, as we saw in the earlier section Section 10.2, the kill( ) system call does not ensure that a new element is added to the pending signal queue of the destination process, thus multiple instances of pending signals can be lost. Real-time signals should be sent by means of a system call like rt_sigqueueinfo( ) (see the later section Section 10.4.6).

System V and BSD Unix variants also have a killpg( ) system call, which is able to explicitly send a signal to a group of processes. In Linux, the function is implemented as a library function that uses the kill( ) system call. Another variation is raise( ), which sends a signal to the current process (that is, to the process executing the function). In Linux, raise() is implemented as a library function.

10.4.2 Changing a Signal Action

The sigaction(sig,act,oact) system call allows users to specify an action for a signal; of course, if no signal action is defined, the kernel executes the default action associated with the delivered signal.

The corresponding sys_sigaction( ) service routine acts on two parameters: the sig signal number and the act table of type sigaction that specifies the new action. A third oact optional output parameter may be used to get the previous action associated with the signal.

The function checks first whether the act address is valid. Then it fills the sa_handler, sa_flags, and sa_mask fields of a new_ka local variable of type k_sigaction with the corresponding fields of *act:

_ _get_user(new_ka.sa.sa_handler, &act->sa_handler); 
_ _get_user(new_ka.sa.sa_flags, &act->sa_flags); 
_ _get_user(mask, &act->sa_mask); 
siginitset(&new_ka.sa.sa_mask, mask); 

The function invokes do_sigaction( ) to copy the new new_ka table into the entry at the sig-1 position of current->sig->action (the number of the signal is one higher than the position in the array because there is no zero signal):

k = &current->sig->action[sig-1]; 
spin_lock(&current->sig->siglock);
if (act) { 
    *k = *act; 
    sigdelsetmask(&k->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP)); 
    if (k->sa.sa_handler == SIG_IGN || (k->sa.sa_handler == SIG_DFL &&
               (sig == SIGCONT || sig == SIGCHLD || sig == SIGWINCH))) { 
        spin_lock_irq(&current->sigmask_lock);
        if (rm_sig_from_queue(sig, current))
            recalc_sigpending(current);
        spin_unlock_irq(&current->sigmask_lock);
    } 
} 

The POSIX standard requires that setting a signal action to either SIG_IGN or SIG_DFL when the default action is "ignore," causes any pending signal of the same type to be discarded. Moreover, notice that no matter what the requested masked signals are for the signal handler, SIGKILL and SIGSTOP are never masked.

If the oact parameter is not NULL, the contents of the previous sigaction table are copied to the process address space at the address specified by that parameter:

if (oact) { 
    _ _put_user(old_ka.sa.sa_handler, &oact->sa_handler); 
    _ _put_user(old_ka.sa.sa_flags, &oact->sa_flags); 
    _ _put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); 
} 

Notice that the sigaction( ) system call also allows initialization of the sa_flags field in the sigaction table. We listed the values allowed for this field and the related meanings in Table 10-4 (earlier in this chapter).

Older System V Unix variants offered the signal( ) system call, which is still widely used by programmers. Recent C libraries implement signal( ) by means of sigaction( ). However, Linux still supports older C libraries and offers the sys_signal( ) service routine:

new_sa.sa.sa_handler = handler; 
new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK; 
ret = do_sigaction(sig, &new_sa, &old_sa); 
return ret ? ret : (unsigned long)old_sa.sa.sa_handler; 

10.4.3 Examining the Pending Blocked Signals

The sigpending( ) system call allows a process to examine the set of pending blocked signals—i.e., those that have been raised while blocked. The corresponding sys_sigpending( ) service routine acts on a single parameter, set, namely, the address of a user variable where the array of bits must be copied:

spin_lock_irq(&current->sigmask_lock);
sigandsets(&pending, &current->blocked, &current->pending.signal);
spin_unlock_irq(&current->sigmask_lock);
copy_to_user(set, &pending, sizeof(sigset_t));

10.4.4 Modifying the Set of Blocked Signals

The sigprocmask( ) system call allows processes to modify the set of blocked signals; it applies only to regular (non-real-time) signals. The corresponding sys_sigprocmask( ) service routine acts on three parameters:

oset

Pointer in the process address space to a bit array where the previous bit mask must be stored

set

Pointer in the process address space to the bit array containing the new bit mask

how

Flag that may have one of the following values:

SIG_BLOCK

The *set bit mask array specifies the signals that must be added to the bit mask array of blocked signals.

SIG_UNBLOCK

The *set bit mask array specifies the signals that must be removed from the bit mask array of blocked signals.

SIG_SETMASK

The *set bit mask array specifies the new bit mask array of blocked signals.

The function invokes copy_from_user( ) to copy the value pointed to by the set parameter into the new_set local variable and copies the bit mask array of standard blocked signals of current into the old_set local variable. It then acts as the how flag specifies on these two variables:

if (copy_from_user(&new_set, set, sizeof(*set))) 
    return -EFAULT; 
new_set &= ~(sigmask(SIGKILL)|sigmask(SIGSTOP)); 
spin_lock_irq(&current->sigmask_lock);
old_set = current->blocked.sig[0]; 
if (how == SIG_BLOCK) 
    sigaddsetmask(&current->blocked, new_set); 
else if (how == SIG_UNBLOCK) 
    sigdelsetmask(&current->blocked, new_set); 
else if (how == SIG_SETMASK) 
    current->blocked.sig[0] = new_set; 
else 
    return -EINVAL; 
recalc_sigpending(current);
spin_unlock_irq(&current->sigmask_lock); 
if (oset) { 
    if (copy_to_user(oset, &old_set, sizeof(*oset))) 
        return -EFAULT; 
} 
return 0; 

10.4.5 Suspending the Process

The sigsuspend( ) system call puts the process in the TASK_INTERRUPTIBLE state, after having blocked the standard signals specified by a bit mask array to which the mask parameter points. The process will wake up only when a nonignored, nonblocked signal is sent to it.

The corresponding sys_sigsuspend( ) service routine executes these statements:

mask &= ~(sigmask(SIGKILL) | sigmask(SIGSTOP)); 
spin_lock_irq(&current->sigmask_lock);
saveset = current->blocked; 
siginitset(&current->blocked, mask);
recalc_sigpending(current); 
spin_unlock_irq(&current->sigmask_lock);
regs->eax = -EINTR; 
while (1) { 
    current->state = TASK_INTERRUPTIBLE; 
    schedule(  ); 
    if (do_signal(regs, &saveset)) 
        return -EINTR; 
} 

The schedule( ) function selects another process to run. When the process that issued the sigsuspend( ) system call is executed again, sys_sigsuspend( ) invokes the do_signal( ) function to deliver the signal that has woken up the process. If that function returns the value 1, the signal is not ignored. Therefore the system call terminates by returning the error code -EINTR.

The sigsuspend( ) system call may appear redundant, since the combined execution of sigprocmask( ) and sleep( ) apparently yields the same result. But this is not true: because of interleaving of process executions, one must be conscious that invoking a system call to perform action A followed by another system call to perform action B is not equivalent to invoking a single system call that performs action A and then action B.

In the particular case, sigprocmask( ) might unblock a signal that is delivered before invoking sleep( ). If this happens, the process might remain in a TASK_INTERRUPTIBLE state forever, waiting for the signal that was already delivered. On the other hand, the sigsuspend( ) system call does not allow signals to be sent after unblocking and before the schedule( ) invocation because other processes cannot grab the CPU during that time interval.

10.4.6 System Calls for Real-Time Signals

Since the system calls previously examined apply only to standard signals, additional system calls must be introduced to allow User Mode processes to handle real-time signals.

Several system calls for real-time signals (rt_sigaction( ), rt_sigpending( ), rt_sigprocmask( ), and rt_sigsuspend( )) are similar to those described earlier and won't be discussed further. For the same reason, we won't discuss two other system calls that deal with queues of real-time signals:

rt_sigqueueinfo( )

Sends a real-time signal so that it is added to the pending signal queue of the destination process

rt_sigtimedwait( )

Dequeues a blocked pending signal without delivering it and returns the signal number to the caller; if no blocked signal is pending, suspends the current process for a fixed amount of time.