450 likes | 658 Views
Signals. Operating Systems. What is a Signal?. A signal is a notification that some event has occurred. 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
E N D
Operating Systems What is a Signal? A signal is a notification that some event has occurred. 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.
Operating Systems 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.
Operating Systems Conditions that generate signals Most signals can be generated naturally for example, by a divide by zero error or a segmentation fault All signals can be generated synthetically by one of the following system calls: kill killpg pthread_kill raise sigqueue
Operating Systems Conditions that generate signals Five signals cannot be generated naturally SIGKILL SIGSTOP SIGTERM SIGUSR1 SIGUSR2 terminate immediately pause requests termination Signal names are defined in signal.h
Operating Systems Terminal generated signals: occur when users type certain keys on the keyboard. For example, Ctrl-C normally generates the signal SIGINT. Conditions that generate signals 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.
Operating Systems 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: * Ignore a signal - sigkill and sigstop cannot be ignored - ignored signals have no effect on the process * Catch the signal and call a function * Let the default apply - usually terminates the process
Operating Systems When you get a signal from a hardware detected error condition, you cannot 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 . . .
Operating Systems 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.
This set is required by Posix.1 Operating Systems Symbol Meaning SIGABRT abnormal termination, initiated by abort SIGALRM timeout signal, initiated by alarm SIGFPE arithmetic error, such as divide by zero SIGHUP hang up on controlling terminal SIGILL invalid hardware instruction SIGINT interactive attention signal (Ctrl-C) SIGKILL terminate ( cannot be caught or ignored ) SIGPIPE write on a pipe with no readers SIGQUIT interactive termination SIGSEGV invalid memory reference SIGTERM termination SIGUSR1 user defined SIGUSR2 user defined Some Common Signal Names default action for all of these signals is to terminate the process.
Operating Systems Symbol Meaning Default Action SIGCHLD child process has stopped ignore SIGCONT continue if stopped continue process SIGSTOP stop signal (can’t be caught/ignored) stop process SIGSTP interactive stop signal stop process SIGTTIN background process trys to read stop process SIGTTOU background process trys to write stop process Job Control Signals See the key sequences that generate signals on your system by typing stty -a See all of the signals the os supports by typing kill -l
Operating Systems 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, 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.
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 Generating Signals This is one of the signal names less the “sig” prefix Example: kill –s USR1 3423
from within a program #include <sys/types.h> #include <signal.h> int kill (pid_t pid, int sig ); Generating Signals returns 0 if successful if pid is positive, kill sends the signal sig to the process pid (must own the process) if pid is negative, kill sends the signal to the process group |pid| if pid is zero, kill sends the signal to members of the callers process group
Sounds grim, but ….. Killing a Parent Process #include <stdio.h> #include <unistd.h> #include <signal.h> if (kill(getppid( ) , SIGTERM) == -1) perror(“Error in kill 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. A process needs permission to send a signal to another process. The superuser can send a signal to any process. For others the basic rule is that the real or effective user id of the sender has to be the same as the real or effective user id of the receiver, i.e. you can only send signals to processes that you own...
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. * The Signal set int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int sig); int sigdelset(sigset_t *set, int sig); int sigismember(const sigset_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
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 sigprocmask ( ) #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.
this code adds SIGINT to the set of signals that the process currently has blocked. Example code #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
See ex1 on X-Server. 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.
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.
There are two issues to think about: Handling Signals What do you need to do inside of the signal handler to change the state of the application so it is known 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
Note that when you are 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 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, f); for (i = 0; i < 10; i++) { printf(“hello\n”); sleep(1); } return 0; } The signal handler void f ( int signum ) { printf(“\nOUCH”); }
int main ( ) { int i; signal ( SIGINT, f); for (i = 0; i < 10; i++) { printf(“hello\n”); sleep(1); } return 0; } install the signal handler void f ( int signum ) { printf(“\nOUCH”); }
int main ( ) { int i; signal ( SIGINT, f); for (i = 0; i < 10; i++) { printf(“hello\n”); sleep(1); } return 0; } hello hello hello ouch INTERRUPT (SIGINT) void f ( int signum ) { printf(“\nOUCH”); } look at the code
#include <signal.h> int sigaction (int sig, const struct sigaction *act, struct sigaction *old); struct sigaction { void (*sa_handler) ( ); // SIG_DFL, SIG_IGN, or pointer to function sigset_t sa_mask; // additional signals to be blocked while in handler int sa_flags; // flags and options } May be null Signal Handlers 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.
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 … Example 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
The following code segment sets up a signal handler that ignores SIGINT if it is using the Default handler for this signal. Example #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
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.
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. Waiting for signals #include <unistd.h> int pause ( ); the return value is not significant. pause suspends the process until a 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
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 … A problem with Pause 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.
… sigset_t sigset; int signum; sigemptyset(&sigset); sigaddset(&sigset, signum); sigprocmask(SIG_BLOCK, &sigset, NULL); while(signal_received == 0) pause(); … // signum is the signal we want to catch But … does this work? No! Now the signal is blocked when the pause statement is executed, so the program never receives the signal.
The delivery of signals with pause was a major problem in Unix. It was fixed by adding the sigsuspend operation. sigsuspend 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_t sigreceived = 0; sigset_t masknew, maskold; int signum = 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