signals and signal handling. signals a predefined message sent between two processes or from the...
TRANSCRIPT
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 9 /* Kill, unblockable (POSIX). */
You can see all the defined signals by executing the command man signal.h
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 comminication 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
>
sigsaction( ) 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
Here is a list of flags on brunel. 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 Value Default Event SIGUSR1 16 Exit User Signal 1 SIGUSR2 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 TIME CMD
21339 pts/17 0:00 tcsh
21683 pts/17 0:00 ps
21329 pts/17 0:00 csh
[1] + Interrupt ./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 signal_ex.c sigpending_ex.c sigsuspend_ex.c
alarm_ex.c sigchld_ex.c signal_ex2.c sigprocmask_ex.c
Child 21804 terminated within 2 seconds
> ./a.out 2 sleep 4
delay is: 2
Child 21806 exceeded limit and is being killed
>