Tải bản đầy đủ (.doc) (11 trang)

Thực hành hệ điều hành 2 tín hiệu và truyền thông giữa các quy trình

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (123.02 KB, 11 trang )

Tirgul 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

1


Sending 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.

2


Sending 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> , );
Causes the next time the specified signal is received, to call the given function
instead of the default function associated with that signal.


3


Example:
#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:

4


/***************************************************************************/
/* 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;
preform the calculation.
param1.sleep_time = 2;
each comutation loop.
param2.number = 10;
preform the calculation.
param2.sleep_time = 7;
each comutation loop.

//the number of times the first son will
//the time that the first son will sleep in
//the number of times the second son will
//the time that the first son will sleep in

5


//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{
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:
pid 14876 operated catchSigT

6


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

7


Example 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
#include

#include
#include
#include
#include
#include
#include
#include

<string.h>
<stdio.h>
<stdlib.h>
<fcntl.h>
<sys/uio.h>
<sys/types.h>
<unistd.h>
<signal.h>
<sys/fcntl.h>

int cpid[5];
int j;

//holds the pids of the childs
//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;

8


}


}
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
PID 22902 caught
PID 22901 caught
PID 22900 caught
PID 22899 caught
22903 is dead
22901 is dead
22902 is dead
22899 is dead
22900 is dead


one
one
one
one
one

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.

9


Example 3:
#include
#include

#include
#include
#include

<stdio.h>
<sys/fcntl.h>
<sys/types.h>
<signal.h>
<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){
perror("problem with kill");
exit(1);
}

/* send to original father */

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){
perror("problem with kill");
exit(2);
}
printf("can I get here?\n");
fathers group? */

/* send to original fathers group */

/* i.e. am I a member of the original

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());

}

10


Example 3 output:
Son - pid = 22879 group leader = 22878
Son - pid = 22879 group leader = 22879
can I get here?

father pid = 22878
father pid = 22878

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

11



×