Ch8PartII

Signals and Nonlocal jump

8.5.4 Blocking and Unblocking Signals

Linux provides implicit and explicit mechanisms for blocking signals:

  • Implicit blocking mechanism
    • By default, the kernel blocks any pending signals of the type currently being processed by a handler.
    • For example, in Figure 8.31, suppose the program has caught signal s and is currently running handler S. If another signals is sent to the process, then s will become pending but will not be received until after handler S returns.
  • Explicit blocking mechanism
    • Applications can explicitly block and unblock selected signals using the sigprocmask function and its helpers.
1
2
3
4
5
6
7
8
9
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
//Returns: 0 if OK, −1 on error
int sigismember(const sigset_t *set, int signum);
//Returns: 1 if member, 0 if not, −1 on error

The sigprocmask function changes the set of currently blocked signals (a bit vector )

The specific behavior depends on the value of how:

  • SIG_BLOCK. Add the signals in set to blocked (blocked = blocked | set).
  • SIG_UNBLOCK. Remove the signals in set from blocked (blocked = blocked & ~set)
  • SIG_SETMASK. blocked = set.

If oldset is non-NULL, the previous value of the blocked bit vector is stored in oldset

  • The sigemptyset initializes set to the empty set.
  • The sigfillset function adds every signal to set.
  • The sigaddset function adds signum to set, sigdelset deletes signum from set, and sigismember returns 1 if signum is a member of set, and 0 if not.
1
2
3
4
5
6
7
8
9
10
11
12
13
sigset_t mask, prev_mask;

Sigemptyset(&mask);
Sigaddset(&mask, SIGINT);

/* Block SIGINT and save previous blocked set */
Sigprocmask(SIG_BLOCK, &mask, &prev_mask);



// Code region that will not be interrupted by SIGINT
/* Restore previous blocked set, unblocking SIGINT */
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);

8.5.5 Writing Signal Handlers

Handlers have several attributes that make them difficult to reason about

  • Handlers run concurrently with the main program and share the same global variables, and thus can interfere with the main program and with other handlers
  • The rules for how and when signals are received is often counterintuitive
  • Different systems can have different signal-handling semantics

In this section, we address these issues and give you some basic guidelines for writing safe, correct, and portable signal handlers.

Safe Signal Handling

  • Keep handlers as simple as possible.

    For example, the handler might simply set a global flag and return immediately, and then all processing associated with the receipt of the signal is performed by the main program, which periodically checks (and resets) the flag.

  • Call only async-signal-safe functions in your handlers.

    • These functions can be safely called from a signal handler, either because it is reentrant (e.g., accesses only local variables), or because it cannot be interrupted by a signal handler.

    • Notice that many popular functions, such as printf, sprintf, malloc,and exit, are not safe

    • The only safe way to generate output from a signal handler is to use the write function

safe functions

  • Save and restore errno

    • save errno to a local variable on entry to the handler and restore it before the handler returns to prevent other handler from interfering it.
    • It is not necessary if the handler terminates the process by calling _exit.
  • Protect accesses to shared global data structures by blocking all signals.

  • Declare global variables with volatile

    • You can tell the compiler neither to cache a variable or put it in register by declaring it with the volatile type qualifier

    • The volatile qualifier forces the compiler to read the value from memory each time the value is referenced in the code

  • Declare flags with sig_atomic_t

    • C provides an integer data type, sig_atomic_t, for which reads and writes are guaranteed to be atomic (uninterruptible) because they can be implemented with a single instruction

Correct Signal Handling

Since pending signals are implemented with a bit vector, it can only record 1 signal each type, so signals cannot be used to count the occurrence of events in other processes

Practice Problem 8.8

What is the output of the following program?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
volatile long counter = 2;
void handler1(int sig){
sigset_t mask, prev_mask;
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &prev_mask); /* Block sigs */
sio_putl(--counter);
sigprocmask(SIG_SETMASK, &prev_mask, NULL); /* Restore sigs */
_exit(0);
}
int main(){
pid_t pid;
sigset_t mask, prev_mask;
printf("%ld", counter);
fflush(stdout);
signal(SIGUSR1, handler1);
if ((pid = Fork()) == 0){
while(1) {};
}
kill(pid, SIGUSR1);
waitpid(-1, NULL, 0);
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &prev_mask); /* Block sigs */
printf("%ld", ++counter);
sigprocmask(SIG_SETMASK, &prev_mask, NULL); /* Restore sigs */
exit(0);
}

My solution: :white_check_mark:

1
213

Portable Signal Handling

Another ugly aspect of Unix signal handling is that different systems have different signal-handling semantics

The Posix standard defines the sigaction function, which allows users to clearly specify the signal-handling semantics they want when they install a handler.

1
2
3
4
#include <signal.h>
int sigaction(int signum, struct sigaction *act,
struct sigaction *oldact);
//Returns: 0 if OK, −1 on error

