C:Workshop:2018:D2
Sommaire
Introduction
The purpose of this session is to introduce fork(2), wait(2) and execvp(2) by a series of examples.
Simple fork
Make it work
- Here is a simple example of code, headers have been removed, use manual pages to find them:
// basic_fork.c: simple example of fork /* * MESSAGE is a simple wrapper around printf so that printf call display the * PID in front of the message * Must have at least 2 arguments, NULL can be used for the empty case */ # define MESSAGE(fmt_, args...) printf("(%u): " fmt_, getpid(), args); int main() { int fork_ret, x = 0; fork_ret = fork(); if (fork_ret == -1) err(1, "Fork failed"); if (fork_ret) { MESSAGE("Inside parent\n", NULL); MESSAGE("Initial x: %d\n", x); x = -42; } else { MESSAGE("Inside child\n", NULL); MESSAGE("Initial x: %d\n", x); x = 42; } MESSAGE("parent id: %u\n", getppid()); MESSAGE("new x:%d\n", x); return 0; }
Run the code several time and observe the output.
Write your own
The purpose of this exercise is to launch two different algoritms on the same data using fork(2).
Here is the base code:
# define MESSAGE(fmt_, args...) printf("(%u): " fmt_, getpid(), args); void random_fill(int tab[], size_t len) { for (int *cur = tab; cur != tab + len; ++cur) *cur = random() % 1024; } void swap(int *a, int *b) { int c = *a; *a = *b; *b = c; } void sort01(int tab[], size_t len) { for (size_t i = 0; i < len; ++i) { size_t min = i; for (size_t j = i + 1; j < len; ++j) if (tab[j] < tab[min]) min = j; swap(tab + i, tab + min); } } void sort02(int tab[], size_t len) { int move = 1; for (size_t i = 0; i < len && move; ++i) { move = 0; for (size_t j = 0; j < len - i - 1; ++j) { if (tab[j] > tab[j + 1]) { move = 1; swap(tab + j, tab + j + 1); } } } } int is_sorted(int tab[], size_t len) { size_t j; for (j = 1; j < len && tab[j - 1] <= tab[j]; ++j) {} return j == len; }
Your goal is to write a program that:
- fill an array of random integers (using the function random_fill().)
- fork:
- the parent runs the algoritm sort01 on the array
- the child runs the algoritm sort02 on the array
- both processes (child and parent) verify the sorted array with (is_sorted) and write a message using (MESSAGE.)
- Write the full program, compile it an run it.
- Bonus question: name the algoritms sort01 and sort02
Waiting and time measure
We now want to run the previous algoritms (sort01 and sort02) and do some time measures.
Running and waiting
Our first step is to write the program that will fork and run the two algorithms. The program will follow this schema:
- Fill an array of random integers
- Do a first fork:
- the child runs sort01
- the parent wait (using wait(2)) for the death of its child
- Do a second fork:
- the child runs sort02
- the parent wait (using wait(2)) for the death of its child
Wall Clock Time
You'll need clock_gettime(2) to do time measures: we get the time before the fork and after the wait, the difference will give you the time of execution of the child (and thus the execution time of the algorithms.) To ensure a correct time measure, you'll need the option CLOCK_MONOTONIC.
CPU and system time
In order to get more advanced time information, we need to get access to the rusage data structure corresponding to the child. For that, you need to replace wait(2) with wait3(2).
The program
You can now write the main program. This program will display the execution time of each algorithm.
Replacing our Program
- Write the program runls.c that replace itself with the command ls -l
Running any command
Simple replacement
We now want to run any command, the idea is to use the argv array of our program. We want the following behavior:
> make runcmd gcc -Wall -Wextra -std=c99 -O3 runcmd.c -o runcmd > ./runcmd runcmd: Need a command > ./runcmd ls >>> running command <<< ls Makefile foo.c forkexec.c mytime.c runcmd runcmd.c runls.c > ./runcmd ls -l >>> running command <<< ls -l total 32 -rw-r--r-- 1 feanor feanor 118 19 janv. 18:52 Makefile -rw-r--r-- 1 feanor feanor 87 19 janv. 19:32 foo.c -rw-r--r-- 1 feanor feanor 367 19 janv. 18:55 forkexec.c -rw-r--r-- 1 feanor feanor 1489 19 janv. 19:44 mytime.c -rwxr-xr-x 1 feanor feanor 7723 22 janv. 09:01 runcmd -rw-r--r-- 1 feanor feanor 479 19 janv. 19:25 runcmd.c -rw-r--r-- 1 feanor feanor 219 19 janv. 18:52 runls.c > ./runcmd cat foo.c >>> running command <<< cat foo.c # include <stdlib.h> int main() { int *p = NULL; *p = 42; return 0; } >
- Write the program runcmd.c that take on its command line a program name and its arguments and run it using execvp
Using fork
We now want to extend the previous program using fork(2). The schema is pretty simple: we first fork a child process, in the child we perform the exec, while in the parent, we wait for the child termination. In order to see the parent waiting, you'll print an extra message after the child termination.
The output is pretty similar to the previous one:
> make runcmd gcc -Wall -Wextra -std=c99 -O3 runcmd.c -o runcmd > ./runcmd runcmd: Need a command > ./runcmd ls >>> running command <<< ls Makefile foo.c forkexec.c mytime.c runcmd runcmd.c runls.c >>> done <<< > ./runcmd ls -l >>> running command <<< ls -l total 32 -rw-r--r-- 1 feanor feanor 118 19 janv. 18:52 Makefile -rw-r--r-- 1 feanor feanor 87 19 janv. 19:32 foo.c -rw-r--r-- 1 feanor feanor 367 19 janv. 18:55 forkexec.c -rw-r--r-- 1 feanor feanor 1489 19 janv. 19:44 mytime.c -rwxr-xr-x 1 feanor feanor 7723 22 janv. 09:01 runcmd -rw-r--r-- 1 feanor feanor 479 19 janv. 19:25 runcmd.c -rw-r--r-- 1 feanor feanor 219 19 janv. 18:52 runls.c >>> done <<< > ./runcmd cat foo.c >>> running command <<< cat foo.c # include <stdlib.h> int main() { int *p = NULL; *p = 42; return 0; } >>> done <<< >
Time
We now want to implement a program similar to time(1): it runs a command and print timing information. We want the behavior of time -p. Here is an example of running time (note that we call it with full path to avoid using the shell version):
> cat ack.c # include <stdio.h> # include <stdlib.h> unsigned acker(unsigned m, unsigned n) { return (m?(n?(acker(m-1,acker(m,n-1))):acker(m-1,1)):n+1); } int main() { printf("%u\n", acker(3,12)); return 0; } > make ack gcc -Wall -Wextra -std=c99 -O3 ack.c -o ack > /usr/bin/time -p ./ack 32765 real 4.50 user 4.49 sys 0.00 >
How it work
In order to implement your version of time, you can start with the code of the previous exercise that fork/exec command passed as argument and extend it with time measure using clock_gettime(2) (for real time) before the fork and after the wait. In order to get user and sys time, you'll need to replace wait with wait3 in order to access the rusage structure of the child.
For the display, you can store your various time in variables of type double print them using the format %g in printf(3).
Status and execution failure
The time program leave with the same exit code as the one of its child (which can be obtain using wait3(2) and macros like WEXITSTATUS).
> cat exit42.c int main() { return 42; } > make exit42 gcc -Wall -Wextra -std=c99 -O3 exit42.c -o exit42 > ./exit42 > echo $? 42 > /usr/bin/time -p ./exit42 Command exited with non-zero status 42 real 0.00 user 0.00 sys 0.00 > echo $? 42
As you can see in this example, the command also print a message when the exit code is different from 0.
For execution failure, we also follow the behavior of time:
> /usr/bin/time -p toto /usr/bin/time: cannot run toto: No such file or directory Command exited with non-zero status 127 real 0.00 user 0.00 sys 0.00 > echo $? 127 > cat foo.c # include <stdlib.h> int main() { int *p = NULL; *p = 42; return 0; } > make foo gcc -Wall -Wextra -std=c99 -O3 foo.c -o foo > /usr/bin/time -p ./foo Command terminated by signal 4 real 0.18 user 0.00 sys 0.00
Your program must have the same behavior as time(1) for all these cases.
Program
- Write the program mytime that take its arguments as a command to be run and output the execution time following the format of time -p.
> make mytime gcc -Wall -Wextra -std=c99 -O3 mytime.c -o mytime > ./mytime ./ack 32765 real: 4.5098 user: 4.5 sys: 0 > ./mytime toto mytime: cannot run toto: No such file or directory Command exited with non-zero status 127 real: 0.000632002 user: 0 sys: 0 > echo $? 127 > ./mytime ./foo Command terminated by signal 4 real: 0.00936465 user: 0 sys: 0.003333 > echo $? 4 > ./mytime ./exit42 Command exited with non-zero status 42 real: 0.00198869 user: 0 sys: 0 > echo $? 42
Things you need:
- wait3(2) and associated macros (WIFEXITED, WEXITSTATUS, WIFSIGNALED and WTERMSIG)
- execvp
- fork