You are on page 1of 8

CS 587 Advanced Operating Systems

Project # 2
Due Thursday, 09/27/2001
Jingyi Dong, Qingfeng Duan, Yasushi Watanabe
Project 2: Measuring Process Creation Time

Keywords: system call, process creation, fork, execv, exit, wait, gettimeofday, and accuracy

I. INTRODUCTION

This project is designed to measure the process creation time. In UNIX, a bunch of the system calls related
to the process operation are provided. The fork system call is used to create a new child process. The
parent process utilizes the wait system call to await the termination of its child process before exiting. By
calling the exit system call, the process terminates itself and closes all I/O streams. Additionally, when the
function execv is called in the child process, a new process will replace the child process thoroughly,
including process context, data, stack, and heap. The only thing unchanged is the process ID. In this
project, we are interested in investigating that how many processes could be created as the system allow,
how much time it will take to create a process, and how the measurement changes in case that a child
process execute a simple program. As usual, the accuracy of the measurements will be discussed.

II. EXPERIMENTS

Our experiments were implemented on CS machine gig-0 running Debian 2.2. There are mainly three
pieces of the tests. First, we design a program to create as many processes as the system allowed. In this
test, we postulate that after forked by a parent process, the new child process immediately calls exit system
call. In addition, the parent process needs to await the terminations of all of its children before exiting.
Secondly, we design a set of programs to time each fork + wait. The purpose of that is to compare the
accuracy of timings among these tests and check to see which one is most appropriate to do such tests. We
chose the gettimeofday system call to do timing tests since it is able to give the time measured in seconds
and microseconds. Finally, a slightly modified version of timing fork + wait are created. In this case, a
child process is allowed to use execv to run a very simple program. Note that each of these tests is
repeatedly at least ten times. To be clear, we give the pseudocodes of the tests as follows:

Table 1 Pseudocodes used for our tests

Test 2
Test 1 Test 3
Subtest 1 Subtest 2 Subtest 3
Counter:=0; Gettimeofday(); Gettimeofday(); Gettimeofday(); Gettimeofday();
While true: Fork(); Loop: First Loop: First Loop:
Fork(); IF child process: Fork(); Fork(); Fork();
IF child process: Exit(); IF child process: IF child process: IF child process:
Exit(); Wait(); Exit(); Exit(); Execv(); Exit();
ELSE IF error Gettimeofday(); Wait(); Wait(); Wait();
Break; End Loop;
ELSE Gettimeofday(); Fork(); Fork();
Wait(); IF child process: IF child process:
ENDIF Exit(); Execv(); Exit();
Counter++; Wait(); Wait();
End while loop End Loop End Loop
Gettimeofday(); Gettimeofday();

Gettimeofday(); Gettimeofday();
Second Loop: Second Loop:
Fork(); Fork();
IF child process: IF child process:
Exit(); Execv(); Exit();
Wait(); Wait();
End Loop End Loop
Gettimeofday(); Gettimeofday();

2
III. RESULTS & DISCUSSION

In test 1, we measure the number of the processes the system can create (See listing 1 on Appendices for
the source code of this test.) The results are tabulated in Table 2. We find that these numbers are constant
during the sequential tests.

Table 2 Numbers of the processes that the system can create obtained from Test 1

# of Tests 1 2 3 4 5 6 7 8 9 10
# of
254 254 254 254 254 254 254 254 254 254
processes

In test 2, timing each fork + wait, we did three subtests. The detailed source codes are referred to listings 2
– 4 on Appendices. In the first subtest, we just time one fork + wait (See listing 2 on Appendices). We
fork a child process at first and then wait for the termination of it. The child process exits immediately.
The averaged time we measured is 0.6595 milliseconds. If the required accuracy is in milliseconds to 1
decimal place, this result might be acceptable since the gettimeofday system call can measure in
microseconds. But if we want the accuracy to 1 millisecond, this result is not quite believable. We need to
loop this block of operations for a few times. This makes sense because of the following two folds of
reasons: 1) each block of operation (fork + wait) is independent of each other. In another word, the parent
does not fork another child process until it waits and assures the termination of the one that it just created.
This allows us to average the total loop times to get the time taken by each fork + wait without introducing
other overheads; 2) we use the numbers of the successful created processes obtained from test 1 as the loop
times. You can set the number as you like because in this test, it works in different way(not parallel, but
serial). In this subtest, we loop 254 times and the average time is 0.5004 milliseconds (See Table 3). Let
us consider the errors that would be implanted so far. The gettimeday system call will bring in the errors.
But this is hard-wired and we cannot get rid of it. The other errors might be from the execution of the
control loop. To get the results at most accuracy, we may need to eliminate such errors. This motivates us
to create the third subtest. In this subtest (See listing 4 on Appendices for source code), we have two for
loops. Both are set to iterate the identical times N, say N = 254. In the first loop, we placed two fork +
wait, whereas in the subsequent loop we just put one fork + wait there. We measure the times for the two
loops and finally get the differences of those two. More clearly, the total time of N fork + wait should be
equal to (Time of the first loop + 2*N*x) – (Time of the second loop + N*x), where x is assumed to be the
time taken by one fork + wait. The average time we measured as this way is 0.4768 milliseconds (See
Table 3), less than the one we measured in subtest2. This implies that the error due to the two for loops
will be 0.0236 milliseconds. This indicates that the error of measurement due to subtest2 is
(0.0236/2)/0.5004, which is approximately 2.4%.
Table 3: Times (in milliseconds) measured for each fork + wait during subtests in tests 2 and test 3