8.5.6 Synchronizing Flows to Avoid Nasty Concurrency Bugs

8.5.7 Explicitly Waiting for Signals

Sometimes a main program needs to explicitly wait for a certain signal handler to run

1
2
3
#include <signal.h>
int sigsuspend(const sigset_t *mask);
//Returns: −1
  • The sigsuspend function temporarily replaces the current blocked set with mask and then suspends the process until the receipt of a signal whose action is either to run a handler or to terminate the process.

  • If the action is to run a handler, then sigsuspend returns after the handler returns, restoring the blocked set to its state when sigsuspend was called.

  • The sigsuspend function is equivalent to an atomic (uninterruptible) version of the following:

    1
    2
    3
    sigprocmask(SIG_BLOCK, &mask, &prev);
    pause();
    sigprocmask(SIG_SETMASK, &prev, NULL);

    8.6 Nonlocal Jumps

C provides a form of user-level exceptional control flow, called a nonlocal jump, that transfers control directly from one function to another

1
2
3
4
#include <setjmp.h>
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env, int savesigs);
//Returns: 0 from setjmp, nonzero from longjmps
  • The setjmp function saves the current calling environment in the env buffer, for later use by longjmp, and returns 0
  • The calling environment includes the program counter, stack pointer, and general-purpose registers.
  • For subtle reasons return value should not be assigned to a variable
1
2
3
4
#include <setjmp.h>
void longjmp(jmp_buf env, int retval);
void siglongjmp(sigjmp_buf env, int retval);
//Never returns
  • The longjmp function restores the calling environment from the env buffer and then triggers a return from the most recent setjmp call that initialized env.
  • The setjmp function is called once but returns multiple times

An important application of nonlocal jumps is to permit an immediate return from a deeply nested function call, usually as a result of detecting some error condition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <setjmp.h>
#include <sys/types.h>
#include <stdlib.h>

jmp_buf buf;

int error1 = 0;
int error2 = 1;

void foo(void), bar(void);

int main()
{
switch(setjmp(buf)) {
case 0:
foo();
break;
case 1:
printf("Detected an error1 condition in foo\n");
break;
case 2:
printf("Detected an error2 condition in foo\n");
break;
default:
printf("Unknown error condition in foo\n");
}
exit(0);
}

/* Deeply nested function foo */
void foo(void)
{
if (error1)
longjmp(buf, 1);
bar();
}

void bar(void)
{
if (error2)
longjmp(buf, 2);
}

Another important application of nonlocal jumps is to branch out of a signal handler to a specific code location, rather than returning to the instruction that was interrupted by the arrival of the signal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>

sigjmp_buf buf;

void handler(int sig)
{
siglongjmp(buf, 1);
}

int main()
{
if (!sigsetjmp(buf, 1)) {
Signal(SIGINT, handler);
Sio_puts("starting\n");
}
else
Sio_puts("restarting\n");

while(1) {
Sleep(1);
Sio_puts("processing...\n");
}
exit(0); /* Control never reaches here */
}
  • The sigsetjmp and siglongjmp functions are versions of setjmp and longjmp that can be used by signal handlers.
  • To avoid a race, we must install the handler after we call sigsetjmp.

The exception mechanisms provided by C++ and Java are higher-level, more structured versions of the C setjmp and longjmp functions. You can think of a catch clause inside a try statement as being akin to a setjmp function. Similarly, a throw statement is similar to a longjmp function.

8.7 Tools for Manipulating Processes

  • strace
    • Prints a trace of each system call invoked by a running program and its children.
    • Compile your program with -static to get a cleaner trace without a lot of output related to shared libraries.
  • ps: Lists processes (including zombies) currently in the system.
  • top Prints information about the resource usage of current processes.
  • pmap Displays the memory map of a process.
  • /proc. A virtual filesystem that exports the contents of numerous kernel data structures in an ASCII text form that can be read by user programs. For example, type cat /proc/loadavg to see the current load average on your Linux system.

8.8 Summary

Linux processes hierarchy

Signals

  • A signal only change some states in the context of the destination process.
  • Signals are always sent by kernal, user program can only ask the kernal to send signals for them.

Why printf is not safe for signal?

Potential dead-lock

1
2
3
4
5
6
7
8
9
10
11
12
void handler(int signum){
printf("handling\n");
}
int main(){
//install handler
signal(SIGINT , &handler);

//main routine
while(1){
printf("running\n");
}
}
  • When printf do the printing, stdout will be locked until printf is finished.
  • However, it is possible that a signal recevied when executing the printf
  • Then the printf in the handler want to use stdout, it wait for stdout to be unlocked
  • The waiting will never end since the interrupted printf in main has no way to finish.

Tips

when using wait(&childExitStat) to get the exit value of child, it will be multiplication of 256.

1
2
3
4
5
6
7
8
9
if (pid == 0){
//in child
exit(2);
}

//in parent
wait(&exitstat);
printf("%d\n" , exitstat);
//exitstat == 2 * 256