Signals and Signal Handling Signals A predefined message sent between two processes or from the kernel to a process, or by a user to a process A software version of a hardware interrupt It is a primitive form of interprocess communication Signals are asynchronous A process may receive a signal at any time during its execution It must be prepared to respond to the signal at that time Most are assigned an integer value (starting with 1) and a symbolic name SIGKILL You can see all the defined signals by executing the command man signal.h 9 /* Kill, unblockable (POSIX). */ Signals Users send signals with the keyboard ^c SIGINT 2 /* Interrupt */ To see the mappings of your keystrokes, enter the command stty -a The kill command utility kill [-signal] pid The default signal sent is SIGTERM (15) You may use either the integer value or the symbolic name of the signal If after using kill pid, the process doesn't terminate, then use kill -9 pid The kill( ) system call Signals Each signal type has associated with it an action which the kernel will perform on behalf of a process when the process is sent that signal Process can Perform the default action for that signal, such as Terminate the process Suspend the process but don't terminate it Ignore the signal The process will be unaware that the signal has occured Two signals cannot be ignored SIGKILL - Terminates a process – SIGSTOP - suspends a process Catch and handle the signal Execute a pre-arranged signal handler – A process can register a special signal-handling function It will be called when the signal is received by the process kill() system call kill() system call: send any signal to a process group or process int kill(pid_t pid, int sig); sig is the signal to send (either integer value or symbolic name) pid specifies where to send the signal pid > 0 :The signal is sent to the process whose PID is pid pid = 0 :The signal is sent to all processes whose GID is the same as the GID of the sender pid = -1 :The signal is sent to all processes that the current process may send signals to If superuser: The signal is sent to all processes except init (PID 1) sig = 0 No signal is actually sent, but all error checking is performed This can be used to see if process pid is still alive RETURN VALUE On success (at least one signal was sent), zero is returned On error, -1 is returned, and errno is set appropriately Example See kill_ex.c on webpage under Code Output > ./kill_ex child 1 is alive child 2 is alive child 1 is alive child 2 is alive child 1 is alive child 2 is alive Parent is about to suspend child 1 child 2 is alive child 2 is alive child 2 is alive Parent is about to resume child 1 child 1 is alive child 2 is alive child 1 is alive child 2 is alive child 1 is alive child 2 is alive Parent is about to terminate both children alarm() alarm( ) system call Each process has an alarm clock timer associated with it which it can use to send itself SIGALRM signals unsigned int alarm(unsigned int seconds); E.g. alarm(3) means send the calling process a SIGALRM signal 3 seconds later If seconds is 0, any currently scheduled alarm is switched off When alarm( ) is called, any previously scheduled alarms are cancelled alarm( ) returns The number of seconds remaining until any previously scheduled alarm was due to be delivered Or zero if there was no previously scheduled alarm Include <unistd.h> pause() pause( ) library function Suspends the calling process until it receives a signal The signal must be one that is not currently set to be ignored by the calling process If the signal is caught and doesn't cause termination -1 is returned errno is set to EINTR If the signal causes termination of the calling process pause( ) does not return Example alarm(3); while(1) { pause(); printf(“Signal Received”); // will be printed when SIGALRM is received. } signal() To ignore or catch a signal, we need to associate a signal-catching routine with that signal signal( ) system call sighandler_t signal(int signum, sighandler_t handler); Include <signal.h> signum is the value of the signal to be caught You may also use the symbolic name for the signal It cannot be SIGKILL or SIGSTOP handler is a pointer to a function This is the function (handler) that is executed if signal signum is received It may be a user-defined function Or SIG_DFL - the default action for the signal Or SIG_IGN - ignore this signal The return value is the signal's previous handler Example: signal_ex.c /* This program demonstrates ignoring the SIGINT. Follow the directions in the printf statements to play with the program */ #include <stdio.h> #include <signal.h> main() { void (*oldHandler)(int); /* To hold old handler value */ printf("I can be Controled-C'ed\n"); sleep(3); oldHandler = signal(SIGINT, SIG_IGN); /* Ignore Control-C */ printf("I'm protected from Control-C now\n"); sleep(3); signal(SIGINT, oldHandler); /* Restore old handler */ printf("I can be Control-C'ed again\n"); sleep(3); printf("Bye!\n"); } signal_ex.c The line void (*oldHandler)(int); declares oldHandler to be a function that takes one int as an argument and returns a void pointer Output: > ./a.out I can be Controled-C'ed ^C > ./a.out I can be Controled-C'ed I'm protected from Control-C now ^CI can be Control-C'ed again ^C > User-defined signal handlers Use the handler to execute any code that you want when the signal is received Usually the first line in a hander is another signal( ) statement This reestablishes the connection between the handler and the signal This is necessary because most signals are reset to take their default action after being caught and before executing the handler If you don't immediately execute another signal( ) statement, then you can't catch another signal of the same type Note that there is a small time frame where if a signal is received, the default action will occur This is what makes signals an unreliable communication mechanism Example: signal_ex2.c /* This program demonstrates using a signal handler to catch SIGINT. Notice that the connection between the signal handler and signal is reestablished immediately up enter the handler. To use: 1. As soon as it is running, hit multiple ^c's then <ret> 2. The signal handler counts the number of ^c's hit and prints out the total 3. The default signal handler is then reestablished 4. Hit another ^c and the program ends */ Example: signal_ex2.c #include <stdio.h> #include <signal.h> #include <unistd.h> int ctrl_c_count = 0; void (* old_handler)(int); void ctrl_c(int); main() { int c; old_handler = signal(SIGINT, ctrl_c); while ((c = getchar())!='\n'); printf("ctrl-c count = %d\n", ctrl_c_count); (void) signal(SIGINT, old_handler); for (;;); } void ctrl_c(int signum) { (void) signal(SIGINT, ctrl_c); ++ctrl_c_count; } output > ./a.out ^C^C^C ctrl-c count = 3 ^C > sigaction( ) system call int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); used to associate an action with a signal signum is the number of the signal to handle May be a symbolic name May not be SIGKILL or SIGSTOP act is a structure that holds the pointer to the name of the handler to associate with the signal oldact holds the pointer to the routine that was the signal's previous handler One important difference between sigaction and signal The signal handling routine stays installed even after a signal has been caught So you don't have to reset the signal handler in the signal handling routine struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; } sa_handler - the name of the handler sa_sigaction - do not use for now sa_mask - gives a mask of signals which should be blocked during execution of the signal handler flags - modify the behavior of the signal handling process See man pages for details. – SA_ONSTACK – SA_RESETHAND – SA_NODEFER – SA_RESTART – SA_SIGINFO – SA_NOCLDWAIT – SA_NOCLDSTOP sigaction_ex.c #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> int ctrl_c_count = 0; void ctrl_c(int); main() { int c, i; struct sigaction new_action; struct sigaction old_action; new_action.sa_handler = ctrl_c; new_action.sa_flags = 0; sigaction( SIGINT, &new_action, &old_action ); for ( i = 0; i < 3; i++ ) { while ((c = getchar())!='\n'); printf("ctrl-c count = %d\n", ctrl_c_count); } sigaction( SIGINT, &old_action, NULL ); for (;;); } void ctrl_c(int signum) { ++ctrl_c_count; } output > ./a.out ^C^C ctrl-c count = 2 ^C^C^C^C ctrl-c count = 6 ^C^C^C^C ctrl-c count = 10 ^C > sigprocmask( ) system call Each process has a signal mask which is a set of signals that are currently blocked from delivery If a blocked signal is sent to a process anyway It is added to a set of pending signals for the process It will be delivered when the block is removed int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); changes the list of currently blocked signals how indicates how the list of signals in set should be treated SIG_BLOCK The set of blocked signals is the union of the current set and set SIG_UNBLOCK The signals in set are removed from the current set of blocked signals SIG_SETMASK The set of blocked signals is set to set oldset contains the previous value of the signal mask Example: sigprocmask_ex.c In this example, I will use SIGUSR1 and SIGUSR2. The definition of these in signal.h is: Name SIGUSR1 SIGUSR2 Value Default Event 16 Exit User Signal 1 17 Exit User Signal 2 #include <stdio.h> #include <signal.h> #include <unistd.h> sigset_t new_signals; int main() { void signal_catcher( int ); // forward declaration of handler struct sigaction new_action; sigemptyset( &new_signals ); // Empties the set of blocked signals sigaddset( &new_signals, SIGUSR1 ); // Adds SIGUSR1 to the set of // blocked signals sigprocmask( SIG_BLOCK, &new_signals, NULL ); // Unions the set of process // blocked signals with new_signals new_action.sa_handler = signal_catcher; new_action.sa_flags = 0; /* Associate the handler with SIGUSR2 */ sigaction( SIGUSR2, &new_action, NULL ); printf( "Waiting for signal\n" ); pause( ); // Sleep until a signal is received printf( "Done\n" ); return 0; } void signal_catcher( int n ) { printf( "Received signal %i will release SIGUSR1\n", n ); sigprocmask( SIG_UNBLOCK, &new_signals, NULL ); printf( "SIGUSR1 released!\n" ); } Output > ./a.out & [1] 21551 > Waiting for signal kill -USR1 21551 > ps PID TTY TIME CMD 21339 pts/17 0:00 tcsh 21558 pts/17 0:00 ps 21329 pts/17 0:00 csh 21551 pts/17 0:00 a.out > kill -USR2 21551 Received signal 17 will release SIGUSR1 > ps PID TTY TIME CMD 21339 pts/17 0:00 tcsh 21329 pts/17 0:00 csh 21559 pts/17 0:00 ps [1] + User signal 1 ./a.out Pending signal SIGUSR1 can now terminate the process sigsuspend( ) system call int sigsuspend(const sigset_t *mask); Is used to suspend a process It replaces the current signal mask with mask The process is suspended until a signal is delivered whose action is to Execute a signal-catching function Or terminate the process If the action is to terminate the process, sigsuspend() does not return. If the action is to execute a signal catching function, sigsuspend() returns after the signal catching function returns. On return, the signal mask is restored to the set that existed before the call to sigsuspend(). sigsuspend_ex.c /* This program demonstrates using sigsuspend To use: 1. a.out & 2. kill -USR1 %1 3. kill -INT %1 4. Then immediately issue a jobs command */ #include <stdio.h> #include <signal.h> #include <unistd.h> int main() { void signal_catcher( int ); // forward declaration of handler struct sigaction new_action; sigset_t no_sigs, blocked_sigs, all_sigs; sigfillset( &all_sigs ); // Turn all bits on sigemptyset( &no_sigs ); // Turn all bits off to mean no signals blocked sigemptyset( &blocked_sigs ); // Initialize new_action.sa_handler = signal_catcher; new_action.sa_mask = all_sigs; new_action.sa_flags = 0; /* Associate the handler with SIGUSR1 */ sigaction( SIGUSR1, &new_action, NULL ); sigaddset( &blocked_sigs, SIGUSR1 ); sigprocmask( SIG_SETMASK, &blocked_sigs, NULL ); while( 1 ) { printf( "Waiting for SIGUSR1 signal\n" ); sigsuspend( &no_sigs ); } printf( "Done\n" ); return 0; } void signal_catcher( int n ) { printf( "Beginning important stuff...\n" ); sleep( 10 ); printf( "Ending important stuff\n" ); } Output > ./a.out & [1] 21674 > Waiting for SIGUSR1 signal kill -USR1 21674 Beginning important stuff... Ending important stuff Waiting for SIGUSR1 signal kill -INT 21674 > ps PID TTY 21339 pts/17 TIME CMD 0:00 tcsh 21683 pts/17 0:00 ps 21329 pts/17 [1] + Interrupt 0:00 csh ./a.out Example: alarm_ex.c /* This program demonstrates the alarm( ) system call */ #include <stdio.h> #include <signal.h> int alarmFlag = 0; /* alarm flag */ void alarmHandler(); /* Forward declaration of alarm handler */ main() { signal(SIGALRM, alarmHandler); /* Install signal handler */ alarm(3); /* Schedule an alarm signal in 3 seconds */ printf("Looping....\n"); while(!alarmFlag) /* Loop until flag set */ { pause(); /* Wait for signal */ } printf("Loop ends due to alarm signal\n"); } void alarmHandler() { printf("An alarm clock signal was received\n"); alarmFlag = 1; } output > ./a.out Looping.... An alarm clock signal was received Loop ends due to alarm signal > Example: sigchld_ex.c /* This program allows a user to limit the amount of time that a command takes to execute. The first parameter to sigchld.out is the maximum # of seconds that is allowed for execution, and the remaining parameters are the command itself. STEPS: 1. Parent process installs a SIGCHLD handler 2. Parent forks a child process to execute the command 3. Parent sleeps for specified number of seconds. When it wakes up, it sends the child process a SIGINT to kill it 4. If child terminates before its parent wakes up, the parent's SIGCHLD handler is executed, causing the parent to terminate immediately. To use: a.out <num of secs> <command> */ #include <stdio.h> #include <stdlib.h> #include <signal.h> int delay; void childHandler(); main(int argc, char* argv[]) { int pid; signal(SIGCHLD, childHandler); /* Install death-of-child handler */ pid = fork(); /* Duplicate */ if (pid == 0) /* Child */ { execvp(argv[2], &argv[2]); /* execute command */ perror("sigchld"); /* Should never execute */ } else /* parent */ { sscanf(argv[1], "%i", &delay); /* Read delay from command line */ printf("delay is: %i\n", delay); sleep(delay); /* Sleep for the specified number of seconds */ printf("Child %i exceeded limit and is being killed\n", pid); kill(pid, SIGINT); /* Kill the child */ } } void childHandler() { int childPid, childStatus; childPid = wait(&childStatus); /* Accept child's termination code */ printf("Child %i terminated within %i seconds\n", childPid, delay); exit (0); } output > ./a.out 2 ls delay is: 2 a.out sigaction_ex.c sigsuspend_ex.c alarm_ex.c sigchld_ex.c signal_ex.c sigpending_ex.c signal_ex2.c Child 21804 terminated within 2 seconds > ./a.out 2 sleep 4 delay is: 2 Child 21806 exceeded limit and is being killed > sigprocmask_ex.c