# of Tests 1 2 3 4 5 6 7 8 9 10 Average
Test2:
.65700 .68300 .67500 .62600 .65600 .63300 .66300 .64300 .69000 .66900 .6595
Subtest1
Test2:
.50754 .49461 .51426 .48383 .49550 .48810 .51034 .49539 .50261 .51194 .5004
Subtest2
Test2:
.47580 .47674 .48172 .47520 .47688 .47544 .47841 .47505 .47773 .47488 .4768
Subtest3
Test3 .50392 .48967 .49976 .49458 .50088 .48764 .49521 .50131 .49826 .50073 .4972

In test 3, we reuse the code in the subtest3 of test2. The only difference is that we now allow each child
process execute a benchmark program called fred (See listing 5 on Appendices.) The average time we
measured for this case is 0.4972 milliseconds (Table 3). It should be noted that this result is less than the
one we obtained in subtest3 of test 2. This is primarily because of the execution of the execv function in
the test 3. In test 2, the child process is designed to exit immediately and the memory is freed at the same
time. In test 3, however, the child process is supposed to execute a program before exiting. This certainly
takes amounts of time. Also, we note that the first measurement in the test 3 is larger than the subsequent
ones during the sequential executions of the same program. This is due to the caching in memory. As we
know, the fred load module is loaded into memory as the program in test 3 run at the first time. The

3
corresponding page table entries are then cached in TLB. Note that the program is executed sequentially,
therefor they tend to spatially access the nearby location they just accessed. This saved time since they do
not need to load fred over and over again. It should also be pointed here that we find some results may be
greater than the first measurement during some trials. We ascribe this to the system which allows the
multiple users who may cause the system under the different loading status.

IV. CONCLUSION

In this project, we have done a series of experiments. Through these tests, we could measure the number of
the processes the system can create and the time taken by each fork + wait in either case that each child
process exits immediately or executes a very simple program before exiting. We found that the number of
the created processes keeps constant under the same system. In addition, the time taken by each fork + wait
is obtained as accurately as the system allowed by eliminating the possible errors. The influences on the
timing measurements due to the execv function call were addressed too.

V. APPENDICES

Listing 1 nproc.cc

#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>

extern "C" {int wait(int *);}

