1.Definition
Linux provides a mechanism to
interrupt a running program. Some of the interrupts are the result of user
programs doing something bad, and some of the interrupts are external events that
are intending to get the attentions of a running program.
Numerous conditions can generate a signal:
- Memory segment violations : Process attempts to access a memory address that does not belong to it. SIGSEGV signal
- Hardware exceptions generate signals: devide by 0 (detected by hardware and the kernel is notified. The kernel then generates the appropriate signal for the process that was running at the time condition occurred)
- Software conditions can generate signals when a process should be notified of various events: Ex SIGURG (generated when out-of-band data arrives over a network conditions).
- From user space or from some other process when someone calls a function like kill()
- When you send the signal from the process to itself using a function like abort()
- When a child process exits the operating system and then sends the SIGCHLD signal to its parent process.
- When user interrupts program from the keyboard, SIGINT is sent.
- When the program behaves incorrectly, one of SIGILL, SIGFPE, SIGSEGV is delivered.
2. The Wide World of Signals
Every signal has a name, it starts with SIG and ends with a description.
Name
|
Number
|
Default
Action |
Description
|
SIGHUP
|
1
|
Exit
|
Hangup (ref termio(7I))
|
SIGINT
|
2
|
Exit
|
Interrupt (ref termio(7I))
|
SIGQUIT
|
3
|
Core
|
Quit (ref termio(7I))
|
SIGILL
|
4
|
Core
|
Illegal Instruction
|
SIGTRAP
|
5
|
Core
|
Trace or breakpoint trap
|
SIGABRT
|
6
|
Core
|
Abort
|
SIGEMT
|
7
|
Core
|
Emulation trap
|
SIGFPE
|
8
|
Core
|
Arithmetic exception
|
SIGKILL
|
9
|
Exit
|
Kill
|
SIGBUS
|
10
|
Core
|
Bus error--a misaligned
address error
|
SIGSEGV
|
11
|
Core
|
Segmentation fault, an address
reference boundary error
|
SIGSYS
|
12
|
Core
|
Bad system call
|
SIGPIPE
|
13
|
Exit
|
Broken pipe
|
SIGALRM
|
14
|
Exit
|
Alarm clock
|
SIGTERM
|
15
|
Exit
|
Terminated
|
SIGUSR1
|
16
|
Exit
|
User defined signal 1
|
SIGUSR2
|
17
|
Exit
|
User defined signal 2
|
SIGCHLD
|
18
|
Ignore
|
Child process status changed
|
SIGPWR
|
19
|
Ignore
|
Power fail or restart
|
SIGWINCH
|
20
|
Ignore
|
Window size change
|
SIGURG
|
21
|
Ignore
|
Urgent socket condition
|
SIGPOLL
|
22
|
Exit
|
Pollable event (ref
streamio(7I))
|
SIGSTOP
|
23
|
Stop
|
Stop (cannot be caught or
ignored)
|
SIGTSTP
|
24
|
Stop
|
Stop (job control, e.g., CTRL-z))
|
SIGCONT
|
25
|
Ignore
|
Continued
|
SIGTTIN
|
26
|
Stop
|
Stopped--tty input (ref
termio(7I))
|
SIGTTOU
|
27
|
Stop
|
Stopped--tty output (ref
termio(7I))
|
SIGVTALRM
|
28
|
Exit
|
Virtual timer expired
|
SIGPROF
|
29
|
Exit
|
Profiling timer expired
|
SIGXCPU
|
30
|
Core
|
CPU time limit exceeded (ref
getrlimit(2))
|
SIGXFSZ
|
31
|
Core
|
File size limit exceeded (ref
getrlimit(2))
|
SIGWAITING
|
32
|
Ignore
|
Concurrency signal used by
threads library
|
SIGLWP
|
33
|
Ignore
|
Inter-LWP signal used by
threads library
|
SIGFREEZE
|
34
|
Ignore
|
Checkpoint suspend
|
SIGTHAW
|
35
|
Ignore
|
Checkpoint resume
|
SIGCANCEL
|
36
|
Ignore
|
Cancellation signal used by
threads library
|
SIGLOST
|
37
|
Ignore
|
Resource lost
|
SIGRTMIN
|
38
|
Exit
|
Highest priority real-time
signal
|
SIGRTMAX
|
45
|
Exit
|
Lowest priority real-time
signal
|
- Exit: forces the process to exit.
- Core: forces the process to exit and create a core file.
- Stop: stops the process.
- Ignore: ignores the signal; no action taken.
Signals are classic example of asynchronous events. They occur at what appear to be random times to the process. When the signal occurs, the process has to tell the kernel what to do with it. There can be three options through which a signal can be disposed:
- The signal can be ignored: This means that nothing will be done when signal occurs
- The signal can be caught : The process registers a function with kernel, this function is called by kernel when that signal occurs
- Let the default action apply: Every signal has a default action (may be to terminate process or ignored etc)
- SIGKILL and SIGSTOP can not be ignored or caughted. This because these two signals provide a way for root user or kernel to kill or stop any process in any situation.
- In a process, action of a signal can be modified by call exec function, it change signal's action to be the default.
- Processes running in background are not terminated by a user pressing a Ctrl + C (because this is purpose of making a process run in background)
3. Signals from the command Line
- “kill” command is used to send any kind of signal from terminal end to a process identified by their pid.
- “killall” sends the signal to all processes running a specified command.
Examples:
- kill -HUP 512 // send a hangup signal to a shell running on a different terminal with process id 512
- killall -HUP inetd // to tell inet program to re-read it’s configuration option
4. Handling and Generating Signals
a. Handling Signals (Catching Signals)
Programs can handle signals using the signal library
function.
void (*signal(int sig, void (*func)(int)))(int);
Where:
- sig: the signal to be caught or
ignored
- func: the function to be call when
the specified signal is received, func: can be a function created by user or one of 2 below defined
types.
SIG_IGN
|
Ignore the signal.
|
SIG_DFL
|
Restore default behavior
|
Examples: with SIGINT signal when we type Ctrl-C. When a signal arrives, the process is interrupted, the current registers are saved, and the signal handler (or func) is invoked. When the signal handler returns, the interrupted activity is continued.
#include <signal.h> #include <stdio.h> #include <unistd.h> void ouch(int sig) { printf("OUCH! - I got signal %d\n", sig); (void) signal(SIGINT, SIG_DFL); } int main() { (void) signal(SIGINT, ouch); while(1) { printf("Hello World!\n"); sleep(1); } }
Note:
Functions in handler should be reentrant: A reentrant function is a function whose execution can be
stopped in between due to any reason (like due to interrupt or signal) and then
can be reentered again safely before its previous invocations complete the
execution.
Suppose a function func() is registered for a call back on a signal occurrence. Now
assume that this func() was already in execution when the
signal occurred. Since this function is call back for this signal so the
current execution on this signal will be stopped by the scheduler and this
function will be called again (due to signal).
The problem can be if func() works on some global values or data structures that are
left in inconsistent state when the execution of this function was stopped in
middle then the second call to same function(due to signal) may cause some
undesired results.
Example: Non-reentrant function scanf()
#include <stdio.h> #include <signal.h> #include <stdlib.h> void handler(int no){ printf("\n\n------------ We are at handler function------------------\n"); printf("Enter your number = "); int p; scanf("%d", &p); printf("\nMy number at handler function is %d\n",p); } int main() { signal(SIGINT, handler); printf("Get number = "); int s; scanf("%d",&s); printf("\n--------------- Get back at main function------------------!\n"); printf("My number at main function = %d\n", s); return 0; }
$./testSignal
Get number =
Press: Ctrl+C
------------------------------- We are at handler function------------------------------------
Enter your number = 1
My number at handler function is 1
2
-------------------------------- Get back at main function-------------------------------------!
My number at main function = 0
b. Generating Signal (Sending Signals)
A program can send a signal defined by sig argument to a different program indentified by pid argument by using the kill() system call with prototype.The call will be fail if the program does’t have permision to send the signal.
- int kill (pid_t pid, int sig); // return 0 if success, -1 on error
- int raise(int sig); // sends the signal sig to the excuting programe
Another example with SIGALRM: child process sends a SIGALRM to parent process. and parent process alarm itself.
#include <sys/types.h> #include <signal.h> #include <stdio.h> #include <unistd.h> static int alarm_fired = 0; void ding(int sig) { printf("Ding -------%d.\n", alarm_fired); alarm_fired++; } int main() { pid_t pid; printf("\n\nAlarm application starting:\n"); pid = fork(); switch(pid) { case -1: perror("fork failed!"); return(1); case 0: sleep(5); kill(getppid(), SIGALRM); return(0); default: printf("Parent PID = %d\n", getpid()); } printf("Waiting for alarms to go off.\n"); (void) signal(SIGALRM,ding); alarm(2); pause(); pause(); printf("\nDone\n\n\n"); return(0); }
Output:
Alarm application starting:
Parent PID = 3128
Waiting for alarms to go off.
Ding --------0.
Ding --------1.
Done
5. Sigaction (reliable signal)
a. Reasons why we should use sigaction() instead of signal()
In early versions:
b. Sigaction function:
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); // return 0 on success and -1 if not
Where sigaction structure used to define the action to be taken on receipt of the signal specified by sig and have following members:
My process id = 2723.
Parent: My process id = 2723.
Child: My process id = 2724.
Child: My process id = 2724.
SIGCHLD. mypid = 2723, mygid = 2723
NOTE: Signal only provide a primitive means of communication. In particular, there is no way to attach any information to them and the recipient only knows the signal number (to add more information,we use pipes or socket..)
Parent caught signal 2
Child caught signal 2
Parent caught signal 3
Child caught signal 3
Parent caught signal 4
Child caught signal 4
Another example of SIGUSR1 and SIGUSR2 in IPC
- The signal() function does not (necessarily) block other signals from arriving while the current handler is executing.
- The signal()function (usually) resets the signal action back to SIG_DFL (default) for almost all signals. This means that the signal()handler must reinstall itself as its first action. It also opens up a window of vulnerability between the time when the signal is detected and the handler is reinstalled during which if a second instance of the signal arrives, the default behavior (usually terminate, sometimes with prejudice - aka core dump) occurs.
- While processing a signal, we can get another signal. Also, there are times where it is very awkward dealing with signals.
To deal with this problems we use sigaction() instead of signal(). However, the interface of sigaction() is undeniably more fiddly.
b. Sigaction function:
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); // return 0 on success and -1 if not
- The sigaction function sets the action associated with the signal sig (except sig = SIGKILL, SIGSTOP)
- If oact is not null, sigaction writes the previous signal action to the location it refers to.
- If act is null, this is all sigaction does.
- If act isn't null, the action for the specified signal is set.
Where sigaction structure used to define the action to be taken on receipt of the signal specified by sig and have following members:
- void (* sa_handler) (int)/* function called when signal sig is received
- void (*sa_sigaction)(int , siginfo_t *, void * ) // Some architectures a union is involved.
- sigset_t sa_mask // a mask of the Signals that are blocked while processing this Signal
- int sa_flags /* signal action modifiers: Ex: with sa_flags = SA_SIGINFO, sa_sigaction should be set instead of sa_handler
c. Signal Sets
These sets are used in sigaction() and other functions to modify process behavior on receipt of signals.
These sets are used in sigaction() and other functions to modify process behavior on receipt of signals.
include <signal.h>
- int sigaddset(sigset_t *set, int signo); // add a signal specified by signo
- int sigemptyset(sigset_t *set); // initializes a signal set to be empty
- int sigfillset(sigset_t *set); // initializes a signal set to contain all defined signals
- int sigdelset(sigset_t *set, int signo); // delete a signal specified by signo
- int sigismember(sigset_t *set, int signo); // determines whether the given signal is a member of a signal set
Example: Catch SIGCHLD signal when child process has stopped or exited, SIGINT when we press Ctrl+C
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void HandleSignal(int sig, siginfo_t *si, void *context) // this is sa_sigaction function with three arguments. { switch(sig) { case SIGINT: printf("\nControl-C was pressed: mypid = %d, mypgid = %d\n",getpid(), getpgid(getpid())); // getpgid() get the process group ID of a process _exit(0); break; case SIGCHLD: printf("\nSIGCHLD. mypid = %d, mypgid = %d\n",getpid(), getpgid(getpid())); if(si->si_code == CLD_EXITED || si->si_code == CLD_KILLED) { printf(" Process %d is done!\n", si->si_pid); } break; } } int main(int argc, char *argv[]) { struct sigaction sVal; pid_t myPID; pid_t myG_PID; // Specify that we will use a signal handler that takes three arguments // instead of one, which is the default. sVal.sa_flags = SA_SIGINFO; // Indicate which function is the signal handler. sVal.sa_sigaction = HandleSignal; myPID = getpid(); myG_PID = getpgid(myPID); printf("\nMy process id = %d.\n", myPID); printf("My process group id = %d.\n", myG_PID); if(fork() == 0) { myPID = getpid(); myG_PID = getpgid(myPID); printf("\nChild: My process id = %d.\n", myPID); printf("Child: My process group id = %d.\n", myG_PID); // Create a new process group that contains this process setpgid(0,0); myPID = getpid(); myG_PID = getpgid(myPID); printf("\nChild: My process id = %d.\n", myPID); printf("Child: My process group id = %d.\n", myG_PID); } else { // Register for SIGINT sigaction(SIGINT, &sVal, NULL); // Register for SIGCHLD sigaction(SIGCHLD, &sVal, NULL); myPID = getpid(); myG_PID = getpgid(myPID); printf("\nParent: My process id = %d.\n", myPID); printf("Parent: My process group id = %d.\n", myG_PID); while(1) { } } return(0); }
Output:
./sigaction1
My process id = 2723.
My process group id = 2723.
Parent: My process id = 2723.
Parent: My process group id = 2723.
Child: My process id = 2724.
Child: My process group id = 2723.
Child: My process id = 2724.
Child: My process group id = 2724.
SIGCHLD. mypid = 2723, mygid = 2723
Process 2724 is done!
^C
Control -C was pressed: mypid = 2723, mygid = 2723
6. SIGUSR1, SIGUSR2
With these signals, we can do any thing which we want, it guarantees that the system will never deliver them to our process. Besides, there is no difference between these two signals.
They can be designed for simple inter-process communication (IPC), for example: a child process could send it to its parent to indicate that it's
completed a certain task, for instance.
NOTE: Signal only provide a primitive means of communication. In particular, there is no way to attach any information to them and the recipient only knows the signal number (to add more information,we use pipes or socket..)
The following example code demonstrates a parent and child alternately scheduling each other with SIGUSR1.
// Code
#include <unistd.h> #include <signal.h> #include <stdio.h> #include <sys/types.h>
int ntimes=0; main() { pid_t pid, ppid; void p_action(int); void c_action(int); static struct sigaction pact, cact; /* set SIGUSR1 action for parent */; pact.sa_handler = p_action; sigaction(SIGUSR1, &pact, NULL); switch (pid = fork()) { case -1: // Shouldn't get here perror("synchro"); _exit(1); case 0: // child cact.sa_handler = c_action; sigaction(SIGUSR1, &cact, NULL); // get parent process-id ppid = getppid(); for (;;) { sleep(1); // wake up parent kill (ppid, SIGUSR1); pause(); // wait for parent signal } default: // parent for (;;) { pause(); // wait for child signal sleep(1); // wake up child kill (pid, SIGUSR1); } } } // end of main void p_action(int sig) { printf("Parent caught signal %d\n", ++ntimes); } void c_action(int sig) { printf("Child caught signal %d\n", ++ntimes); }
Result:
Parent caught signal 1
Child caught signal 1
Parent caught signal 2
Child caught signal 2
Parent caught signal 3
Child caught signal 3
Parent caught signal 4
Child caught signal 4
Another example of SIGUSR1 and SIGUSR2 in IPC
// Code p1.c
#include <stdio.h> #include <signal.h> #include <time.h>
/* Global variables! We need these so that main() and sh1 & sh2 can communicate. */ char sym = 'X'; FILE *fp = NULL; void sh1(int st) { signal(SIGUSR1,sh1); fscanf(fp,"%c",&sym); } void sh2(int st) { fp = fopen("foo","r"); setbuf(fp,NULL); } int main() { /* Register signal handlers. */ signal(SIGUSR2,sh2); signal(SIGUSR1,sh1); setbuf(stdout,NULL); while(1) printf(" %c",sym); return 0; }
// Code p2.c
#include <stdio.h> #include <signal.h> #include <unistd.h>
int main(int argc, char **argv) { /* Get p1's process ID. */ pid_t pid = atoi(argv[1]); /* Open foo for unbuffered writing. */ FILE *fp = fopen("foo","w"); setbuf(fp,NULL); /* Tell p1 to open foo. */ kill(pid,SIGUSR2); /* Read char, write to foo, notify p1. */ char c; while(scanf("%c",&c) == 1) { if (c == '\n') continue; fprintf(fp,"%c",c); kill(pid,SIGUSR1); } /* Delete file foo & kill p1. */ unlink("foo"); kill(pid,SIGTERM); return 0; }
Explanation
it will
consist of two programs. Program
p1
will run in one terminal, printing
X's endlessly. In another terminal, we'll launch program p2
with p1's PID as a command-line
argument. Whenever we enter a new symbol as input to p2, it will write that
symbol to a file foo
and signal p1 with signal SIGUSR1. In
response to receiving SIGUSR1, p1's signal handler will read a character from
file foo and make that character the one that it prints. We also need to signal
p1 when file foo
is ready to be opened, since it can't
successfully open it until p2 creates it. We use signal SIGUSR2 to indicate
that foo is ready for p1 to open.
References:
[1] http://www.thegeekstuff.com/2012/03/linux-signals-fundamentals/
[2] http://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal
[3] http://www.ibm.com/developerworks/library/l-reent/[4] http://man.yolinux.com/cgi-bin/man2html?cgi_command=sigaction
[5] http://www.thegeekstuff.com/2011/02/send-signal-to-process/
No comments:
Post a Comment