Instead, when the signal is sent to the process, the operating system stops the execution of the process, and "forces" it to call the signal handler function.. When that signal handler f
Trang 1Tirgul 2
Signals and communication between processes
What Are Signals?
Signals, to be short, are various notifications sent to a process in order to notify it
of various "important" events By their nature, they interrupt whatever the process
is doing at this minute, and force it to handle them immediately Each signal has an integer number that represents it (1, 2 and so on), as well as a symbolic name that
is usually defined in the file /usr/include/signal.h or one of the files included by it directly or indirectly (HUP, INT and so on Use the command 'kill -l' to see a list of signals supported by your system)
Each signal may have a signal handler, which is a function that gets called when the process receives that signal The function is called in "asynchronous mode", meaning that no where in your program you have code that calls this function directly Instead, when the signal is sent to the process, the operating system stops the execution of the process, and "forces" it to call the signal handler function When that signal handler function returns, the process continues execution from wherever it happened to be before the signal was received, as if this interruption never occurred
What Are Signals Used For?
Signals are usually used by the operating system to notify processes that some event occurred, without these processes needing to poll for the event
Trang 2Sending Signals Using the Keyboard
The most common way of sending signals to processes is using the keyboard There are certain key presses that are interpreted by the system as requests to send signals to the process with which we are interacting:
Ctrl-C
Pressing this key causes the system to send an INT signal (SIGINT) to the running process By default, this signal causes the process to immediately terminate
Ctrl-Z
Pressing this key causes the system to send a TSTP signal (SIGTSTP) to the running process By default, this signal causes the process to suspend
execution
Ctrl-\
Pressing this key causes the system to send a ABRT signal (SIGABRT) to the running process By default, this signal causes the process to
immediately terminate Note that this redundancy (i.e Ctrl-\ doing the same
as Ctrl-C) gives us some better flexibility
Sending Signals from the Command Line
Another way of sending signals to processes is done using various commands, usually internal to the shell:
kill
The kill command accepts two parameters: a signal name (or number), and a process ID Usually the syntax for using it goes something like:
kill -<signal> <PID>
fg
On most shells, using the 'fg' command will resume execution of the process (that was suspended with Ctrl-Z), by sending it a CONT signal
Trang 3Sending Signals Using System Calls
#include <unistd.h> /* standard unix functions, like getpid() */
#include <sys/types.h> /* various type definitions, like pid_t */
#include <signal.h> /* signal name macros, and the kill() prototype */
/* first, find my own process ID */
pid_t my_pid = getpid();
/* now that i got my PID, send myself the STOP signal */
kill(my_pid, SIGSTOP);
Catchable and Non-Catchable Signals
Some signals cannot be caught and handled by processes For example KILL which causes termination (from the shell: kill -9 …), STOP which causes
suspension (can later be resumed with a CONT signal)
Note STOP is not the signal generated by Ctrl+Z.
On the other hand, signals such as SEGV (notify of segmentation fault – meaning accessing illegal memory address) and BUS (notify of bus error – meaning
accessing memory address with invalid alignment) are both catchable.
Default Signal Handlers
There is a default signal handler for all signals For example the default handler for TERM is call the exit() system call.
The pause() System Call
the pause() system call causes the process to halt execution, until some signal is received.
The signal() System Call
usage: Signal( <signal number> , <pointer to function> );
Causes the next time the specified signal is received, to call the given function instead of the default function associated with that signal
Trang 4#include <stdio.h> /* standard I/O functions */
#include <unistd.h> /* standard unix functions, like getpid() */
#include <sys/types.h> /* various type definitions, like pid_t */
#include <signal.h> /* signal name macros, and the signal() prototype */
/* first, here is the signal handler */
void catch_int(int sig_num)
{
/* re-set the signal handler again to catch_int, for next time */
signal(SIGINT, catch_int);
/* and print the message */
printf("Don't do that\n");
}
/* set the INT (Ctrl-C) signal handler to 'catch_int' */
signal(SIGINT, catch_int);
/* now, lets get into an infinite loop of doing nothing */
while (true) {
pause();
}
Pre-defined Signal Handlers
SIG_IGN:
Causes the process to ignore the specified signal.
signal(SIGINT, SIG_IGN);
SIG_DFL:
Causes the system to set the default signal handler for the given signal (i.e the same handler the system would have assigned for the signal when the process started running): signal(SIGTSTP, SIG_DFL);
Example 1 – Process communication:
Trang 5/***************************************************************************/ /* This program demonstrates sending signals between processes: */ /* The 'father' process creates two 'son' processes Trying to compute the */ /* same value The first to finish exits and the 'father' 'kills' its */ /* slow 'son' by sending a signal */ /***************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/fcntl.h>
typedef struct _param {
int number;
int sleep_time;
} param;
//This function will be calld as a reaction to catching a signal
void catchSigT(int sig_num)
{
printf("pid %d operated catchSigT\n",getpid());
signal(SIGTERM, catchSigT);
};
void elegant_death(int sig_num)
{
printf("pid %d declares it's loss with dignity\n",getpid());
signal(SIGINT, catchSigT);
exit(0);
};
int pid1 = 1, pid2 = 1; //will hold the process id's of the sons
int main (char **argv, int argc)
{
param param1, param2;
param args;
int * stat;
int first,i;
long result;
param1.number = 10; //the number of times the first son will
preform the calculation
param1.sleep_time = 2; //the time that the first son will sleep in each comutation loop
param2.number = 10; //the number of times the second son will preform the calculation
param2.sleep_time = 7; //the time that the first son will sleep in each comutation loop
Trang 6
//defining the catch of SIGINT and SIGTERM to activate the 'elegent_death' & 'catch sig' function
signal(SIGTERM, catchSigT);
signal(SIGINT, elegant_death);
//forking the two sons
pid1 = fork();
if(pid1 != 0)
pid2 = fork();
//sons code
if(pid1 == 0 || pid2 == 0)
{
pause();//waiting for the father to begin the computation
//updating the pointer to the right structure
if(pid1 == 0)
args = param1;
else
args = param2;
//computing
result = 1;
for (i=1; i<args.number; i++)
{
result = result * i;
printf("Current result = %d from %d\n", result, getpid());
sleep(args.sleep_time);
}
printf("Final result =%d, given by pid %d\n", result, getpid());
exit(0);
return NULL;
}
sleep(1);//waiting for sons to pause
//waiking sons
kill(pid1,SIGTERM);
kill(pid2,SIGTERM);
printf("Created processes with pid 1 =%d and pid 2 =%d\n", pid1, pid2); first = wait(stat);//waiting for the first son to finish
//killing the son that did not finish
if(first == pid1)
{
kill(pid2, SIGINT);
printf("pid1: %d finished first and dad is killing pid2: %d\n",
pid1,pid2);
}
else
{
kill(pid1, SIGINT);
printf("pid2: %d finished first and dad is killing pid1: %d\n",
pid2,pid1);
}
}
Example1 output:
Trang 7pid 14876 operated catchSigT
Current result = 1 from 14876
pid 14877 operated catchSigT
Current result = 1 from 14877
Created processes with pid 1 =14876 and pid 2 =14877
Current result = 2 from 14876
Current result = 6 from 14876
Current result = 24 from 14876
Current result = 2 from 14877
Current result = 120 from 14876
Current result = 720 from 14876
Current result = 5040 from 14876
Current result = 40320 from 14876
Current result = 6 from 14877
Current result = 362880 from 14876
Final result =362880, given by pid 14876
pid1: 14876 finished first and dad is killing pid2: 14877
pid 14877 declares it's loss with dignity
Trang 8Example 2 – more complicated:
/
******************************************************************************
***/
/* Description : This program runs a few processes which send to each other
*/
/* signals using the kill command The signals activate the procedure
*/
/* sigCathcher which sends a signal to the next process Each child process */
/* exits after receiving the signal The parent collects all the zombies
*/
/* and then exits
*/
/
******************************************************************************
***/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/fcntl.h>
int cpid[5]; //holds the pids of the childs
int j; //pointer to cpid
int sigCatcher(){ // function to activate when a signal is caught
printf("PID %d caught one\n",getpid());
signal(SIGINT,sigCatcher); // reset the signal catcher
if(j>-1)
kill(cpid[j],SIGINT); //send signal to next child in cpid
}
int main(){
int i;
int zombie;
int status;
int pid;
signal(SIGINT,sigCatcher); // set the signal catcher to sigCatcher
for(i=0;i<5;i++){
if((pid=fork())== 0){ // create new child
printf("PID %d ready\n",getpid());
j=i-1;
pause(); // wait for signal
exit(0); // end proccess (become a zombie)
}
else // Only father updates the cpid array
cpid[i]=pid;
Trang 9}
sleep(2); // allow children time to enter pause
kill(cpid[4],SIGINT); // send signal to first child
sleep(2); // wait for children to become zombies
for(i=0;i<5;i++){
zombie = wait(&status); // collect zombies
printf("%d is dead\n",zombie);
}
exit(0);
}
Example 2 output:
PID 22899 ready
PID 22900 ready
PID 22901 ready
PID 22902 ready
PID 22903 ready
PID 22903 caught one
PID 22902 caught one
PID 22901 caught one
PID 22900 caught one
PID 22899 caught one
22903 is dead
22901 is dead
22902 is dead
22899 is dead
22900 is dead
execvp and Signal handlers
Since the process image is replaced when performing execvp, all functions
(including the signal handlers) will not exist in the new memory image Therefore execvp sets the signal handler of all signals back to default However, the bit that specifies if the signal should be ignore or not (if SIG_IGN was used) is not
changed, so signals which were ignored before the execvp will still be ignored after the execvp
Process Groups
Each process has an id (PID) and belongs to a group One of the processes in the group is the group leader, and all member’s group leader id (GID) are equal to his PID
int getpid() – return the process’s PID.
int getpgrp() – return the process’s GID.
setpgrp() – set this process’s GID to be equal to his PID.
setpgrp(int pid1, int pid2) – set process’s pid1 GID to be equal to pid2’s PID.
Trang 10Example 3:
#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
void cntl_c_handler(int dummy)
{
printf("\n%d caught SIGINT\n\n", getpid());
signal(SIGINT, cntl_c_handler);
}
main() {
int x; /* just to show that both will have it */
int pid; /* distinguish between father and son */
int pgrp, stat;
signal(SIGINT, cntl_c_handler);
x = 33;
pid = 1;
pid = fork();
if (pid == 0){ /* son */
signal(SIGINT, SIG_DFL); /* will not affect the father */
pgrp = getpgrp();
if(kill(pgrp, SIGCONT) == -1){ /* send to original father */
perror("problem with kill");
exit(1);
}
printf("Son - pid = %d group leader = %d father pid = %d\n", getpid(), getpgrp(), getppid());
setpgid(getpid(), getpid()); /* same as using setpgrp() */
printf("Son - pid = %d group leader = %d father pid = %d\n", getpid(), getpgrp(), getppid());
if(kill(-pgrp,SIGINT) == -1){ /* send to original fathers group */ perror("problem with kill");
exit(2);
}
printf("can I get here?\n"); /* i.e am I a member of the original fathers group? */
if(kill(-getpgrp(),SIGINT) == -1){
perror("problem with kill");
exit(3);
}
printf("can I get here now?\n");
}
else{
pause();
printf("Father - pid = %d group leader = %d \n", getpid(), getpgrp()); }
}
Trang 11Example 3 output:
Son - pid = 22879 group leader = 22878 father pid = 22878
Son - pid = 22879 group leader = 22879 father pid = 22878
can I get here?
22878 caught SIGINT
Father - pid = 22878 group leader = 22878
Process Groups and Signals
Signals sent from the keyboard (like by ctrl-C) are actually sent to the current foreground process group This means that if the current process performed fork, and his sons did not move to another group, then the signal will be received by both father and sons
The shell is a process which manages the user
processes It receives the command from the user, forks and has his son exec the process requested Therefore the shell must contain some code that will make sure signals (like by ctrl-C) will only effect the current foreground process, and not the shell or any of the background processes.
References:
Signals