Signal Delivery

   

Signal delivery became much more complicated with the advent of multithreaded processes. The kernel has to support the old paradigm of delivering a signal to a process as well as to deliver the signal to a particular thread within the process. Signal delivery is further complicated because the interface to the kill() system call is complicated a pretty good feat for a system call with only two arguments.

The kill() system call is the way a process sends a signal. The first argument is the PID of the process to which the signal should be sent, and the second argument is the number of the signal to send. The complication comes from the PID argument, which can take on values other than an actual PID. If the specified PID is zero, the signal is sent to all processes in the same process group as the process sending the signal. If PID is 1 and the user is not root, the signal is sent to all processes belonging to the user. If the PID is 1 and the user is root, the signal is sent to all processes other than system processes. If the PID is the constant KILL_ALL_OTHERS (defined in /usr/include/sys/signal.h), the behavior is similar to the case where PID is 1 except that the signal is not sent to the sending process. And finally, if PID is negative but not 1 or KILL_ALL_OTHERS, the signal is sent to all processes whose process group is the absolute value of PID. Table 14-1 summarizes the various actions of kill().

Table 14-1. Actions of the kill() System Call

PID

Effective UID

Signal sent to

>0

Any

Specific process

0

Any

All processes in current process group

1

Root

All processes

1

Nonroot

All processes with same UID

KILL_ALL_OTHERS

Root

All processes except sender

KILL_ALL_OTHERS

Nonroot

All processes with same UID except for the sending process

<0

Any

Process group |PID|


The first thing the kill() kernel function has to do is validate the arguments and sort out where the signal should be delivered. kill() then calls kill1() and passes it the signal number, the process group or process to signal, a flag indicating whether the signal is going to a group or a single process, and a flag indicating whether or not this is a "kill all" signal.

The job of kill1() is to interpret the arguments from kill() and then call psignalx() for each process that should be sent the signal. Depending on the arguments, this could mean going through the process group hash to find all processes in a group, or it could mean looping through all processes looking for the correct ones.

There is also a second system call, which provides a means of initiating a signal: sigqueue(). sigqueue() does not have the broadcasting functionality that kill() has the PID passed to sigqueue() must be a valid PID, and only that process receives the signal. However, sigqueue() does have the ability to send a value along with the signal. This value is of type union sigval. After validating arguments, sigqueue() creates a sigcount structure for the process if one doesn't already exist. It then goes to the list of free ksi structures to find an available one and copies the sigval into it. Finally, it calls psignalx() to deliver the signal. The psignalx() routine is where the logic for kill() and sigqueue() converge, as show in Figure 14-2.

Figure 14-2. The kill() and sigqueue() System Calls

graphics/14fig02.gif


psignalx() is a relatively simple front end to pm_psignalx(). The first thing psignalx() does is check to see if the pointer to the ksi_t that was passed to it is on the stack or in malloced memory. Recall that we maintain for each process a list of free ksi_t structures that we use to hold the siginfo data. It is also possible for a function to just declare a type ksi_t as a local variable and use that. If psignalx() finds that the ksi_t pointer it has received points to the stack, it copies that data into a ksi_t from the free list. psignalx() then grabs the sched_lock and calls pm_psignalx(), passing a pointer to the proc structure, the signal number, and the (possibly new) pointer to a ksi_t.

pm_psignalx() is still not the one that does the real work it's another front end. It locks the process lock for the process, checks to make sure the process isn't a zombie, then calls pm_signalx().

At last, in pm_signalx() we're ready to actually post the signal. Signals can be posted either to a process or to a particular thread within the process. Recall that the proc structure has p_sig for pending signals, and each thread structure has kt_sig for pending signals. One of the arguments to pm_signalx() is a pointer to a kthread structure. If this pointer is NULL, then the signal gets posted to the process…sort of. We'll see some exceptions to that rule soon.

The first thing pm_signalx() does is look at the process to see if there are any threads that have called sigwait() to wait for the signal. If it finds one, then it just does a wakeup for that thread and it is done. For a thread in sigwait(), there is no other action taken on the signal other than waking up the sleeping thread.

If no thread is in sigwait(), then we look for threads in sigpause(), pause(), or sigsuspend() that are waiting for this signal. If a thread is found here, then that thread becomes the target of the signal. In other words, even if a signal is sent to a process, if there is a thread waiting for it, that thread is the one that gets it.

Next, we check to see if the process is multithreaded. If there is only a single thread in the process, that thread becomes the target of the signal. If there are multiple threads, we check each to see if it has registered a handler for the signal. If so, that thread gets the signal. If we have multiple threads, no thread is waiting for the signal, and no thread has a handler for the signal, then we deliver the signal to the process itself. Finally, we're ready to actually raise the signal by setting the appropriate bit either in p_sig or kt_sig.

The next thing we have to do is decide whether or not to wake up the thread we just set the bit for. This depends on a combination of what state the thread is in and what signal we're sending it. It's just a straightforward case statement to get it to behave as described in the man pages.

Finally, there's just a little cleanup to do. If the thread was a sigwaiter and we woke it up, then we remove it from the sigwaiters list. If we had siginfo along with the signal, here is where it gets enqueued onto the receiving thread's list. And if the signal we're delivering is SIGKILL, we mark the process as terminating so it won't get back into user space to run anymore.

So now the signal has been delivered. The receiving thread or process has its bit set to let it know the signal has arrived, and it has been woken up if appropriate. Let's now have a look at what the receiving process does with the signal.



HP-UX 11i Internals
HP-UX 11i Internals
ISBN: 0130328618
EAN: 2147483647
Year: 2006
Pages: 167

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net