int main() {

int pid_t, ret;


int counter = 0;

while(1) {

pid_t = fork();

// failure to fork
if (pid_t == -1) break;

// each child process immediately exits


if (pid_t == 0) exit(ret);

// count the total processes are created


counter++;

// awaits the termination of all child processes


for (int i=0; i <= counter; i++){
int pid = wait (&ret);
}

cout << "Totally there are " << counter


<<" processes were created on this system." << endl;

exit(0);

Listing 2 testime1.cc:

#include <iostream.h>
#include <iomanip.h>
#include <unistd.h>
#include <sys/time.h>

4
extern "C" { int wait(int *);}

int main()
{
int pid_t, pid, ret;
struct timeval tv1, tv2;
struct timezone tz1, tz2;
unsigned long total;

// fixed format for floating point I/O


cout.setf(ios::fixed, ios::floatfield);
cout.setf(ios::showpoint);
cout.precision(5);

// start time
gettimeofday(&tv1, &tz1);

pid_t = fork();
if (pid_t == 0) exit(ret);
pid = wait(&ret);

// end time
gettimeofday(&tv2, &tz2);

// total time used in microseconds


total = (tv2.tv_sec - tv1.tv_sec)*1000000 +
(tv2.tv_usec - tv1.tv_usec);

cout << "The time taken for each fork + wait is: "
<< total/double(1000) << " milliseconds. " << endl;

return 0;
}

Listing 3 testime2.cc:

#include <iostream.h>
#include <iomanip.h>
#include <unistd.h>
#include <sys/time.h>

extern "C" { int wait(int *);}

const int N = 254;

int main()
{
int pid_t, pid, ret;
struct timeval tv1, tv2;
struct timezone tz1, tz2;
unsigned long total;

// fixed format for floating point I/O


cout.setf(ios::fixed, ios::floatfield);
cout.setf(ios::showpoint);
cout.precision(5);

// start time
gettimeofday(&tv1, &tz1);

for (int i=0; i <= N; i++) {


pid_t = fork();
if (pid_t == 0) exit(ret);
pid = wait(&ret);
}

// end time
gettimeofday(&tv2, &tz2);

5
// total time used in microseconds
total = (tv2.tv_sec - tv1.tv_sec)*1000000 +
(tv2.tv_usec - tv1.tv_usec);

cout << "The time taken for each fork + wait is: "
<< total/(double(1000)*double(N)) << " milliseconds. " << endl;

return 0;
}

Listing 4 testime3.cc:

#include <iostream.h>
#include <iomanip.h>
#include <unistd.h>
#include <sys/time.h>

extern "C" { int wait(int *);}

const int N = 254;

int main()
{
int pid_t1, pid1, ret;
struct timeval tv1, tv2, tv3, tv4;
struct timezone tz1, tz2, tz3, tz4;
unsigned long FirstLoopTime, SecondLoopTime, total;

// fixed format for floating point I/O


cout.setf(ios::fixed, ios::floatfield);
cout.setf(ios::showpoint);
cout.precision(5);

// get the start time of 1st loop


gettimeofday(&tv1, &tz1);

for (int i=0; i <= N; i++) {


pid_t1 = fork();
if (pid_t1 == 0) exit(ret);
pid1 = wait(&ret);

pid_t1 = fork();
if (pid_t1 == 0) exit(ret);
pid1 = wait(&ret);
}

// get the end time of 1st loop


gettimeofday(&tv2, &tz2);

// get the time taken by first loop


FirstLoopTime = (tv2.tv_sec - tv1.tv_sec)*1000000 +
(tv2.tv_usec - tv1.tv_usec);

// get the start time of 2nd loop


gettimeofday(&tv3, &tz3);

for (int i=0; i <= N; i++) {


pid_t1 = fork();
if (pid_t1 == 0) exit(ret);
pid1 = wait(&ret);
}

// get the end time of 2nd loop


gettimeofday(&tv4, &tz4);

// get the time taken by second loop


SecondLoopTime = (tv4.tv_sec - tv3.tv_sec)*1000000 +
(tv4.tv_usec - tv3.tv_usec);

// the total time

6
total = FirstLoopTime - SecondLoopTime;

cout << "Time for first loop (ms): " << FirstLoopTime/double(1000) <<endl;
cout << "Time for second loop (ms): " << SecondLoopTime/double(1000) <<endl;
cout << "The actual time eliminating the influence of control loop (ms): "
<< total/double(1000) << endl;

cout << "The time taken for each fork + wait is: "
<< total/(double(1000)*double(N)) << " milliseconds. " << endl;

return 0;
}

Listing 5 fred.cc:

#include <iostream.h>

int main()
{
exit(0);
}

Listing 6 testexec.cc:

#include <iostream.h>
#include <iomanip.h>
#include <unistd.h>
#include <sys/time.h>

extern "C" { int wait(int *);}

const int N = 254;

int main()
{
int pid_t1, pid1, ret;
struct timeval tv1, tv2, tv3, tv4;
struct timezone tz1, tz2, tz3, tz4;
unsigned long FirstLoopTime, SecondLoopTime, total;

char *pathname = "./";


char *const argv[2] = {"fred",0};

// fixed format for floating point I/O


cout.setf(ios::fixed, ios::floatfield);
cout.setf(ios::showpoint);
cout.precision(5);

// get the start time of 1st loop


gettimeofday(&tv1, &tz1);

for (int i=0; i <= N; i++) {


pid_t1 = fork();
if (pid_t1 == 0) { execv(pathname, argv); exit(ret); }
pid1 = wait(&ret);

pid_t1 = fork();
if (pid_t1 == 0) { execv(pathname, argv); exit(ret); }
pid1 = wait(&ret);
}

// get the end time of 1st loop


gettimeofday(&tv2, &tz2);

// get the time taken by first loop


FirstLoopTime = (tv2.tv_sec - tv1.tv_sec)*1000000 +
(tv2.tv_usec - tv1.tv_usec);

// get the start time of 2nd loop

7
gettimeofday(&tv3, &tz3);

for (int i=0; i <= N; i++) {


pid_t1 = fork();
if (pid_t1 == 0) { execv(pathname, argv); exit(ret); }
pid1 = wait(&ret);
}

// get the end time of 2nd loop


gettimeofday(&tv4, &tz4);

// get the time taken by second loop


SecondLoopTime = (tv4.tv_sec - tv3.tv_sec)*1000000 +
(tv4.tv_usec - tv3.tv_usec);

// the total time


total = FirstLoopTime - SecondLoopTime;

cout << "Time for first loop (ms): " << FirstLoopTime/double(1000) <<endl;
cout << "Time for second loop (ms): " << SecondLoopTime/double(1000) <<endl;
cout << "The actual time eliminating the influence of control loop (ms): "
<< total/double(1000) << endl;

cout << "The time taken for each fork + wait is: "
<< total/(double(1000)*double(N)) << " milliseconds. " << endl;

return 0;
}

VI. REFFERNCES
- Operating System: a design-oriented approach, Charles Crowley, 1997.
- Linux online manual

You might also like