Please indicate the source: http://blog.csdn.net/gaoxiangnumber1
Welcome to my github: https://github.com/gaoxiangnumber1
10.1 Introduction
- Signals are software interrupts. Signals provide a way of handling asynchronous events.
10.2 Signal Concepts
- Signal names all begin with “SIG” and they are all defined by positive integer constants(the signal number) in
10.3 signal Function
#include <signal.h>
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
Returns: previous disposition of signal if OK, SIG_ERR on error
- Because the semantics of signal differ among implementations, we must use sigaction function instead. We provide an implementation of signal that uses sigaction in Section 10.14.
- signo is the name of the signal from Figure 10.1.
- The value of func is
(a) SIG_IGN: tell the system to ignore the signal.(SIGKILL and SIGSTOP can’t be ignored.)
(b) SIG_DFL: set the action associated with the signal to its default value.(final column in Figure 10.1)
(c) The address of a function to be called when the signal occurs: we call the function either the signal handler or the signal-catching function.
- The return value from signal is the pointer to the previous signal handler. Many systems call the signal handler with additional arguments.(Section 10.14.)
- In
#define SIG_ERR (void(*)())-1
#define SIG_DFL (void(*)())0
#define SIG_IGN (void(*)())1
- These constants can be used in place of the “pointer to a function that takes an integer argument and returns nothing”, the second argument to signal, and the return value from signal.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
static void sig_usr(int signo)
{
if(signo == SIGUSR1)
{
printf("received SIGUSR1\n");
}
else if(signo == SIGUSR2)
{
printf("received SIGUSR2\n");
}
else
{
printf("received signal %d\n", signo);
exit(1);
}
}
int main()
{
if(signal(SIGUSR1, sig_usr) == SIG_ERR)
{
Exit("can't catch SIGUSR1");
}
if(signal(SIGUSR2, sig_usr) == SIG_ERR)
{
Exit("can't catch SIGUSR2");
}
for(;;)
{
pause();
}
exit(0);
}
- The signal handler catches either of the two user-defined signals and prints the signal number. pause(Section 10.10) suspends the calling process until a signal is received.
- We invoke the program in the background and use the kill(1) command to send it signals.
$ ./a.out & #start process in background
[1] 7216 #job-control shell prints job number and process ID
$ kill -USR1 7216 #send SIGUSR1
received SIGUSR1
$ kill -USR2 7216 #send SIGUSR2
received SIGUSR2
$ kill 7216 #now send SIGTERM
[1]+ Terminated ./a.out
- When we send the SIGTERM signal, the process is terminated, since it doesn’t catch the signal, and the default action for the signal is termination.
Program Start-Up
- When a program is executed, the status of all signals is either default or ignore. Normally, all signals are set to their default action, unless the process that calls exec is ignoring the signal. exec change the disposition of any signals being caught to their default action and leave the status of all other signals alone.
- A signal that is being caught by a process that calls exec cannot be caught by the same function in the new program, since the address of the signal-catching function in the caller probably has no meaning in the new program file that is executed.
- Example of this signal status behavior:
With a shell that doesn’t support job control, when we execute a process in the background, the shell automatically sets the disposition of the interrupt and quit signals in the background process to be ignored. So, if we type the interrupt character, it doesn’t affect the background process. If this weren’t done and we typed the interrupt character, it would terminate the foreground process and all the background processes.
- Many interactive programs that catch these two signals have following code. The process catches the signal only if the signal is not currently being ignored.
void sig_int(int), sig_quit(int);
if(signal(SIGINT, SIG_IGN) != SIG_IGN)
{
signal(SIGINT, sig_int);
}
if(signal(SIGQUIT, SIG_IGN) != SIG_IGN)
{
signal(SIGQUIT, sig_quit);
}
- These two calls show a limitation of signal function: we are not able to determine the current disposition of a signal without changing the disposition.
Process Creation
- When a process calls fork, the child inherits the parent’s signal dispositions. Since the child starts with a copy of the parent’s memory image, the address of a signal catching function has meaning in the child.
10.4 Unreliable Signals
- In earlier versions of UNIX, signals were unreliable: a signal could occur and the process would never know about it. A process could catch the signal or ignore it. Sometimes, we would like to tell the kernel to block a signal: don’t ignore it, remember if it occurs, and tell us later when we’re ready.
- One problem with early versions was that the action for a signal was reset to its default each time the signal occurred. Following code describes these earlier systems concerns how to handle the interrupt signal.
int sig_int();
...
signal(SIGINT, sig_int);
...
sig_int()
{
signal(SIGINT, sig_int);
...
}
- The problem is that there is a window of time(after the signal has occurred, but before the call to signal in the signal handler) when the interrupt signal could occur another time. This second signal would cause the default action to occur, which for this signal terminates the process.
- Another problem with earlier systems was that the process was unable to turn a signal off when it didn’t want the signal to occur. All the process could do was ignore the signal. There are times when we would like to tell the system “prevent the following signals from interrupting me, but remember if they do occur.” Example is shown by a piece of code that catches a signal and sets a flag for the process that indicates that the signal occurred:
int sig_int();
int sig_int_flag;
main()
{
signal(SIGINT, sig_int);
...
while(sig_int_flag == 0)
pause();
...
}
sig_int()
{
signal(SIGINT, sig_int);
sig_int_flag = 1;
}
- The process is calling the pause function to put it to sleep until a signal is caught. When the signal is caught, the signal handler sets the flag sig_int_flag to a nonzero value. The process is awakened by the kernel after the signal handler returns, the flag is nonzero, and does whatever it needs to do.
- But there is a window of time when things can go wrong: If the signal occurs after the test of sig_int_flag but before the call to pause, the process could go to sleep forever(assuming that the signal is never generated again). This occurrence of the signal is lost.
10.5 Interrupted System Calls
- If a process caught a signal while the process was blocked in a slow system call, the system call was interrupted. The system call return an error and errno was set to EINTR. This was done under the assumption that since a signal occurred and the process caught it, there is chance that something has happened that should wake up the blocked system call. Note that it is a system call within the kernel that is interrupted when a signal is caught, not a function.
- The system calls are divided into two categories: slow system calls and all others. The slow system calls are those that can block forever:
- Reads can block the caller forever if data isn’t present with certain file types(pipes, terminal devices, and network devices).
- Writes can block the caller forever if the data can’t be accepted immediately by these same file types.
- Opens on certain file types that block the caller until some condition occurs(such as a terminal device open waiting until an attached modem answers the phone).
- pause function(which by definition puts the calling process to sleep until a signal is caught) and wait function.
- Certain ioctl operations
- Some of the interprocess communication functions(Chapter 15).
- Exception to slow system calls is anything related to disk I/O. Although a read or a write of a disk file can block the caller temporarily while the disk driver queues the request and then the request is executed, unless a hardware error occurs, the I/O operation always returns and unblocks the caller quickly.
- Earlier POSIX gave implementations a choice of how to deal with reads and writes that have processed partial amounts of data.
- If read has received and transferred data to an application’s buffer, but has not yet received all that the application requested and is then interrupted, operating system could either fail the system call, with errno set to EINTR, or allow the system call to succeed, returning the partial amount of data received.
- If write is interrupted after transferring some of the data in an application’s buffer, operation system could either fail the system call, with errno set to EINTR, or allow the system call to succeed, returning the partial amount of data written.
- POSIX.1 2001 version of the standard: return partial success.
- Problem with interrupted system calls is that we must handle the error return. The typical code sequence(assuming a read operation and assuming that we want to restart the read even if it’s interrupted) would be
again:
if((n = read(fd, buf, BUFFSIZE)) < 0)
{
if(errno == EINTR)
goto again;
}
- To prevent applications from having to handle interrupted system calls, 4.2BSD introduced the automatic restarting of certain interrupted system calls. The system calls that were automatically restarted are ioctl, read, readv, write, writev, wait, and waitpid. The first five functions are interrupted by a signal only if they are operating on a slow device; wait and waitpid are always interrupted when a signal is caught. Since this caused a problem for some applications that didn’t want the operation restarted if it was interrupted, 4.3BSD allowed the process to disable this feature on a per-signal basis.
- POSIX.1 requires an implementation to restart system calls only when the SA_RESTART flag is in effect for the interrupting signal. Section 10.14: this flag is used with the sigaction function to allow applications to request that interrupted system calls be restarted.
- Linux: When signal handlers are installed with the signal function, interrupted system calls will be restarted.
- Figure 10.3 summarizes the signal functions and their semantics provided by the various implementations.
- Figure 10.18 provide our own version of the signal function that automatically tries to restart interrupted system calls(other than for the SIGALRM signal). Figure 10.19 provide function signal_intr that tries to never do the restart.
10.6 Reentrant Functions
- When a signal that is being caught is handled by a process, the normal sequence of instructions being executed by the process is temporarily interrupted by the signal handler. The process then execute the instructions in the signal handler. If the signal handler returns(instead of calling exit or longjmp), then the normal sequence of instructions that the process was executing when the signal was caught continues executing.(This is similar to what happens when a hardware interrupt occurs.)
- In signal handler, we can’t tell where the process was executing when the signal was caught. What if the process was in the middle of allocating memory on its heap using malloc, and we call malloc from the signal handler? What if the process was in the middle of a call to getpwnam(Section 6.2), that stores its result in a static location, and we call the same function from the signal handler?
- malloc: havoc can result for the process, since malloc usually maintains a linked list of all its allocated areas, and it may have been in the middle of changing this list.
getpwnam: the information returned to the normal caller can get overwritten with the information returned to the signal handler.
- The functions that are guaranteed to be safe to call from within a signal handler are reentrant and are called async-signal safe. Besides being reentrant, they block any signals during operation if delivery of a signal might cause inconsistencies.
- Figure 10.4 lists async-signal safe functions. Most functions that are not async-signal safe because
(a) they are known to use static data structures.
(b) they call malloc or free.
(c) they are part of the standard I/O library.
- Most standard I/O library use global data structures in a non-reentrant way. When we call printf from signal handlers, it is not guaranteed to produce the expected results, since the signal handler can interrupt a call to printf from our main program.
- Even if we call a function listed in Figure 10.4 from a signal handler, there is only one errno variable per thread(Section 1.7), and we might modify its value. Consider a signal handler that is invoked right after main has set errno. If the signal handler calls read, this call can change the value of errno, wiping out the value that was just stored in main.
So we should save and restore errno when calling async-signal safe functions from a signal handler.(A commonly caught signal is SIGCHLD, and its signal handler usually calls one of the wait functions. All the wait functions can change errno.)
- longjmp(Section 7.10) and siglongjmp(Section 10.15) are not in Figure 10.4 because the signal may have occurred while the main routine was updating a data structure in a non-reentrant way. This data structure could be left half updated if we call siglongjmp instead of returning from the signal handler. If it is going to do such things as update global data structures, while catching signals that cause sigsetjmp to be executed, an application needs to block the signals while updating the data structures.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
static void my_alarm(int signo)
{
printf("Enter signal handler:\n");
struct passwd *rootptr;
printf("AAAAA\n");
if((rootptr = getpwnam("root")) == NULL)
{
Exit("getpwnam(root) error");
}
printf("BBBBB\n");
alarm(1);
printf("Leave signal handler:\n");
}
int main()
{
struct passwd *ptr;
signal(SIGALRM, my_alarm);
printf("signal success\n");
alarm(1);
for(int cnt = 0;;++cnt)
{
if(cnt % 10000 == 0)
{
printf("for(;;) %d\n", cnt);
}
if((ptr = getpwnam("xiang")) == NULL)
{
Exit("getpwnam error");
}
if(strcmp(ptr->pw_name, "xiang") != 0)
{
printf("return value corrupted! pw_name = %s\n", ptr->pw_name);
}
}
exit(0);
}
Output:
signal success
for(;;) 0
for(;;) 10000
for(;;) 20000
for(;;) 30000
for(;;) 40000
for(;;) 50000
for(;;) 60000
for(;;) 70000
for(;;) 80000
for(;;) 90000
for(;;) 100000
for(;;) 110000
for(;;) 120000
for(;;) 130000
for(;;) 140000
for(;;) 150000
for(;;) 160000
Enter signal handler:
AAAAA
^C
*/
- Figure 10.5: A program that calls the non-reentrant function getpwnam from a signal handler that is called every second(use alarm(Section 10.10) to generate SIGALRM signal every second).
- When this program was run, the results were random. Usually, the program would be terminated by a SIGSEGV signal when the signal handler returned after several iterations. An examination of the core file showed that the main function had called getpwnam, but that when getpwnam called free, the signal handler interrupted it and called getpwnam, which in turn called free. The data structures maintained by malloc and free had been corrupted when the signal handler(indirectly) called free while the main function was also calling free. Occasionally, the program would run for several seconds before crashing with a SIGSEGV error. When the main function did run correctly after the signal had been caught, the return value was sometimes corrupted and sometimes fine.
10.7 SIGCLD Semantics
- Whenever a process terminates or stops, SIGCHLD is sent to the parent. This signal is ignored by default, so the parent must catch this signal if it wants to be notified whenever a child’s status changes. The normal action in the signal-catching function is to call one of the wait functions to fetch the child’s process ID and termination status.
- System V has a signal named SIGCLD. The semantics of this signal were different from other signals. Applications should use SIGCHLD signal, but many systems define SIGCLD to be the same as SIGCHLD for backward compatibility. If you maintain software that uses SIGCLD, you need to check your system’s manual page to see which semantics it follows.
- The semantics of the BSD SIGCHLD signal are similar to those of all other signals. When the signal occurs, the status of a child has changed, and we need to call one of the wait functions to determine what has happened.
- System V handled SIGCLD differently from other signals. SVR4-based systems continue this tradition if we set its disposition using either signal or sigset. This older handling of SIGCLD consists of the following behavior:
- If the process sets its disposition to SIG_IGN, children of the calling process will not generate zombie processes. On termination, the status of these child processes is discarded. If it subsequently calls one of the wait functions, the calling process will block until all its children have terminated, and then wait returns -1 with errno set to ECHILD.
—The default disposition of this signal is to be ignored, but this default will not cause the preceding semantics to occur. Instead, we specifically have to set its disposition to SIG_IGN.
—Linux: If either signal or sigset is called to set the disposition of SIGCHLD to be ignored, zombies are never generated. We can also set the SA_NOCLDWAIT flag(Figure 10.16) with sigaction to avoid zombies.
- If we set the disposition of SIGCLD to be caught, the kernel immediately checks whether any child processes are ready to be waited for and, if so, calls the SIGCLD handler.
- Section 10.4: The first thing to do on entry to a signal handler is to call signal again, to reestablish the handler. This action is intended to minimize the window of time when the signal is reset back to its default and could get lost.
- Figure 10.6: This program doesn’t work on System V platforms. The output is a continual string of SIGCLD received lines. Eventually, the process runs out of stack space and terminates abnormally.
Linux doesn’t have this problem because it doesn’t call the SIGCHLD signal handler when a process arranges to catch SIGCHLD and child processes are ready to be waited for, even though SIGCLD and SIGCHLD are defined to be the same value.
- Problem with this program is that the call to signal at the beginning of the signal handler invokes item 2: the kernel checks whether a child needs to be waited for(which is the case, since we’re processing a SIGCLD signal), so it generates another call to the signal handler. The signal handler calls signal, and the whole process starts over again.
- Solution: move the call to signal after the call to wait. We call signal after fetching the child’s termination status; the signal is generated again by the kernel only if some other child has since terminated.
- Since POSIX.1 doesn’t reset a signal’s disposition to its default when the signal occurs(assuming we’re using the POSIX.1 sigaction function to set its disposition), there is no need for us to establish a signal handler for SIGCHLD within that handler.
- Linux:
#define SIGCLD SIGCHLD
#define SIGCHLD 17
10.8 Reliable-Signal Terminology and Semantics
- A signal is generated for a process(or sent to a process) when the event that causes the signal occurs. The event could be a hardware exception, a software condition, a terminal-generated signal, or a call to the kill function. When the signal is generated, the kernel sets a flag in the process table.
- A signal is delivered to a process when the action for a signal is taken. During the time between the generation of a signal and its delivery, the signal is said to be pending.
- A process has the option of blocking the delivery of a signal. If a signal that is blocked is generated for a process, and if the action for that signal is default action or to catch the signal, then the signal remains pending for the process until the process
(a) unblocks the signal or
(b) changes the action to ignore the signal.
- The system determines what to do with a blocked signal when the signal is delivered, not when it’s generated. This allows the process to change the action for the signal before it’s delivered. sigpending function(Section 10.13) can be called by a process to determine which signals are blocked and pending.
- What happens if a blocked signal is generated more than once before the process unblocks the signal?
POSIX.1 allows the system to deliver the signal either once or more than once. If the system delivers the signal more than once, we say that the signals are queued. Most UNIX systems do not queue signals unless they support the real-time extensions to POSIX.1.
- What happens if more than one signal is ready to be delivered to a process?
POSIX.1 does not specify the order in which the signals are delivered to the process. The Rationale for POSIX.1 suggests that signals related to the current state of the process be delivered before other signals.
- Each process has a signal mask that defines the set of signals currently blocked from delivery to that process. We can think of this mask as having one bit for each possible signal. If the bit is on for a signal, that signal is currently blocked. A process can examine and change its current signal mask by calling sigprocmask(Section 10.12).
- POSIX.1 defines sigset_t data type to hold a signal set. The signal mask is stored in one of these signal sets(Section 10.11).
10.9 kill and raise Functions
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo);
#include <signal.h>
int raise(int signo);
Both return: 0 if OK, -1 on error
- kill sends a signal to a process or a group of processes.
- raise allows a process to send a signal to itself.
raise(signo);
is equivalent to kill(getpid(), signo);
- 4 different conditions for pid to kill. pid
- > 0:
The signal is sent to the process whose process ID is pid.
- = 0:
The signal is sent to all processes whose process group ID equals the process group ID of the sender and for which the sender has permission to send the signal. All processes excludes an implementation-defined set of system processes. For most UNIX systems, this set of system processes includes the kernel processes and init(pid 1).
- < 0:
The signal is sent to all processes whose process group ID equals the absolute value of pid and for which the sender has permission to send the signal. The set of all processes excludes certain system processes as before.
- == -1:
The signal is sent to all processes on the system for which the sender has permission to send the signal. The set of processes excludes certain system processes.
- A process needs permission to send a signal to another process. The superuser can send a signal to any process. For other users: real/effective user ID of sender has to equal real/effective user ID of receiver. If the implementation supports _POSIX_SAVED_IDS(as POSIX.1 now requires), saved set-user-ID of receiver is checked instead of its effective user ID.
- One special case for the permission testing: if the signal being sent is SIGCONT, a process can send it to any other process in the same session.
- POSIX.1 defines signal number 0 as the null signal. signo = 0: the normal error checking is performed by kill, but no signal is sent. This technique is used to determine if a process still exists. If we send the process the null signal and it doesn’t exist, kill returns -1 and errno is set to ESRCH.
- Since UNIX recycle process IDs after some time, the existence of a process with a given process ID doesn’t necessarily mean that it’s the process that you think it is. Note that the test for process existence is not atomic: by the time that kill returns the answer to the caller, the process in question might have exited, so the answer is of limited value.
- If the call to kill causes the signal to be generated for the calling process and if the signal is not blocked, either signo or some other pending, unblocked signal is delivered to the process before kill returns. Additional conditions occur with threads(Section 12.8).
10.10 alarm and pause Functions
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
Returns: 0 or number of seconds until previously set alarm
- alarm allows us to set a timer that will expire at a specified time in the future. When the timer expires, SIGALRM signal is generated. If we ignore or don’t catch this signal, its default action is to terminate the process.
- seconds is the number of clock seconds in the future when the signal should be generated. When that time occurs, the signal is generated by the kernel, although additional time could elapse before the process gets control to handle the signal, because of processor scheduling delays.
- There is only one alarm clock per process. When we call alarm, if a previously registered alarm clock has not expired, the number of seconds left for that alarm clock is the return value. That previously registered alarm clock is replaced by the new value. If new value is 0, the previous alarm clock is canceled.
- If we want to catch SIGALRM, we must install its signal handler before calling alarm. If we call alarm first and sent SIGALRM before installing the signal handler, our process will terminate.
#include <unistd.h>
int pause(void);
Returns: -1 with errno set to EINTR
- pause suspends the calling process until a signal is caught. The only time pause returns is if a signal handler is executed and that handler returns. In this case, pause returns -1 with errno set to EINTR.
- Using alarm and pause, we can put a process to sleep for a specified amount of time.
- This implementation has three problems.
- If the caller already has an alarm set, that alarm is erased by the first call to alarm. Solution: We can correct this by looking at alarm’s return value. If the number of seconds until previously set alarm is:
-1 < the argument. We should wait only until the existing alarm expires.
-2 > the argument. Before returning we should reset this alarm to occur at its designated time in the future.
- We have modified the disposition for SIGALRM. If we’re writing a function for others to call, we should save the disposition when our function is called and restore it when we’re done.
Solution: We can correct this by saving the return value from signal and resetting the disposition before our function returns.
- There is a race condition between the first call to alarm and the call to pause. On a busy system, it’s possible for the alarm to go off and the signal handler to be called before we call pause. If that happens, the caller is suspended forever in the call to pause(assuming other signal isn’t caught).
Solution: Two ways to correct problem 3. One use setjmp and longjmp(Section 7.10), shown in Figure 10.8; the other use sigprocmask and sigsuspend(Section 10.19).
- sleep2 avoids the race condition from Figure 10.7. Even if the pause is never executed, sleep2 returns when the SIGALRM occurs.
- Another problem with sleep2 that involves its interaction with other signals. If the SIGALRM interrupts some other signal handler, then when we call longjmp, we abort the other signal handler. Figure 10.9 shows this scenario.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>
static jmp_buf env_alrm;
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
static void sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
unsigned int sleep2(unsigned int seconds)
{
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
{
return seconds;
}
if(setjmp(env_alrm) == 0)
{
if(alarm(seconds) != 0)
{
Exit("alarm() error");
}
pause();
}
return alarm(0);
}
static void sig_int(int signo)
{
printf("\nsig_int starting\n");
volatile int sum;
for(int num1 = 0; num1 < 30000000; ++num1)
{
for(int num2 = 0; num2 < 40000000; ++num2)
{
sum += num1 * num2;
}
}
printf("sig_int finished\n");
}
int main()
{
unsigned int unslept;
if(signal(SIGINT, sig_int) == SIG_ERR)
{
Exit("signal(SIGINT) error");
}
unslept = sleep2(5);
printf("sleep2 returned: %u\n", unslept);
exit(0);
}
- The loop in the SIGINT handler was written so that it executes for longer than 5 seconds(longer than the argument to sleep2). The integer k is declared as volatile to prevent an optimizing compiler from discarding the loop.
$ ./a.out
^C #we type the interrupt character
sig_int starting
sleep2 returned: 0
- The longjmp from sleep2 aborted the other signal handler(sig_int) even though it wasn’t finished.
- We can use alarm to put an upper time limit on operations that can block.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
static void sig_alrm(int signo){}
int main()
{
char line[100];
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
{
Exit("signal(SIGALRM) error");
}
alarm(10);
if(read(STDIN_FILENO, line, 100) < 0)
{
Exit("read error");
}
alarm(0);
exit(0);
}
- This program has two problems.
- A race condition between the first call to alarm and the call to read. If the kernel blocks the process between these two function calls for longer than the alarm period, the read could block forever.
- If system calls are automatically restarted, the read is not interrupted when the SIGALRM signal handler returns. In this case, the timeout does nothing.
- Redo the preceding example using longjmp, we don’t worry about whether a slow system call is interrupted.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>
static jmp_buf env_alrm;
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
static void sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
int main()
{
char line[100];
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
{
Exit("signal(SIGALRM) error");
}
if(setjmp(env_alrm) != 0)
{
Exit("read timeout");
}
alarm(10);
if(read(STDIN_FILENO, line, 100) < 0)
{
Exit("read error");
}
alarm(0);
exit(0);
}
- Realize that we still have the problem of interactions with other signal handlers, as in Figure 10.8.
- If we want to set a time limit on an I/O operation, we need to use longjmp, while recognizing its possible interaction with other signal handlers. Another option is to use the select or poll functions, described in Sections 14.4.1 and 14.4.2.
10.11 Signal Sets
- POSIX.1 defines the data type sigset_t to contain a signal set and the following five functions to manipulate signal sets.
#define _GNU_SOURCE
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
All four return: 0 if OK, -1 on error
int sigismember(const sigset_t *set, int signo);
Returns: 1 if true, 0 if false, -1 on error
- sigemptyset initializes the signal set pointed to by set so that all signals are excluded.
- sigfillset initializes the signal set so that all signals are included.
- All applications have to call sigemptyset/sigfillset once for each signal set before using it, because we cannot assume that the C initialization for external and static variables(0) corresponds to the implementation of signal sets on a given system. Once we have initialized a signal set, we can add and delete specific signals in the set.
- sigaddset adds a single signal to an existing set.
- sigdelset removes a single signal from a set.
Implementation
- If the implementation has fewer signals than bits in an integer, a signal set can be implemented using one bit per signal. For the remainder of this section, assume that an implementation has 31 signals and 32-bit integers. sigemptyset zeros the integer, sigfillset turns on all the bits in the integer. These two functions can be implemented as macros in
#define sigemptyset(ptr) (*(ptr) = 0)
#define sigfillset(ptr) (*(ptr) = ~(sigset_t)0, 0)
- Since sigfillset must return 0, in addition to setting all the bits on in the signal set, we use comma operator, which returns the value after the comma as the value of the expression.
- sigaddset turns on a single bit and sigdelset turns off a single bit; sigismember tests a certain bit. Since no signal is ever numbered 0, we subtract 1 from the signal number to obtain the bit to manipulate. Figure 10.12 shows implementations.
10.12 sigprocmask Function
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
Returns: 0 if OK, -1 on error
- Signal mask of a process is the set of signals currently blocked from delivery to that process. A process can examine its signal mask, change its signal mask, or perform both operations in one step by calling sigprocmask.
- If oset != NULL: the current signal mask for the process is returned through oset.
- If set = NULL: signal mask of the process is not changed, and how is ignored.
- If set != NULL: how(Figure 10.13) indicates how the current signal mask is modified.
- SIGKILL and SIGSTOP can’t be blocked.
- After calling sigprocmask, if any unblocked signals are pending, at least one of these signals is delivered to the process before sigprocmask returns.
- sigprocmask is defined only for single-threaded processes. Separate function to manipulate thread’s signal mask in a multithreaded process is in Section 12.8.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno;
if(sigprocmask(0, NULL, &sigset) < 0)
{
Exit("sigprocmask error");
}
else
{
printf("%s", str);
if(sigismember(&sigset, SIGINT))
printf(" SIGINT");
if(sigismember(&sigset, SIGQUIT))
printf(" SIGQUIT");
if(sigismember(&sigset, SIGUSR1))
printf(" SIGUSR1");
if(sigismember(&sigset, SIGALRM))
printf(" SIGALRM");
printf("\n");
}
errno = errno_save;
}
- Figure 10.14 shows a function that prints the names of the signals in the signal mask of the calling process. We use this function in Figure 10.20 and Figure 10.22.
10.13 sigpending Function
#include <signal.h>
int sigpending(sigset_t *set);
Returns: 0 if OK, -1 on error
- sigpending returns the set of signals(through set) that are blocked from delivery and currently pending for the calling process.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
static void sig_quit(int signo)
{
printf("Catch SIGQUIT\n");
if(signal(SIGQUIT, SIG_DFL) == SIG_ERR)
{
Exit("can't reset SIGQUIT");
}
printf("Set SIGQUIT handler = SIG_DFL success\n");
}
int main()
{
sigset_t newmask, oldmask, pendmask;
if(signal(SIGQUIT, sig_quit))
{
Exit("Can't catch SIGQUIT");
}
printf("Set SIGQUIT handler = sig_quit success\n");
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
{
Exit("SIG_BLOCK error");
}
printf("SetBlock SIGQUIT success\n");
printf("Begin sleep\n");
sleep(5);
printf("End sleep\n");
if(sigpending(&pendmask) < 0)
{
Exit("sigpending error");
}
if(sigismember(&pendmask, SIGQUIT))
{
printf("\nSIGQUIT pending\n");
}
else
{
printf("\nNo SIGQUIT pending\n");
}
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
Exit("SIG_SETMASK error");
}
printf("Unblock SIGQUIT success\n");
printf("Begin sleep\n");
sleep(5);
printf("End sleep\n");
exit(0);
}
- The process blocks SIGQUIT, saving its current signal mask to restore later, and then goes to sleep for 5 seconds. Any occurrence of the quit signal during this period is blocked and won’t be delivered until the signal is unblocked. At the end of sleep, we check whether the signal is pending and unblock the signal.
- We save old mask when we block the signal. To unblock the signal, we did a SIG_SETMASK of the old mask. Alternatively, we could SIG_UNBLOCK only the signal that we had blocked. But if we write a function that can be called by others and if we need to block a signal in our function, we can’t use SIG_UNBLOCK to unblock the signal, we have to use SIG_SETMASK and restore the signal mask to its prior value, because it’s possible that the caller had blocked this signal before calling our function.
- If we generate the quit signal during this sleep period, the signal is now pending and unblocked, so it is delivered before sigprocmask returns(the printf in the signal handler is output before the printf that follows the call to sigprocmask).
- The process then goes to sleep for another 5 seconds. If we generate the quit signal during this sleep period, the signal should terminate the process, since we reset the handling of the signal to its default when we caught it.
$ ./a.out
^\ #generate signal once(before 5 seconds are up)
SIGQUIT pending #after return from sleep
caught SIGQUIT #in signal handler
SIGQUIT unblocked #after return from sigprocmask
^\Quit(core dump) #generate signal again
$ ./a.out
^\^\^\^\^\^\^\^\^\^\ #generate signal 10 times(before 5 seconds are up)
SIGQUIT pending
caught SIGQUIT #signal is generated only once
SIGQUIT unblocked
^\Quit(core dump) #generate signal again
- The message Quit(core dump) is printed by the shell when it sees that its child terminated abnormally.
- When we run the program the second time, we generate the quit signal ten times while the process is asleep, but the signal is delivered only once to the process when it’s unblocked. This demonstrates that signals are not queued on this system.
10.14 sigaction Function
#define _GNU_SOURCE
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
Returns: 0 if OK, -1 on error
- sigaction allows us to examine or modify(or both) the action associated with a signal.
- signo is the signal number whose action we are examining or modifying.
- If act != NULL: we are modifying the action.
- If oact != NULL: the system returns the previous action for the signal through oact.
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
- When changing the action for a signal, if sa_handler contains the address of a signal-catching function, then sa_mask specifies a set of signals that are added to the signal mask of the process before the signal-catching function is called. When the signal-catching function returns, the signal mask of the process is reset to its previous value. So, we are able to block certain signals whenever a signal handler is invoked.
- The operating system includes the signal being delivered in the signal mask when the handler is invoked. We are guaranteed that whenever we are processing a given signal, another occurrence of the same signal is blocked until we’re finished processing the first occurrence. Section 10.8: additional occurrences of the same signal are usually not queued.
- POSIX.1 requires that a signal handler remain installed until explicitly changed. Once we install an action for a given signal, that action remains installed until we explicitly change it by calling sigaction.
- sa_flags of act specifies options for the handling of this signal. Figure 10.16 details the meaning of these options when set. The SUS column contains • if the flag is defined as part of POSIX.1, and XSI if it is defined as part of the XSI option.
- sa_sigaction is an alternative signal handler used when the SA_SIGINFO flag is used with sigaction. Implementations might use the same storage for both the sa_sigaction field and the sa_handler field, so applications can use only one of these fields at a time.
- Normally, the signal handler is called as
void handler(int signo);
but if the SA_SIGINFO flag is set, the signal handler is called as
void handler(int signo, siginfo_t *info, void *context);
- The siginfo structure contains information about why the signal was generated. All POSIX.1-compliant implementations must include at least the si_signo and si_code members. Additionally, implementations that are XSI compliant contain at least the following fields:
struct siginfo
{
int si_signo;
int si_errno;
int si_code;
pid_t si_pid;
uid_t si_uid;
void* si_addr;
int si_status;
union sigval si_value;
};
- The sigval union contains the following fields:
int sival_int;
void* sival_ptr;
- Applications pass an integer value in si_value.sival_int or pass a pointer value in si_value.sival_ptr when delivering signals.
- Figure 10.17 shows values of si_code for various signals, as defined by the Single UNIX Specification. Note that implementations may define additional code values.
- If signal is SIGCHLD: si_pid, si_status, and si_uid fields will be set.
If signal is SIGBUS, SIGILL, SIGFPE, or SIGSEGV: si_addr contains the address responsible for the fault, although the address might not be accurate.
- si_errno field contains the error number corresponding to the condition that caused the signal to be generated, although its use is implementation defined.
- The context argument to the signal handler is a typeless pointer that can be cast to a ucontext_t structure identifying the process context at the time of signal delivery.
- This structure contains at least the following fields.
ucontext_t* uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
- The uc_stack field describes the stack used by the current context. It contains at least the following members.
void* ss_sp;
size_t ss_size;
int ss_flags;
- When an implementation supports the real-time signal extensions, signal handlers established with the SA_SIGINFO flag will result in signals being queued reliably. A separate range of reserved signal numbers is available for real-time application use. Applications can pass information along with the signal by using the sigqueue function(Section 10.20).
Example —signal Function
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
typedef void Sigfunc(int);
Sigfunc *signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(signo == SIGALRM)
{
act.sa_flags |= SA_INTERRUPT;
}
else
{
act.sa_flags |= SA_RESTART;
}
if(sigaction(signo, &act, &oact) < 0)
{
return SIG_ERR;
}
return oact.sa_handler;
}
int main()
{
if(signal(SIGALRM, SIG_DFL) == SIG_ERR)
{
printf("signal error");
}
exit(0);
}
- We must use sigemptyset to initialize sa_mask member of the structure because we’re not guaranteed that act.sa_mask = 0 does the same thing.
- We set the SA_RESTART flag for all signals other than SIGALRM(allow us to set a timeout for I/O operations(Figure 10.10)), so that any system call interrupted by these other signals will be automatically restarted.
Example —signal_intr Function
- Figure 10.19: signal that tries to prevent any interrupted system calls from being restarted.
10.15 sigsetjmp and siglongjmp Functions
- When a signal is caught, the signal-catching function is entered, with the current signal automatically being added to the signal mask of the process. This prevents subsequent occurrences of that signal from interrupting the signal handler. If we longjmp out of the signal handler, what happens to the signal mask for the process?
- BSD: setjmp and longjmp save and restore the signal mask. Linux don’t do this, although it supports an option to provide BSD behavior.
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
Returns: 0 if called directly, nonzero if returning from a call to siglongjmp
void siglongjmp(sigjmp_buf env, int val);
- If savemask != 0: sigsetjmp saves the current signal mask of the process in env. When siglongjmp is called, if env was saved by sigsetjmp with a nonzero savemask, then siglongjmp restores the saved signal mask.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <time.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno;
if (sigprocmask(0, NULL, &sigset) < 0)
{
Exit("sigprocmask error");
}
else
{
printf("%s", str);
if (sigismember(&sigset, SIGINT))
printf(" SIGINT");
if (sigismember(&sigset, SIGQUIT))
printf(" SIGQUIT");
if (sigismember(&sigset, SIGUSR1))
printf(" SIGUSR1");
if (sigismember(&sigset, SIGALRM))
printf(" SIGALRM");
printf("\n");
}
errno = errno_save;
}
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
static void sig_usr1(int signo)
{
time_t starttime;
if(canjump == 0)
{
return;
}
pr_mask("starting sig_usr1: ");
alarm(3);
starttime = time(NULL);
for(;;)
{
if(time(NULL) > starttime + 5)
{
break;
}
}
pr_mask("finishing sig_usr1: ");
canjump = 0;
siglongjmp(jmpbuf, 1);
}
static void sig_alrm(int signo)
{
pr_mask("in sig_alrm: ");
}
int main()
{
if(signal(SIGUSR1, sig_usr1) == SIG_ERR)
{
Exit("signal(SIGUSR1) error");
}
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
{
Exit("signal(SIGALRM) error");
}
pr_mask("starting main: ");
if(sigsetjmp(jmpbuf, 1))
{
pr_mask("ending main: ");
exit(0);
}
canjump = 1;
for(;;)
{
pause();
}
exit(0);
}
- One technique that should be used whenever siglongjmp is called from a signal handler: We set the variable canjump to a nonzero value only after we’ve called sigsetjmp. This variable is examined in the signal handler, and siglongjmp is called only if the flag canjump is nonzero. This technique provides protection against the signal handler being called at some earlier or later time, when the jump buffer hasn’t been initialized by sigsetjmp. Providing this type of protection usually isn’t required with longjmp in normal C code. Since a signal can occur at any time, we need the added protection in a signal handler.
- We use data type sig_atomic_t, which is the type of variable that can be written without being interrupted. A variable of this type should not extend across page boundaries on a system with virtual memory and can be accessed with a single machine instruction. We include the type qualifier
volatile
for these data types since the variable is being accessed by two different threads of control: the main function and the asynchronously executing signal handler. Figure 10.21 shows a timeline for this program.
- We can divide Figure 10.21 into three parts: left part(main), center part(sig_usr1), and right part(sig_alrm).
- While the process is executing in the left part, its signal mask is 0(no signals are blocked).
While executing in the center part, its signal mask is SIGUSR1.
While executing in the right part, its signal mask is SIGUSR1 | SIGALRM.
$ ./a.out & #start process in background
starting main:
[1] 531 #the job-control shell prints its process ID
$ kill -USR1 531 #send the process SIGUSR1
starting sig_usr1: SIGUSR1
$ in sig_alrm: SIGUSR1 SIGALRM
finishing sig_usr1: SIGUSR1
ending main:
#just press RETURN
[1] + Done ./a.out &
- When a signal handler is invoked, the signal being caught is added to the current signal mask of the process. The original mask is restored when the signal handler returns. siglongjmp restores the signal mask that was saved by sigsetjmp.
- If we change sigsetjmp/siglongjmp to setjmp/longjmp on Linux, the final line of output becomes
ending main: SIGUSR1
This means that the main function is executing with the SIGUSR1 signal blocked, after the call to setjmp.
10.16 sigsuspend Function
- What if we want to unblock a signal and then pause, waiting for the previously blocked signal to occur? Assuming that the signal is SIGINT, the incorrect way to do this is:
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
pause();
- If the signal is sent to the process while it is blocked, the signal delivery will be deferred until the signal is unblocked. To the application, this can look as if the signal occurs between the unblocking and the pause. If this happens, or if the signal occurs between the unblocking and the pause, it is lost. Any occurrence of the signal in this window of time is lost, we might not see the signal again and the pause will block indefinitely.
- We need a way to both restore the signal mask and put the process to sleep in a single atomic operation. This feature is provided by the sigsuspend function.
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
Returns: -1 with errno set to EINTR
- The signal mask of the process is set to the value pointed to by sigmask. Then the process is suspended until a signal is caught or until a signal occurs that terminates the process. If a signal is caught and the signal handler returns, sigsuspend returns, and the signal mask of the process is set to its value before the call to sigsuspend.
- There is no successful return from this function. If it returns to the caller, it always returns -1 with errno set to EINTR(indicating an interrupted system call).
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno;
if (sigprocmask(0, NULL, &sigset) < 0)
{
Exit("sigprocmask error");
}
else
{
printf("%s", str);
if (sigismember(&sigset, SIGINT))
printf(" SIGINT");
if (sigismember(&sigset, SIGQUIT))
printf(" SIGQUIT");
if (sigismember(&sigset, SIGUSR1))
printf(" SIGUSR1");
if (sigismember(&sigset, SIGALRM))
printf(" SIGALRM");
printf("\n");
}
errno = errno_save;
}
static void sig_int(int signo)
{
pr_mask("\nIn sig_int: ");
}
int main()
{
sigset_t newmask, oldmask, waitmask;
pr_mask("main start: ");
if(signal(SIGINT, sig_int) == SIG_ERR)
{
Exit("signal(SIGINT) error");
}
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
{
Exit("SIG_BLOCK error");
}
pr_mask("In critical region: ");
if(sigsuspend(&waitmask) != -1)
{
Exit("sigsuspend error");
}
pr_mask("After return from sigsuspend:");
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
Exit("SIG_SETMASK error");
}
pr_mask("main exit: ");
exit(0);
}
- Figure 10.22 shows the correct way to protect a critical region of code from a specific signal.
$ ./a.out
main start:
In critical region: SIGINT
^C
In sig_int: SIGINT SIGUSR1
After return from sigsuspend: SIGINT
main exit:
- We added SIGUSR1 to the mask installed when we called sigsuspend so that when the signal handler ran, we could tell that the mask had actually changed. We can see that when sigsuspend returns, it restores the signal mask to its value before the call.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
volatile sig_atomic_t quitflag;
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno;
if (sigprocmask(0, NULL, &sigset) < 0)
{
Exit("sigprocmask error");
}
else
{
printf("%s", str);
if (sigismember(&sigset, SIGINT))
printf(" SIGINT");
if (sigismember(&sigset, SIGQUIT))
printf(" SIGQUIT");
if (sigismember(&sigset, SIGUSR1))
printf(" SIGUSR1");
if (sigismember(&sigset, SIGALRM))
printf(" SIGALRM");
printf("\n");
}
errno = errno_save;
}
static void sig_int(int signo)
{
if(signo == SIGINT)
{
pr_mask("\nSIGINT: interrupt: ");
}
else if(signo == SIGQUIT)
{
pr_mask("\nSIGQUIT: quitflag = 1; ");
quitflag = 1;
}
}
int main()
{
sigset_t newmask, oldmask, zeromask;
if(signal(SIGINT, sig_int) == SIG_ERR)
{
Exit("signal(SIGINT) error");
}
if(signal(SIGQUIT, sig_int) == SIG_ERR)
{
Exit("signal(SIGQUIT) error");
}
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
pr_mask("main start: ");
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
{
Exit("SIG_BLOCK error");
}
pr_mask("After SIG_BLOCK: ");
while(quitflag == 0)
{
pr_mask("while(quitflag == 0):");
sigsuspend(&zeromask);
}
pr_mask("After while(quitflag == 0): ");
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
Exit("SIG_SETMASK error");
}
pr_mask("After SIG_SETMASK, main exit: ");
exit(0);
}
- Figure 10.23: use sigsuspend to wait for a signal handler to set a global variable. We catch both the interrupt signal and the quit signal, but want to wake up the main routine only when the quit signal is caught.
$ ./a.out
main start:
After SIG_BLOCK: SIGQUIT
while(quitflag == 0): SIGQUIT
^C
SIGINT: interrupt: SIGINT
while(quitflag == 0): SIGQUIT
^C
SIGINT: interrupt: SIGINT
while(quitflag == 0): SIGQUIT
^C
SIGINT: interrupt: SIGINT
while(quitflag == 0): SIGQUIT
^\
SIGQUIT: quitflag = 1; SIGQUIT
After while(quitflag == 0): SIGQUIT
After SIG_SETMASK, main exit:
- Figure 10.24 shows how signals can be used to synchronize a parent and child, the five routines TELL_WAIT, TELL_PARENT, TELL_CHILD, WAIT_PARENT, and WAIT_CHILD from Section 8.9.
- We use two user-defined signals: SIGUSR1 is sent by parent to child; SIGUSR2 is sent by child to parent.
- The sigsuspend function is fine if we want to go to sleep while we’re waiting for a signal to occur, but what if we want to call other system functions while we’re waiting? This problem has no solution unless we use multiple threads and dedicate a separate thread to handling signals(Section 12.8).
- Without threads, the best is to set a global variable in the signal handler when the signal occurs. For example, if we catch both SIGINT and SIGALRM and install the signal handlers using the signal_intr function, the signals will interrupt any slow system call that is blocked. The signals are most likely to occur when we’re blocked in a call to the read function waiting for input from a slow device(This is especially true for SIGALRM, since we set the alarm clock to prevent us from waiting forever for input.) The code to handle this looks similar to the following:
if(intr_flag)
{
handle_intr();
}
if(alrm_flag)
{
handle_alrm();
}
while((n = read( ... )) < 0)
{
if(errno == EINTR)
{
if(alrm_flag)
{
handle_alrm();
}
else if(intr_flag)
{
handle_intr();
}
}
else
{
}
}
else if(n == 0)
{
}
else
{
}
- We test each of the global flags before calling read and again if read returns an interrupted system call error. The problem occurs if either signal is caught between the first two if statements and the subsequent call to read. Signals occurring in here are lost. The signal handlers are called, and they set the appropriate global variable, but the read never returns(unless some data is ready to be read).
- What we would like to be able to do is the following sequence of steps, in order.
- Block SIGINT and SIGALRM.
- Test the two global variables to see whether either signal has occurred and, if so, handle the condition.
- Call read(or any other system function) and unblock the two signals, as an atomic operation. The sigsuspend function helps us only if step 3 is a pause operation.
10.17 abort Function
#include <stdlib.h>
void abort(void);
This function never returns
- abort sends SIGABRT signal to the caller. Processes should not ignore this signal.
- ISO C states that calling abort will deliver an unsuccessful termination notification to the host environment by calling raise(SIGABRT). ISO C requires that if the signal is caught and the signal handler returns, abort still doesn’t return to its caller. If this signal is caught, the only way the signal handler can’t return is if it calls exit, _exit, _Exit, longjmp, or siglongjmp. POSIX.1 specifies that abort overrides the blocking or ignoring of the signal by the process.
- The intent to catch SIGABRT is to allow process to perform any cleanup that it wants to do before the process terminates. If the process doesn’t terminate itself from this signal handler, POSIX.1 states that, when the signal handler returns, abort terminates the process.
- ISO C leaves it up to the implementation as to whether output streams are flushed and whether temporary files(Section 5.13) are deleted. POSIX.1 allows an implementation to call fclose on open standard I/O streams before terminating if the call to abort terminates the process.
- For defensive programming, if we want standard I/O streams to be flushed, we specifically do it before calling abort(err_dump function in Appendix B).
- Figure 10.25 shows an implementation of abort function as specified by POSIX.1.
- We first see whether the default action will occur; if so, we flush all the standard I/O streams. This is not equivalent to calling fclose on all the open streams since fflush just flushes them and doesn’t close them; when the process terminates, the system closes all open files.
If the process catches the signal and returns, we flush all the streams again, since the process could have generated more output.
- The only condition we don’t handle is the case where the process catches the signal and calls _exit or _Exit. In this case, any unflushed standard I/O buffers in memory are discarded. We assume that caller that does this doesn’t want the buffers flushed.
- Section 10.9: if calling kill causes the signal to be generated for the caller, and if the signal is not blocked(we guarantee in Figure 10.25), then the signal(or some other pending, unlocked signal) is delivered to the process before kill returns. We block all signals except SIGABRT, so we know that if the call to kill returns, the process caught the signal and the signal handler returned.
10.18 system Function
- system function in Section 8.13 did not do any signal handling. POSIX.1 requires that system ignore SIGINT and SIGQUIT and block SIGCHLD. Before showing a version that handles these signals correctly, let’s see why we need to worry about signal handling.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
void Exit(char *string)
{
printf("%s\n", string);
exit(1);
}
int System(const char *cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL)
{
return 1;
}
if((pid = fork()) < 0)
{
status = -1;
}
else if(pid == 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
else
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1;
break;
}
}
}
return status;
}
static void sig_int(int signo)
{
printf("Catch SIGINT\n");
}
static void sig_chld(int signo)
{
printf("Catch SIGCHLD\n");
}
int main()
{
if(signal(SIGINT, sig_int) == SIG_ERR)
{
Exit("signal(SIGINT) error");
}
if(signal(SIGCHLD, sig_chld) == SIG_ERR)
{
Exit("signal(SIGCHLD) error");
}
if(system("/bin/ed") < 0)
{
Exit("System() error");
}
exit(0);
}
- The program in Figure 10.26 uses system from Section 8.13 to invoke the ed(1) editor.(ed is an interactive program that catches the interrupt and quit signals. If we invoke ed from a shell and type the interrupt character, it catches the interrupt signal and prints a question mark. The ed program sets the disposition of quit signal so that it is ignored.) The program in Figure 10.26 catches both SIGINT and SIGCHLD. If we invoke the program, we get
$ ./a.out
a #append text to the editor’s buffer
Here is one line of text
. #period on a line by itself stops append mode
1,$p #print first through last lines of buffer to see what’s there
Here is one line of text
w temp.foo #write the buffer to a file
25 #editor says it wrote 25 bytes
q #and leave the editor
caught SIGCHLD
- When the editor terminates, the system sends the SIGCHLD signal to the parent(the a.out process). We catch it and return from the signal handler. But if it is catching the SIGCHLD signal, the parent should be doing so because it has created its own children, so that it knows when its children have terminated. The delivery of this signal in the parent should be blocked while the system function is executing. This is what POSIX.1 specifies. Otherwise, when the child created by system terminates, it would fool the caller of system into thinking that one of its own children terminated. The caller would then use one of the wait functions to get the termination status of the child, thereby preventing the system function from being able to obtain the child’s termination status for its return value.
- If we run the program again, this time sending the editor an interrupt signal, we get
$ ./a.out
a #append text to the editor’s buffer
hello, world
. #period on a line by itself stops append mode
1,$p #print first through last lines to see what’s there
hello, world
w temp.foo #write the buffer to a file
13 #editor says it wrote 13 bytes
^C #type the interrupt character
? #editor catches signal, prints question mark
caught SIGINT #and so does the parent process
q #leave editor
caught SIGCHLD
- Section 9.6: Typing the interrupt character causes the interrupt signal to be sent to all the processes in the foreground process group. Figure 10.27 shows the arrangement of the processes when the editor is running.
- SIGINT is sent to all three foreground processes, shell ignores it. As we can see from the output, both the a.out process and the editor catch the signal. But when we’re running system function, we shouldn’t have both the parent and the child catching the two terminal-generated signals: interrupt and quit. Instead, these two signals should be sent to the program that is running: the child. Since the caller of system gives up control while the program executes, waiting for it to finish, the caller of system should not receive these two terminal-generated signals. For this reason, POSIX.1 specifies that the system function should ignore these two signals while waiting for the command to complete.
- If we link the program in Figure 10.26 with this system function, the resulting binary differs from the last(flawed) one in the following ways.
- No signal is sent to the calling process when we type the interrupt or quit character.
- When the ed command exits, SIGCHLD is not sent to the calling process. Instead, it is blocked until we unblock it in the last call to sigprocmask, after the system function retrieves the child’s termination status by calling waitpid.
- POSIX.1 states that if wait or waitpid returns the status of a child process while SIGCHLD is pending, then SIGCHLD should not be delivered to the process unless the status of another child process is also available. Linux doesn’t do it. SIGCHLD remains pending after the system function calls waitpid; when the signal is unblocked, it is delivered to the caller. If we called wait in the sig_chld function in Figure 10.26, a Linux system would return -1 with errno set to ECHILD, since the system function already retrieved the termination status of the child.
- Many older texts show the ignoring of the interrupt and quit signals as follows:
if((pid = fork()) < 0)
{
err_sys("fork error");
}
else if (pid == 0)
{
execl(...);
_exit(127);
}
old_intr = signal(SIGINT, SIG_IGN);
old_quit = signal(SIGQUIT, SIG_IGN);
waitpid(pid, &status, 0)
signal(SIGINT, old_intr);
signal(SIGQUIT, old_quit);
- The problem with this code is that we have no guarantee after the fork regarding whether the parent or child runs first. If the child runs first and the parent doesn’t run for some time after, an interrupt signal might be generated before the parent is able to change its disposition to be ignored. So, we change the disposition of the signals before the fork in Figure 10.28.
- Note that we have to reset the dispositions of these two signals in the child before the call to execl. This allows execl to change their dispositions to the default, based on the caller’s dispositions(Section 8.10).
Return Value from system
- The return value from system is the termination status of the shell, which isn’t always the termination status of the command string.
- Run the program in Figure 8.24 and send signals to the command that’s executing:
$ tsys "sleep 30" #we press the interrupt key
^C normal termination, exit status = 130
$ tsys "sleep 30" #we press the quit key
ˆ\sh: 946 Quit
normal termination, exit status = 131
- When we terminate the sleep call with the interrupt signal, pr_exit(Figure 8.5) thinks that it terminated normally. The same thing happens when we kill the sleep call with the quit key. The Bourne shell has a poorly documented feature in which its termination status is 128 plus the signal number, when the command it was executing is terminated by a signal. We can see this with the shell interactively.
$ sh #make sure we’re running the Bourne shell
$ sh -c "sleep 30"
ˆC #press the interrupt key
$ echo $? #print termination status of last command
130
$ sh -c "sleep 30"
ˆ\sh: 962 Quit - core dumped #press the quit key
$ echo $? #print termination status of last command
131
$ exit #leave Bourne shell
- On the system being used, SIGINT = 2 and SIGQUIT = 3, giving us the shell’s termination statuses of 130 and 131.
- This time we send a signal directly to the shell and see what is returned by system:
- We can see that the return value from system reports an abnormal termination only when the shell itself terminates abnormally.
- If we find our process executing sleep and send it a signal directly, so that the signal goes only to the individual process instead of the entire foreground process group, we will find that these shells behave like the Bourne shell and exit with a normal termination status of 128 plus the signal number.
- When writing programs that use the system function, be sure to interpret the return value correctly. If you call fork, exec, and wait yourself, the termination status is not the same as if you call system.
10.19 sleep, nanosleep, and clock_nanosleep Functions
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
Returns: 0 or number of unslept seconds
- This function causes the calling process to be suspended until either
- The amount of wall clock time specified by seconds has elapsed.(return 0)
- A signal is caught by the process and the signal handler returns.(return number of unslept seconds)
- As with an alarm signal, the actual return may occur at a time later than requested because of other system activity.
- Although sleep can be implemented with alarm(Section 10.10), this isn’t required. If alarm is used, there can be interactions between the two functions. POSIX.1 standard leaves all these interactions unspecified.
For example, if we do an alarm(10) and 3 wall clock seconds later do sleep(5), what happens?
The sleep will return in 5 seconds(assuming that some other signal isn’t caught in the interim), but will another SIGALRM be generated 2 seconds later? These details depend on the implementation.
- Linux implement sleep using nanosleep, which allows the implementation to be independent of signals and the alarm timer.
- Figure 10.29 shows an implementation of POSIX.1 sleep. This function is a modification of Figure 10.7, which handles signals reliably, avoiding the race condition in the earlier implementation.
- We don’t use any form of nonlocal branching(as in Figure 10.8 to avoid the race condition between alarm and pause), so there is no effect on other signal handlers that may be executing when the SIGALRM is handled.
#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
Returns: 0 if slept for requested time or -1 on error
- nanosleep provides nanosecond-level granularity. It suspends the calling process until either the requested time has elapsed or the function is interrupted by a signal.
- reqtp specifies the amount of time to sleep in seconds and nanoseconds. If the sleep interval is interrupted by a signal and the process doesn’t terminate, the timespec structure pointed to by remtp will be set to the amount of time left in the sleep interval. We can set this parameter to NULL if we are uninterested in the time unslept.
- If the system doesn’t support nanosecond granularity, the requested time is rounded up. Because nanosleep doesn’t involve the generation of any signals, we can use it without worrying about interactions with other functions.
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *reqtp, struct timespec *remtp);
Returns: 0 if slept for requested time or error number on failure
- With the introduction of multiple system clocks(Section 6.10), we can use clock_nanosleep to suspend the calling thread using a delay time relative to a particular clock.
- clock_id specifies the clock against which the time delay is evaluated. Identifiers for clocks are listed in Figure 6.8
- flags is used to control whether the delay is absolute or relative. flags =
- 0: sleep time is relative(i.e., how long we want to sleep).
- TIMER_ABSTIME: sleep time is absolute(i.e., we want to sleep until the clock reaches the specified time).
- reqtp and remtp are the same as in nanosleep. When we use an absolute time, remtp is unused because it isn’t needed; we can reuse the same value for the reqtp argument for additional calls to clock_nanosleep until the clock reaches the specified absolute time value.
- Except for error returns,
clock_nanosleep(CLOCK_REALTIME, 0, reqtp, remtp);
has the same effect as
nanosleep(reqtp, remtp);
- The problem with using a relative sleep is that some applications require precision with how long they sleep, and a relative sleep time can lead to sleeping longer than desired. For example, if an application wants to perform a task at regular intervals, it would have to get the current time, calculate the amount of time until the next time to execute the task, and then call nanosleep. Between the time that the current time is obtained and the call to nanosleep is made, processor scheduling and preemption can result in the relative sleep time extending past the desired interval. Using an absolute time improves the precision, even though a time-sharing process scheduler makes no guarantee that our task will execute immediately after our sleep time has ended.
10.20 sigqueue Function
- Section 10.8: Most UNIX systems don’t queue signals. With the real-time extensions to POSIX.1, some systems began adding support for queueing signals.
- Generally a signal carries one bit of information: the signal itself. In addition to queueing signals, these extensions allow applications to pass more information along with the delivery(Section 10.14). This information is embedded in a siginfo structure. Along with system-provided information, applications can pass an integer or a pointer to a buffer containing more information to the signal handler.
- To use queued signals we have to do the following:
- Specify the SA_SIGINFO flag when we install a signal handler using the sigaction function. If we don’t specify this flag, the signal will be posted, but it is left up to the implementation whether the signal is queued.
- Provide a signal handler in the sa_sigaction member of the sigaction structure instead of using the usual sa_handler field. Implementations might allow us to use the sa_handler field, but we won’t be able to obtain the extra information sent with the sigqueue function.
- Use the sigqueue function to send signals.
#include <signal.h>
int sigqueue(pid_t pid, int signo, const union sigval value)
Returns: 0 if OK, -1 on error
- sigqueue is similar to kill, except that we can only direct signals to a single process with sigqueue, and we can use the value argument to transmit either an integer or a pointer value to the signal handler.
- Signals can’t be queued infinitely: SIGQUEUE_MAX limit from Figure 2.9 and Figure 2.11. When this limit is reached, sigqueue can fail with errno set to EAGAIN.
- With the real-time signal enhancements, a separate set of signals was introduced for application use. These are the signal numbers between SIGRTMIN and SIGRTMAX, inclusive. Be aware that the default action for these signals is to terminate the process.
- Figure 10.30 summarizes the way queued signals differ in behavior among the implementations covered in this text.
10.21 Job-Control Signals
- Of the signals in Figure 10.1, POSIX.1 considers six to be job-control signals:
SIGCHLD Child process has stopped or terminated.
SIGCONT Continue process, if stopped.
SIGSTOP Stop signal(can’t be caught or ignored).
SIGTSTP Interactive stop signal.
SIGTTIN Read from controlling terminal by background process group member.
SIGTTOU Write to controlling terminal by a background process group member.
- Except for SIGCHLD, most programs don’t handle these signals: interactive shells usually do all the work required to handle them. When we type the suspend character(Control-Z), SIGTSTP is sent to all processes in the foreground process group. When we tell the shell to resume a job in the foreground or background, the shell sends all the processes in the job the SIGCONT signal. If SIGTTIN or SIGTTOU is delivered to a process, the process is stopped by default, and the job-control shell recognizes this and notifies us.
- Exception is a process that is managing the terminal, the vi(1) editor, for example. It needs to know when the user wants to suspend it so that it can restore the terminal’s state to the way it was when vi was started. When it resumes in the foreground, the vi editor needs to set the terminal state back to the way it wants it, and it needs to redraw the terminal screen.
- There are some interactions between the job-control signals. When any of the four stop signals(SIGTSTP, SIGSTOP, SIGTTIN, or SIGTTOU) is generated for a process, any pending SIGCONT signal for that process is discarded. When SIGCONT is generated for a process, any pending stop signals for that same process are discarded.
- The default action for SIGCONT is to continue the process, if it is stopped; otherwise, the signal is ignored. Normally, we don’t have to do anything with this signal. When SIGCONT is generated for a process that is stopped, the process is continued, even if the signal is blocked or ignored.
- The program in Figure 10.31 demonstrates the normal sequence of code used when a program handles job control. This program simply copies its standard input to its standard output, but comments are given in the signal handler for typical actions performed by a program that manages a screen.
- When the program in Figure 10.31 starts, it arranges to catch the SIGTSTP signal only if the signal’s disposition is SIG_DFL. The reason is that when the program is started by a shell that doesn’t support job control(/bin/sh, for example), the signal’s disposition should be set to SIG_IGN. In fact, the shell doesn’t explicitly ignore this signal; init sets the disposition of the three job-control signals(SIGTSTP, SIGTTIN, and SIGTTOU) to SIG_IGN. This disposition is then inherited by all login shells. Only a job-control shell should reset the disposition of these three signals to SIG_DFL.
- When we type the suspend character, the process receives the SIGTSTP signal and the signal handler is invoked. At this point, we would do any terminal-related processing: move the cursor to the lower-left corner, restore the terminal mode, and so on. We then send ourself the same signal, SIGTSTP, after resetting its disposition to its default(stop the process) and unblocking the signal. We have to unblock it since we’re currently handling that same signal, and the system blocks it automatically while it’s being caught. At this point, the system stops the process. It is continued only when it receives(usually from the job-control shell, in response to an interactive fg command) a SIGCONT signal. We don’t catch SIGCONT. Its default disposition is to continue the stopped process; when this happens, the program continues as though it returned from the kill function. When the program is continued, we reset the disposition for the SIGTSTP signal and do whatever terminal processing we want(we could redraw the screen, for example).
10.22 Signal Names and Numbers
- How to map between signal numbers and names? Linux provide the array
extern char *sys_siglist[];
The array index is the signal number, giving a pointer to the character string name of the signal.
#include <signal.h>
void psignal(int signo, const char *msg);
- psignal print the character string corresponding to a signal number in a portable manner.
- The string msg(which normally includes the name of the program) is output to the standard error, followed by a colon and a space, followed by a description of the signal, followed by a newline. If msg is NULL, then only the description is written to the standard error.
#include <signal.h>
void psiginfo(const siginfo_t *info, const char *msg);
- If you have a siginfo structure from an alternative sigaction signal handler, you can print the signal information with the psiginfo function.
- Although this function has access to more information than just the signal number, platforms vary in exactly what additional information is printed.
- If you only need the string description of the signal and don’t necessarily want to write it to standard error(you might want to write it to a log file, for example), you can use the strsignal function.
#include <string.h>
char *strsignal(int signo);
Returns: a pointer to a string describing the signal
- Given a signal number, strsignal will return a string that describes the signal. This string can be used by applications to print error messages about signals received. Linux return a string indicating that the signal number is unrecognized if the signal number is invalid.
10.23 Summary
Please indicate the source: http://blog.csdn.net/gaoxiangnumber1
Welcome to my github: https://github.com/gaoxiangnumber1