C:Workshop:2018:D2

De wiki-prog
Aller à : navigation, rechercher


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