Generating a Signal

When a signal is sent to a process, either from the kernel or from another process, the kernel generates it by invoking the send_sig_info( ), send_sig( ), force_sig( ), or force_sig_info( ) functions. These accomplish the first phase of signal handling described earlier in Section 10.1, updating the process descriptor as needed. They do not directly perform the second phase of delivering the signal but, depending on the type of signal and the state of the process, may wake up the process and force it to receive the signal.

10.2.1 The send_sig_info( ) and send_sig( ) Functions

The send_sig_info( ) function acts on three parameters:

The signal number.

info

Either the address of a siginfo_t table or one of two special values. 0 means that the signal has been sent by a User Mode process, while 1 means that it has been sent by the kernel.

A pointer to the descriptor of the destination process.

The send_sig_info( ) function starts by checking whether the parameters are correct:

The function then checks if the signal is being sent by a User Mode process. This occurs when info is equal to 0 or when the si_code field of the siginfo_t table is negative or 0 (positive values of this field mean that the signal was sent by some kernel function):

if ((!info || ((unsigned long)info != 1 && (info->si_code <=0))) && ((sig != SIGCONT) || (current->session != t->session)) && (current->euid A t->suid) && (current->euid A t->uid) && (current->uid A t->suid) && (current->uid A t->uid) && !capable(CAP_KILL)) return -EPERM;

If the signal is sent by a User Mode process, the function determines whether the operation is allowed. The signal is delivered only if at least one of the following conditions holds:

• The owner of the sending process has the proper capability (usually, this simply means the signal was issued by the system administrator; see Chapter 20).

• The signal is sigcont and the destination process is in the same login session of the sending process.

• Both processes belong to the same user.

If the sig parameter has the value 0, the function returns immediately without generating any signal. Since 0 is not a valid signal number, it is used to allow the sending process to check whether it has the required privileges to send a signal to the destination process. The function also returns if the destination process is in the task_zombie state, indicated by checking whether its siginfo_t table has been released:

Now the kernel has finished the preliminary checks, and it is going to fiddle with the signal-related data structures. To avoid race conditions, it disables the interrupts and acquires the signal spin lock of the destination process:

spin_lock_irqsave(&t->sigmask_lock, flags);

Some types of signals might nullify other pending signals for the destination process. Therefore, the function checks whether one of the following cases occurs:

• sig is a sigkill or sigcont signal. If the destination process is stopped, it is put in the task_running state so that it is able to either execute the do_exit( ) function or just continue its execution; moreover, if the destination process has sigstop, sigtstp, sigttou, or sigttin pending signals, they are removed:

rm_sig_from_queue(SIGSTOP, t);

rm_sig_from_queue(SIGTSTP, t);

rm_sig_from_queue(SIGTTOU, t);

rm_sig_from_queue(SIGTTIN, t);

The rm_sig_from_queue( ) function clears the bit in t->pending.signal associated with the signal number passed as first argument and removes any item in the pending signal queue of the process that corresponds to that signal number.

• sig is a sigstop, sigtstp, sigttin, or sigttou signal. If the destination process has a pending sigcont signal, it is removed:

rm_sig_from_queue(SIGCONT, t);

Next, send_sig_info( ) checks whether the new signal can be handled immediately. In this case, the function also takes care of the delivering phase of the signal:

spin_unlock_irqrestore(&t->sigmask_lock, flags); return 0;

The ignored_signal( ) function returns the value 1 when all three conditions for ignoring a signal that are mentioned in Section 10.1 are satisfied. However, to fulfill a POSIX requirement, the sigchld signal is handled specially. POSIX distinguishes between explicitly setting the "ignore" action for the sigchld signal and leaving the default in place (even if the default is to ignore the signal). To let the kernel clean up a terminated child process and prevent it from becoming a zombie (see Section 3.5.2), the parent must explicitly set the action to "ignore" the signal. So ignored_signal( ) handles this case as follows: if the signal is explicitly ignored, ignored_signal( ) returns 0, but if the default action was "ignore" and the process didn't change that default, ignored_signal( ) returns 1.

If ignored_signal( ) returns 1, the siginfo_t table of the destination process must not be updated, and the send_sig_info( ) function terminates. Since the signal is no longer pending, it has been effectively delivered to the destination process, even if the process never sees it.

If ignored_signal( ) returns 0, the phase of signal delivering has to be deferred, therefore send_sig_info( ) may have to modify the data structures of the destination process to let it know later that a new signal has been sent to it. However, if the signal being handled was already pending, the send_sig_info( ) function can simply terminate. In fact, there can be at most one occurrence of any regular signal type in the pending signal queue of a process because regular signal occurrences are not really queued:

