580 likes | 797 Views
Signals. What is a signal. A signal is a software interrupt. Usually a signal is sent to a process asynchronously and whatever the process is doing is interrupted. Signals have been around since the early days of Unix, but early versions were not reliable, signals could get lost. Both
E N D
What is a signal A signal is a software interrupt. Usually a signal is sent to a process asynchronously and whatever the process is doing is interrupted. Signals have been around since the early days of Unix, but early versions were not reliable, signals could get lost. Both 4.3BSD and SVR3 made revisions to their signal code to make signals reliable, but the changes were incompatible. Posix.1 standardized the reliable signal routines.
The Life Cycle of a Signal A signal is born when an event occurs that generates the signal. A signal ceases to exist when it is delivered, which means that some action specified for the signal has been taken. Between generation and delivery a signal is said to be pending.
Conditions that Generate Signals Terminal generated signals: occur when users type certain keys on the keyboard. For example, Ctrl-C normally generates the signal SIGINT. Hardware exceptions: conditions detected by hardware, like divide by zero, are sent to the kernel. The kernel generates the appropriate signal. The kill( ) function or the kill command: The kill function allows a process to send any signal to another process or process group, as long as it owns that process. Software Conditions: Some software conditions can cause signals to be generated. For example, SIGURG is generated when out of band data arrives over a network connection.
Some Common Signal Names SIGABRT process abort implementation defined SIGALRM timer abnormal termination SIGBUS access undefined memory implementation defined SIGCHILD child process terminated ignore SIGCONT execution continued continue SIGFPE error in arithmetic implementation defined SIGHUP terminal hangs up abnormal termination SIGILL invalid instruction implementation defined SIGINT Ctrl-C abnormal termination SIGKILL terminated abnormal termination SIGPIPE write on pipe w/ no reader abnormal termination SIGSEGV invalid memory reference implementation defined SIGTERM termination stop SIGUSR1 user defined abnormal termination SIGUSR2 user defined abnormal termination
See all of the signals the os supports by typing kill -l See the key sequences that generate signals on your system by typing stty -a
A signal is a classic example of an asynchronous event. They can occur at random times as far as the process is concerned. A process must tell the kernel what to do if and when a signal is received. A process can: * Catch the signal and ignore it - SIGKILL and SIGSTOP cannot be ignored - ignored signals have no effect on the process * Catch the signal and do something with it * Let the default apply - usually terminates the process
When you get a signal from a hardware detected error condition, you should not ignore the signal. Your program will likely not get safely past the condition. Such signals should be handled right away and the program terminated with a call to exit( ) More on this later . . .
Terminology A signal is a software notification of an event. A signal is generated when the event that causes the signal occurs. A signal is delivered when the process catches the signal and takes some action on it. A signal that has not been delivered is pending. A signal handler is a function that the programmer writes to take some specific action when a given signal occurs. A program installs a signal handler by calling sigaction( ). A signal handler may choose to ignore a signal, in which case it is thrown away. A process can use a signal mask to block a set of signals. A blocked signal waits in a pending mode until the process unblocks the signal, at which time it is delivered to the process.
What to do with a Signal Ignore the signal. Most signals can be ignored, but SIGKILL and SIGSTOP cannot. Catch the signal by telling the kernel to call a function provided by the programmer (signal handler), when a particular signal is generated. Let the default action apply. The default for most signals is to terminate the process.
A really polished program will . . . Block all signals as soon as your program begins. Set all keyboard generated signals you don’t want to handle to be ignored. Catch SIGTERM and arrange to clean everything up and terminate when it arrives. This is the standard way that a sys admin will shut down processes. Catch all error generated signals and arrange to log them, print an appropriate error message, do any necessary clean-up, and terminate.
Generating Signals from the command line A user can only kill a process that he or she owns. Generate a signal with the kill command. From the shell kill –s signal pid This is one of the signal names less the “sig” prefix Example: kill –s USR1 3423
Generating Signals from within a program #include <sys/types.h> #include <signal.h> int kill (pid_t pid, int sig ); returns 0 if successful if pid is positive, kill sends the signal sig to the process pid (must own the process) if pid is zero, kill sends the signal to members of the callers process group
Killing a Parent Process Sounds grim, but ….. #include <stdio.h> #include <unistd.h> #include <signal.h> if (kill(getppid( ) , SIGTERM) == -1) perror(“Error in kill command”);
Sending a Signal to Yourself #include <stdio.h> #include <unistd.h> #include <signal.h> if (raise(SIGTERM) == -1) perror(“Error in raise command”);
Generating Signals (cont) The function alarm ( unsigned int num ) will generate a SIGALRM signal after num seconds have elapsed. The signal is sent to the calling process.
Signal Sets A process can temporarily prevent a signal from being delivered by blocking it. Blocked signals do not affect the behavior of the process until they are delivered. The signal mask contains the set of all of the signals that are currently blocked. The following functions initialize and modify a signal set. * intsigemptyset(sigset_t *set); intsigfillset(sigset_t *set); intsigaddset(sigset_t *set, int sig); intsigdelset(sigset_t *set, int sig); intsigismember(constsigset_t *set, int sig); contains no signals use either of these functions to initialize a signal mask before using it. contains all signals adds a signal to the signalset deletes a signal from the signalset This function returns 1 if sig is a member of the set. Otherwise it returns 0. * note that blocking a signal is different from ignoring a signal.
Example Code this code initializes the signal set twosigs so that it contains just the two signals SIGINT and SIGQUIT. #include <signal.h> sigset_t twosigs; … sigemptyset (&twosigs); sigaddset (&twosigs, SIGINT); sigaddset (&twosigs, SIGQUIT); create a signal set make the signal set empty add sigint add sigquit
SIGPROCMASK Installing a signal set SIG_BLOCK: add this set to the signals currently blocked SIG_UNBLOCK: delete this set from the signals currently blocked SIG_SETMASK: set the signal mask to this set #include <signal.h> int sigprocmask (int how, const sigset_t *set, sigset_t *oldset); the function returns the current set of signals being blocked in oldset. This allows the program to restore the old signal set. If this value is NULL, then no value is returned. the set of signals to be used for the modification. If this value is NULL, then the function simply retrieves the current signal mask and stores it in oldset. No change is made to the current signal set.
Example Code this code adds SIGINT to the set of signals that the process currently has blocked. #include <signal.h> sigset_t mymask; … sigemptyset(&mymask); sigaddset(&mymask, SIGINT); sigprocmask(SIG_BLOCK, &mymask, NULL); … sigprocmask(SIG_UNBLOCK, &mymask, NULL); … The old set is not saved. initialize the mask set and then set SIGINT //if a ctrl-C occurs here, it will be blocked. add to the signal mask using the signal set we just created clears the SIGINT from the signal mask
Example Code This code will block Ctrl-C then do some useless work. If a Ctrl-C comes in during this time, the process does not see the signal. When the process unblocks it, the signal is delivered. cns3060/signals/ex1
Handling Signals Remember that blocking a signal only means that the signal will not be delivered until the signal is unblocked. It is a way of temporarily protecting your code from a signal. Once the signal is unblocked it will be delivered to the process. Then it must be handled in some way. In most cases, an unhandled signal will result in the process terminating.
Handling Signals There are two issues to think about: What do you need to do inside of the signal handler to change the state of the application so that the application knows that the signal occurred? Where do you go after handling the signal. Choices are: return to where the application was when it was interrupted terminate the program do a global jump to another part of the program
Reentrant Functions When a signal is handled, the signal handler has no notion of what the application was doing when it was interrupted by the signal. What if the application had just called a function like malloc, and the signal handler also calls malloc. - the heap manager could get confused.
Reentrant Functions Note that when you in the signal handler, you are limited to making system calls that are considered to be asynch-signal-safe. These system calls are guaranteed to be re-entrant. POSIX defines the system calls that are guaranteed to be asynch-signal-safe.
The Signal System Call This call was defined by ANSI C, but has been deprecated. It does not work well in multi-process environments. However, it is a simple call and illustrates signal handling effectively. #include <signal.h> void signal ( int signum, void (*act) (int) ) this is just a pointer to a function that takes an integer parameter and returns a void.
Example from Molay chapter 6 #include <stdio.h> #include <signal.h> void f(int); int main ( ) { int i; signal ( SIGINT, kickme); for (i = 0; i < 10; i++) { printf(“hello\n”); sleep(1); } return 0; } The signal handler void kickme ( intsignum ) { printf(“\nOUCH”); }
int main ( ) { int i; signal ( SIGINT, kickme); for (i = 0; i < 10; i++) { printf(“hello\n”); sleep(1); } return 0; } install the signal handler void kickme ( intsignum ) { printf(“\nOUCH”); }
int main ( ) { int i; signal ( SIGINT, kickme); for (i = 0; i < 10; i++) { printf(“hello\n”); sleep(1); } return 0; } hello hello hello ouch INTERRUPT (SIGINT) void kickme ( intsignum ) { printf(“\nOUCH”); } look at the code – ouch.c
Signal Handlers #include <signal.h> intsigaction (int sig, conststructsigaction *act, structsigaction *old); structsigaction { void (*sa_handler) ( ); // SIG_DFL, SIG_IGN, or pointer to function sigset_tsa_mask; // additional signals to be blocked while in handler intsa_flags; // flags and options } May be null the signal handling information to be set for handling this signal. the signal to be caught the function returns the current signal handling information in this structure. If NULL, nothing is returned. reset to default ignore eliminates race conditions SA_RESTART Restart any system calls interrupted by this signal once the signal handler has finished
A signal handler is an ordinary function that takes a single integer parameter and returns a void. The operating system sets the parameter to the signal that was delivered to the process. Most signal handlers ignore this parameter, although it is possible to write a signal handler that handles several different signals.
Example The following code segment sets up a signal handler that catches ctrl-C. … char handlermsg[ ] = “User hit ctrl-c!\n”; void catch_ctrl_c (int signo) { write(STDERR_FILENO, handlermsg, strlen(handlermsg)); } … struct sigaction act; act.sa_handler = catch_ctrl_c; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(SIGINT, &act, NULL) < 0) { // signal handler is installed … this is the actual signal handler. It prints a message. note that write is signal safe ... printf is not! load the address of the handler don’t worry about any other signals no flags
Example The following code segment sets up a signal handler that ignores SIGINT if it is using the Default handler for this signal. #include <signal.h> #include <stdio.h> struct sigaction act; … if (sigaction(SIGINT, NULL, &act) == -1) perror(“Could not get the old signal handler for SIGINT”); else if (act.sa_handler == SIG_DFL) { act.sa_handler = SIG_IGN; if (sigaction(SIGINT, &act, NULL) == -1) perror (“Could not ignore SIGINT”); } This just gets the current signal handler in act This sets the new signal handler
Question How do you tell an application that a signal that it expected has occurred?
See handler.c example This code uses a shared variable as a flag. The program computes in an endless loop until the flag gets set. The flag gets set by the signal handler for SIGINT.
Waiting for a Signal One of the primary uses of signals is to keep programs from burning up cpu time while waiting for some event. Instead of running in a tight loop and checking to see if a signal has been received (polling), the program can put itself into a suspended state until the waited-for event occurs. #include <unistd.h> int pause ( ); the return value is not significant. pause suspends the process until any signal that is not being ignored is delivered to the process. If a signal is caught by the process, the pause returns after the signal handler returns. pause( ) alwaysreturns -1 with errno set to EINTR
pause() process B process A signal handler signal
The Problem with Pause to wait for a particular signal, we need to check which signal caused the pause to return. This information is not directly available, so we use an external static variable as a flag which the signal handler sets to 1. We can then check this flag when pause returns. … static volatile sig_atomic_t signal_received = 0; … while(signal_received == 0) pause( ); // go on to other stuff … the pause call is put into a loop so that we can check the value of the flag. If it is still zero, we must call pause again. what happens if a signal is delivered between the test and the pause? The program does not catch it!! the pause does not return until another signal is received. To solve this problem, we should test the flag while the signal is blocked! Volatile: This value may be changed by something outside of this scope. It should not be optimized by the compiler. sig_atomic_t is a data type that is guaranteed can be read or written without being interruped.
Does This Work? … sigset_tsigset; intsignum; sigemptyset(&sigset); sigaddset(&sigset, signum); sigprocmask(SIG_BLOCK, &sigset, NULL); while(signal_received == 0) pause(); … // signum is the signal we want to catch No! Now the signal is blocked when the pause statement is executed, so the program never receives the signal.
sigsuspend The delivery of signals with pause was a major problem in Unix. It was fixed by adding the sigsuspend operation. int sigsuspend(const sigset_t *sigmask); return value is always -1 sigsuspend sets the signal mask to the one pointed to by sigmask and pauses the process in a single atomic operation. When sigsuspend returns, the signal mask is reset to the value it had before sigsuspend was called.
Consider the following code sigfillset(&mask); // set mask to block all signals sigdelset(&mask, signum); // remove signum from the set sigsuspend(&mask); // wait for signum Now the program pauses. All signals except signum have been blocked. What’s wrong?
Consider the following code sigfillset(&mask); // set mask to block all signals sigdelset(&mask, signum); // remove signum from the set sigsuspend(&mask); // wait for signum what happens if the signal is delivered just before this code block is entered? The signal is handled, and now sigsuspend puts the program in a wait condition. If another signal is not delivered, the program waits forever.
now, all signals are blocked. Correct code to wait for a signal (signum) declare static variable to test. It gets set in the signal hander. static volatile sig_atomic_tsigreceived = 0; sigset_tmaskall, maskmost, maskold; intsignum = SIGNUM; sigfillset(&maskall); sigfillset(&maskmost); sigdelset(&maskmost, SIGNUM); sigprocmask(SIG_SETMASK, &maskall, &maskold); if (sigreceived == 0) sigsuspend(&maskmost); sigprocmask(SIGSETMASK, &maskold, NULL); the signal we want to wait for. block all signals blocks all signals but SIGNUM test the flag. this is the critical section unblock SIGNUM and pause as an atomic action. after returning from the signal handler, restore the old signal mask.
Correct code to wait for a signal ... allowing other signals while waiting for SIGUSR1 static volatile sig_atomic_tsigreceived = 0; sigset_tmasknew, maskold; intsignum = SIGUSR1; sigprocmask(SIG_SETMASK, NULL, &masknew); sigaddset(&masknew, signum); sigprocmask(SIG_SETMASK, &masknew, &maskold); sigdelset(&masknew, SIGUSR1); while (sigreceived == 0) sigsuspend(&masknew); sigprocmask(SIGSETMASK, &maskold, NULL); The flag The signal I want to wait for get current add my signal to the current set set mask now SIGUSR1 is blocked remove SIGUSR1 from the set if flag is ok, wait for SIGUSR1 … it is unblocked by the sigsuspend call restore
System Calls and Signals Some system calls, like terminal I/O can block the process for a very long time. The capability is needed to be able to interrupt these system calls so that signals can be delivered during long waits for I/O. In POSIX.1, slow system calls that are interrupted return with -1, and errno set to EINTR. The program must handle this error explicitly and restart the system call if desired.
Remember the sigactionstruct …. structsigaction { void (*sa_handler) ( ); sigset_tsa_mask; intsa_flags; SA_RESTART Restart any system calls interrupted by this signal once the signal handler has finished
Slow System Calls are those that can block forever. These include * reads from files that block forever if no data is present. This includes pipes, network connections, and terminals. * Writes to the above if the data cannot be accepted. * opens of files that block until some condition occurs, for example waiting to open a terminal device until modem answers the phone. * pause * some interprocess communication functions
The following code segment restarts the read system call if interrupted by a signal. #include <sys/types> #include <sys/uio.h> #include <unistd.h> #include <errno.h> intfd, retval, size; char *buf; . . . while (retval = read(fd, buf, size), retval == -1 && errno == EINTR); if (retval == -1) { // handle errors here … } note the use of the comma operator: the left-hand expression is evaluated first then the second expression is evaluated and the result used as the condition for the while loop. that is, the read occurs. Then the return value is tested. If it failed because of an interrupt, the read is re-started.
see non-local exits in the GNU C library manual siglongjump and sigsetjump Used to unwind the call stack sigsetjump is like a statement label siglongjump is like a goto … it branches to the label set by sigsetjump It is not recommended that you use these