Case 2 : SA_NODEFER bit set

Now let's reconsider the very same scenario, only this time we use the SA_NODEFER signal flag. So, when the first instance of signal n arrives, our process jumps into the signal-handling code (which will take 55 ms to complete). As before, the second signal will arrive just 10 ms into the signal-handling code, but hang on, this time it is not masked; it is not deferred. Thus, we will reenter the signal handler function immediately. Then, 20 ms later (after the signal handler was first entered by signal n instance #1), the third signal instance arrives. Again, we will reenter the signal handler function. Yes, this will happen five times.

Figure 4 shows us this scenario:

Figure 4: SA_NODEFER bit set: no queue; all signal instances processed upon delivery, stack intensive

This looks good, but please realize the following: 

  • The signal handler code itself must be written to be reentrant-safe (no global or static variable usage; only call async-signal safe functions within it), as it is being continually reentered in this scenario.
  • Stack usage: every time the signal handler is reentered, do realize that an additional call frame has been allocated (pushed) on to the process stack.

The second point bears thinking about: what if so many signals arrive (while handling previous invocations) that we overload and, indeed, overflow the stack? Well, disaster. Stack overflow is a bad bug; no exception handling is practically possible (we cannot, with any degree of confidence, catch  or trap into a stack overflow issue). 

A interesting code example ch11/defer_or_not.c follows to demonstrate both of these cases:

For readability, only key parts of the code are displayed; to view the complete source code, build and run it; the entire tree is available for cloning from the book's GitHub repo here: https://github.com/PacktPublishing/Hands-on-System-Programming-with-Linux
static volatile sig_atomic_t s=0, t=0;
[...]
int main(int argc, char **argv)
{
int flags=0;
struct sigaction act;
[...]
flags = SA_RESTART;
if (atoi(argv[1]) == 2) {
flags |= SA_NODEFER;
printf("Running with the SA_NODEFER signal flag Set ");
} else {
printf("Running with the SA_NODEFER signal flag Cleared [default] ");
}

memset(&act, 0, sizeof(act));
act.sa_handler = sighdlr;
act.sa_flags = flags;
if (sigaction(SIGUSR1, &act, 0) == -1)
FATAL("sigaction failed ");
fprintf(stderr, " Process awaiting signals ... ");

while (1)
(void)pause();
exit(EXIT_SUCCESS);
}

Here is the signal handler function:

/* 
* Strictly speaking, should not use fprintf here as it's not
* async-signal safe; indeed, it sometimes does not work well!
*/
static void sighdlr(int signum)
{
int saved;
fprintf(stderr, " sighdlr: signal %d,", signum);
switch (signum) {
case SIGUSR1:
s ++; t ++;
if (s >= MAX)
s = 1;
saved = s;
fprintf(stderr, " s=%d ; total=%d; stack %p :", s, t, stack());
DELAY_LOOP(saved+48, 5); /* +48 to get the equivalent ASCII value */
fprintf(stderr, "*");
break;
default:;
}
}

We deliberately let the signal-handling code take a fairly long time (via our use of the DELAY_LOOP macro) so that we can simulate the case in which the same signal is delivered multiple times while it is being handled. In a real-world application, always strive to keep your signal handling as brief as is possible.

The inline-assembly stack() function is an interesting way to get a register's value. Read the following comment to see how it works:

/* 
* stack(): return the current value of the stack pointer register.
* The trick/hack: on x86 CPU's, the ABI tells us that the return
* value is always in the accumulator (EAX/RAX); so we just initialize
* it to the stack pointer (using inline assembly)!
*/
void *stack(void)
{
if (__WORDSIZE == 32) {
__asm__("movl %esp, %eax");
} else if (__WORDSIZE == 64) {
__asm__("movq %rsp, %rax");
}
/* Accumulator holds the return value */
}
The processor ABI - Application Binary Interface—documentation is an important area for the serious systems developer to be conversant with; check out more on this in the Further reading section on the GitHub repository.

To properly test this application, we write a small shell script bombard_sig.sh, which literally bombards the given process with the (same) signal (we use SIGUSR1 here). The user is expected to pass the process PID and the number of signal instances to send as parameters; if the second parameter is given as -1, the script continually bombards the process. Here is the key code of the script:

SIG=SIGUSR1
[...]
NUMSIGS=$2
n=1
if [ ${NUMSIGS} -eq -1 ] ; then
echo "Sending signal ${SIG} continually to process ${1} ..."
while [ true ] ; do
kill -${SIG} $1
sleep 10e-03 # 10 ms
done
else
echo "Sending ${NUMSIGS} instances of signal ${SIG} to process ${1} ..."
while [ ${n} -le ${NUMSIGS} ] ; do
kill -${SIG} $1
sleep 10e-03 # 10 ms
let n=n+1
done
fi
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset