辅导案例-M3101
DUT Informatique
M3101 Système S3
2019 / 2020
Travaux Dirigés no 1 : Processes and signals
Objectives: know how to create Unix processes using the fork() system
call and how to synchronize them with the parent process using exit() and
wait(). Understand inter-process communication using signals.
1 Creation and identification of processes
In Unix systems, such as Linux and OS X, creation of a new process is done using the fork() system
call:
#include
pid_t fork(void);
This system call creates a new process, called “child process”, which executes the same code and
has the same memory content as the original, “parent” process. If fork() succeeds, it returns zero to
the newly created child process, and the (strictly positive) PID of the child to the parent process. If
fork() fails, it returns −1 to the calling process, and no new process is created.
The child process is an almost perfect clone of its parent. Besides the program code and the
memory content, the child process inherits from the parent its current directory, the open files, the
environment variables, the signal handlers (see below), and so on.
In the man 2 fork page, you will find the list of attributes that fork() does not pass from parent
to child. The first and foremost of them is, of course, the unique process identifier or PID. The parent
process learns the PID of the newly created child from the return value of fork(). To know its own
PID or the PID of its parent, a process can use the following primitives:
#include
pid_t getpid(void);
pid_t getppid(void);
2 Process termination
A process can finish its execution by calling the exit() function:
#include
void exit(int status);
The argument of exit() is the process’ exit status, an integer between 0 and 255. In the C language,
returning from the main() function (by executing return n) is equivalent to calling exit(n).
A process can also be terminated by a signal, sent by another process or by the system kernel
itself. This is called abnormal termination.
After the end of a process, its parent can obtain some details about the termination using the
wait() system call:
#include
pid_t wait(int *pointer_to_status);
Travaux Dirigés no 1 Processes and signals 2/8
Calling wait() puts the parent process into the waiting state until one of its children dies. Once
this happens, wait() returns the PID of the defunct process: we cannot know in advance who will
die, but we can know who died. If one or several child processes die before the parent calls wait(),
the syscall returns immediately. If there are no more children to wait for, wait() returns −1. If the
argument of wait is a non-NULL pointer to an integer, for example &status, then the kernel will put
at this address the reason of the termination (whether it was normal or abnormal), and either the exit
status, if the termination was normal (that is, caused by exit() or by returning from main()), or the
type of signal which terminated the process, if the termination was abnormal.
The macros WIFEXITED(status) and WIFSIGNALED(status) tell, respectively, whether the child
ended normally or was killed by a signal. In the former case, the macro WEXITSTATUS(status) gives
us the exit status. In the latter case, the macro WTERMSIG(status) gives us the number of the signal
which caused the termination. Read man 2 waitpid for a more generic version of wait().
A terminated process stays in the kernel process list in the “zombie” state until its parent (if it is
alive) retrieves the post-mortem information about its termination using wait() or waitpid(). If the
parent process dies before its children, the orphaned processes are “adopted” by the process 1, init,
which makes automatically the necessary wait() calls.
3 Signals
In Unix, signals are a mechanism to inform a process about some event in the system. Signals may
be sent for various reasons: they can be sent by the kernel as a result of an execution error (memory
overrun, forbidden instruction, etc.), they can be sent from the terminal ( or ) or
by the kill command, or else by the kill() system call from another process.
#include
int kill(pid_t target_process_pid, int signal_name_or_number);
The kill() system call takes two arguments: the PID of the target process and the number of the
signal to deliver. It returns 0 when successful, on −1 in the case of failure.
In the following table you will find some frequently occurring signals. For each of them, we
give its symbolic name (a macro that you can use as an argument of kill()), its number on the x86
architecture, and its effect on the receiving process by default.
Name No By default Remarks
SIGHUP 1 termination hang-up of the controlling terminal (e.g., death of xterm)
SIGINT 2 termination interrupt from the terminal ()
SIGKILL 9 termination immediate termination, cannot be caught or ignored
SIGSEGV 11 termination invalid or forbidden memory access (segmentation fault)
SIGPIPE 13 termination write to a pipe with no readers
SIGALRM 14 termination timer signal from alarm()
SIGTERM 15 termination immediate termination
SIGUSR1 10 termination user-defined signal
SIGUSR2 12 termination user-defined signal
SIGCHLD 17 ignored child process terminated or stopped
SIGCONT 18 continuation continue execution if stopped
SIGSTOP 19 stop immediate stop, cannot be caught or ignored
SIGTSTP 20 stop stop from the terminal ()
A process can either keep the default behaviour upon signal reception, or change how the signal
is treated: ignore it or call a special handling function. This is done using the signal() system call:
IUT d’Orsay – DUT Informatique 2019 / 2020 M3101 Système S3
Travaux Dirigés no 1 Processes and signals 3/8
#include
int signal(int signal_name_or_number, sighandler_t handler);
The handler argument should be one of the following
• the SIG_IGN macro to ignore the signal,
• the SIG_DFL to restore the default behaviour,
• the name of a C function to call whenever the signal is received. This function must take a
single argument of type int: the number of the signal being handled.
In certain versions of Unix (though not Linux), after one execution of the handler function the default
behaviour is restored. In order to keep the redefined behaviour, one should call signal() at the end
of the handler function. Read man 2 sigaction for a more generic and portable version of signal().
If the user installs a handler function, this function is called immediately whenever the signal
is received. After the handler function returns, the program execution continues from where it was
interrupted. If during the execution of a handler, the same signal arrives again, the handler function
is not interrupted. Any signal is considered to be blocked during the execution of its handler, and the
kernel will wait until the handler returns before calling it again. Notice that the system only registers
that a blocked signal has arrived but not the number of signals received. Thus, even if the signal was
received a hundred times while being blocked, the kernel will only re-execute the handler once. Read
man 7 signal for a detailed description of this mechanism.
In order to inform the parent process about the termination of one of its children, the system
sends him a signal SIGCHLD. Thus, one possible way to avoid zombie processes is to install a signal
handler for SIGCHLD which calls wait() any time the signal is received. Another possibility is to
execute signal(SIGCHLD,SIG_IGN); in the parent process: this instructs the kernel not to keep the
defunct children as zombies. In this case, a call to wait() will put the parent process in the waiting
state until all of its children terminate, and then return −1.
4 Exercises
Exercise 1: Who am I? Consider the following program (the #include lines are omitted).
1 int main () {
2 pid_t child = fork();
3 if (child == -1) { perror("fork() error"); exit(1); }
4 //printf("My PID is %d.\n", getpid()); // à ajouter pour la question (b)
5 if (child == 0) {
6 printf("Child process: my PID is %d.\n", getpid());
7 exit(0); // à enlever pour la question (b)
8 }
9 printf("Now my PID is %d.\n", getpid());
10 exit(0);
11 }
Question (a): What is the output of this program?
IUT d’Orsay – DUT Informatique 2019 / 2020 M3101 Système S3
Travaux Dirigés no 1 Processes and signals 4/8
Question (b): What is the output, if we add line 4 and remove line 7?
Exercise 2: Summing on a Sunday afternoon. Once upon a time there was Bob, a daring young
programmer who set to find out the captain’s age. To get the answer, he had to sum two integer
numbers: the number of sunny days in London in the year 2105 AD (between 0 and 127) and the
number of the 2nd year students who will come to IUT on 21/05 (between 0 and 127). As each of
this numbers required about 6 hours to compute, Bob decided to parallelize the work and wrote the
following program (the #include lines and the error handling are omitted).
int sunny_days_in_London(int year) { /* 6 heures de calcul */ }
int second_year_presence(int day) { /* encore 6 heures de calcul */ }
int main () {
int captains_age = 0;
pid_t cpid = fork();
if (cpid != 0) captains_age += sunny_days_in_London(2105);
else captains_age += second_year_presence(2105);
printf("Le capitaine a %d ans et mon PID est %d\n", captains_age, (int) getpid());
return 0;
}
Explain Bob’s mistake and propose a way to solve the problem using exit() and wait(). Detail
the actions of both processes.
IUT d’Orsay – DUT Informatique 2019 / 2020 M3101 Système S3
Travaux Dirigés no 1 Processes and signals 5/8
How long will take it to your program to compute the answer?
Exercise 3: Firefork(). If we run the following program, how many processes will it create?
int main(int argc, char ** argv) {
pid_t id;
int i, N = 0;
if (argc > 1) N = atoi(argv[1]);
for (i = 0; i < N; i++) {
id = fork();
if (id == -1) { perror("fork() error"); exit(1); }
printf("I am %d, son of %d.\n", getpid(), getppid());
}
printf("%d out.\n", getpid());
return 0;
}
Give a generic formula for the number of processes depending on N.
Exercise 4: world! Hello, Consider the following program with a hole in it:
int main () {
pid_t id = fork();
if (id == 0) {
printf("Hello, ");
exit(0);
}
// <-- TROU
printf("world!");
return 0;
}
We want to complete this program to make sure that the words are always printed in the right order,
independently of scheduling decisions.
The system call int sleep(int n) puts the program in the waiting state for n seconds. Can we
complete the program by calling sleep(1)? sleep(100)? Explain your answer.
IUT d’Orsay – DUT Informatique 2019 / 2020 M3101 Système S3
Travaux Dirigés no 1 Processes and signals 6/8
Can we complete the program with a wait() call? Substantiate your answer.
Exercise 5: To infinity in eight seconds. The following program increments a counter of type
unsigned int and stops after the maximal value is reached and the counter goes back to zero.
volatile unsigned int count = 0;
//
//
//
//
//
//
// <-- TROU 1
//
//
//
//
//
//
int main() {
//
//
//
//
//
//
// <-- TROU 2
//
//
//
//
//
//
for (count = 1; count > 0; count++);
return 0;
}
We declare the variable count as volatile to prevent compiler optimisation (which otherwise might
remove the loop entirely).
Complete the code so that count is reset to 1 when the process receives the SIGUSR1 signal.
Complete the code so that the increment changes sign when the process receives SIGUSR2.
IUT d’Orsay – DUT Informatique 2019 / 2020 M3101 Système S3
Travaux Dirigés no 1 Processes and signals 7/8
To ensure that our program behaves correctly, we want to watch the counter. A new system call
comes in handy:
#include
unsigned int alarm(unsigned int nb_sec);
The system call alarm() sets a timer that will send a SIGALRM signal to the calling process nb_sec
seconds later. Any alarm() call cancels and replaces the previously set alarm; alarm(0) cancels the
current alarm and does not set a new one.
Complete the code so that the current value of count is printed every second.
Exercise 6: Et mon courroux, coucou! Let us go back to Exercise 4. As we know, the parent
process receives the SIGCHLD signal whenever its child process terminates. Can we use this to ensure
the correct order of execution?
The primitive int pause(void) puts the calling process to sleep until it receives a signal which
is neither ignored nor blocked. Can we complete the program with a pause() call? Explain your
answer.
Can we complete the program with a pause() call and a simple handler for SIGCHLD, for example,
a function that does nothing?
Bob is not happy with our answers. What if we used our signal handler to avoid calling pause()
if the signal has already arrived? To do that, we would need a global “flag” variable:
volatile int flag = 0;
void handler_chld (int sig) { flag = 1; }
int main () {
signal(SIGCHLD, handler_chld);
if (fork() == 0) { printf("Hello"); exit(0); }
while (!flag) pause();
printf(" world");
return 0;
}
Why is it important to install the handler before calling fork()?
IUT d’Orsay – DUT Informatique 2019 / 2020 M3101 Système S3
Travaux Dirigés no 1 Processes and signals 8/8
Why is it important to test the flag in a while loop?
Does Bob’s program guarantee that the words are printed in the right order?
Exercise 7: One, two, many. Let us run the following program:
volatile int count = 0;
void handler(int sig) { count++; }
int main() {
int i;
signal(SIGUSR1, handler);
if (fork() == 0) {
for (i = 0; i < 256; i++)
kill(getppid(), SIGUSR1);
exit(0);
}
wait(NULL);
printf("Final: %d\n", count);
return 0;
}
What is the output of this program? Explain the answer.
IUT d’Orsay – DUT Informatique 2019 / 2020 M3101 Système S3
51作业君 51作业君

扫码添加客服微信

添加客服微信: IT_51zuoyejun