if (sig < 32 && sigismember(&t->pending.signal, sig)) { spin_unlock_irqrestore(&t->sigmask_lock, flags); return 0;

If it proceeds, the send_sig_info( ) function must insert a new item in the pending signal queue of the destination process. This is achieved by invoking the send_signal( ) function:

retval = send_signal(sig, info, &t->pending);

In turn, the send_signal( ) function checks the length of the pending signal queue and appends a new sigqueue data structure:

if (atomic_read(&nr_queued_signals) < max_queued_signals) { q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC); atomic_inc(&nr_queued_signals); q->next = NULL; *(t->pending.tail) = q; t->pending.tail = &q->next;

Then the send_sig_info( ) function fills the siginfo_t table inside the new queue item:

if ((unsigned long)info == 0) { q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_USER;

q->info._sifields._kill._pid = current->pid; q->info._sifields._kill._uid = current->uid; } else if ((unsigned long)info == 1) { q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_KERNEL; q->info._sifields._kill._pid = 0; q->info._sifields._kill._uid = 0; } else copy_siginfo(&q->info, info);

The info argument passed to the send_signal( ) function either points to a previously built siginfo_t table or stores the constants 0 (for a signal sent by a User Mode process) or 1 (for a signal sent by a kernel function).

If it is not possible to add an item to the queue, either because it already includes max_queued_signals elements or because there is no free memory for the sigqueue data structure, the signal occurrence cannot be queued. If the signal is real-time and was sent through a system call that is explicitly required to queue it (like rt_sigqueueinfo( )), the send_signal( ) function returns an error code. Otherwise, it sets the corresponding bit in t->pending.signal:

if (sig >= 32 && info && (unsigned long)info != 1 && info->si_code != SI_USER)

return -EAGAIN; sigaddset(&t->pending.signal, sig); return 0;

It is important to let the destination process receive the signal even if there is no room for the corresponding item in the pending signal queue. Suppose, for instance, that a process is consuming too much memory. The kernel must ensure that the kill( ) system call succeeds even if there is no free memory; otherwise, the system administrator doesn't have any chance to recover the system by terminating the offending process.

If the send_signal( ) function successfully terminated and the signal is not blocked, the destination process has a new pending signal to consider:

if (!retval && !sigismember(&t->blocked, sig)) signal_wake_up(t);

The signal_wake_up( ) function performs three actions:

1. Sets the sigpending flag of the destination process.

2. Checks whether the destination process is already running on another CPU and, in this case, sends an interprocessor interrupt to that CPU to force a reschedule of the current process (see Section 4.6.2). Since each process checks the existence of pending signals when returning from the schedule( ) function, the interprocessor interrupt ensures that the destination process quickly notices the new pending signal if it is already running.

3. Checks whether the destination process is in the task_interruptible state and, in this case, wakes it up by invoking the wake_up_process( ).

Finally, the send_sig_info( ) function re-enables the interrupts, releases the spin lock, and terminates with the error code of send_signal( ) :

spin_unlock_irqrestore(&t->sigmask_lock, flags); return retval;

The send_sig( ) function is similar to send_sig_info( ). However, the info parameter is replaced by a priv flag, which is 1 if the signal is sent by the kernel and 0 if it is sent by a process. The send_sig( ) function is implemented as a special case of send_sig_info( ) :

return send_sig_info(sig, (void*)(long)(priv != 0), t);

10.2.2 The force_sig_info( ) and force_sig( ) Functions

The force_sig_info(sig, info, t) function is used by the kernel to send signals that cannot be explicitly ignored or blocked by the destination processes. The function's parameters are the same as those of send_sig_info( ). The force_sig_info( ) function acts on the signal struct data structure that is referenced by the sig field included in the descriptor t of the destination process:

spin_lock_irqsave(&t->sigmask_lock, flags); if (t->sig->action[sig-1].sa.sa_handler == SIG_IGN)

t->sig->action[sig-1].sa.sa_handler = SIG_DFL; sigdelset(&t->blocked, sig); recalc_sigpending(t);

spin_unlock_irqrestore(&t->sigmask_lock, flags); return send_sig_info(sig, info, t);

force_sig( ) is similar to force_sig_info( ). Its use is limited to signals sent by the kernel; it can be implemented as a special case of the force_sig_info( ) function:

I [email protected] RuBoard

Was this article helpful?

0 0

Post a comment