Skip to main content
留学咨询

辅导案例-M3101

By May 15, 2020No Comments

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 exi
t(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); } // 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; // // // // // // // // // // // // // int main() { // // // // // // // // // // // // // 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

admin

Author admin

More posts by admin