20150126:TP:C:Times:EN
Sommaire
Introduction
Our goal is now to use execvp to run programs from our C code.
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 clang -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 clang -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 clang -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 clang -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 clang -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 clang -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