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.
- Applications can explicitly block and unblock selected signals using the
1 |
|
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, andsigismember
returns 1 if signum is a member of set, and 0 if not.
1 | sigset_t mask, prev_mask; |
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,
andexit
, are not safeThe only safe way to generate output from a signal handler is to use the
write
function
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
.
- save
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 qualifierThe
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
- C provides an integer data type,
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 |
|
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 |
|
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
3sigprocmask(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 |
|
- The
setjmp
function saves the current calling environment in theenv
buffer, for later use bylongjmp
, 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 |
|
- 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 |
|
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 |
|
- The
sigsetjmp
andsiglongjmp
functions are versions ofsetjmp
andlongjmp
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
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 | void handler(int signum){ |
- When
printf
do the printing,stdout
will be locked untilprintf
is finished. - However, it is possible that a signal recevied when executing the
printf
- Then the
printf
in thehandler
want to usestdout
, it wait forstdout
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 | if (pid == 0){ |