CMPS-3600
Lab-2 - Introduction to the Fork System Call

Objectives:
write a program that uses system calls
fork(2), wait(2), write(2), open(2), close(2)

What do those numbers above mean?

Some resources

This lab is an introduction to Linux system programming, using the typical system calls that are needed when one process forks a new process.

Do this lab in your Odin directory: 3600/2

Sample source files are on Odin at: ~gordon/public_html/3600/examples/2/

Copy them with this series of commands...

   $ cd
   $ cd 3600/2
   $ cp ~gordon/public_html/3600b/examples/2/* .

Now see if the files build properly...

   $ make clean
   $ make

Note:
Your C code in this lab does not have to conform to the -ansi -pedantic compile flags.
But, you may code that way if you wish.

Before executing any code, read the source of each file to understand the system calls. Follow the instructions in each source to execute each program. Pay particular attention to the programs that ask you to run top and strace.

strace is a utility that traps system calls and signals. The system call is the fundamental interface between an application and the kernel.

See 'man syscalls' to know which calls are supported by the Linux kernel.

A signal is one means of communicating between processes. Strace writes to stderr which you generally redirect to a file like this:

   $ strace -f ./test 2>outfile

You can open outfile to view the calls and signals executed by your process The -f switch focuses on what occurs between the parent and child after fork.

Don't worry too much about understanding everything that strace spits out. You will understand more and more as we go.

The purpose now is to expose you to a powerful tool which you can use for debugging your own systems code later on in the course. For example, this runs strace on signals only:

   $ strace -e trace=signal ./test 2>outfile 

top is another powerful utility that you will frequently use in this course. One way to use top is to open one terminal and run top

   $ top -u [your_username]

and open a second terminal window to start your process:

   $ ./test

Or you could run your process in the background and start top in the same window. For example, fork3.c demonstrates how a forked child becomes a zombie. When a forked child dies it becomes a defunct process (zombie) until the parent harvests the child's exit code by calling wait(2). If the parent never calls wait(2) the zombie is left as an entry in the kernel's process table. If you leave a zombie you did not code properly.

You use strace and top to ensure that a zombie does not occur, like this:

   $ gcc -o fork3 fork3.c
   $ strace -q -f ./fork3  2>errfile

Next run fork3 in the background and run top to watch the fork and wait calls.

   $ ./fork3 6 >out &
   $ top -u [username]    # fork3 is listed twice - once for parent and
                          # once for child; 'Z' occurs when the child dies
   $ cat out log

When you see two fork3 processes running you know the parent called fork(2). When you see the defunct zombie process (in state "Z") you know the child has died and the parent has not yet harvested the child's exit code. When the zombie disappears you know its parent has called wait and harvested the code.

This final handoff between the child and its parent is the way things should happen. You cannot kill a zombie process since it isn't running but a zombie counts as one of the limited number of processes on a system.

A fork call should never leave a zombie.

To ensure a zombie doesn't happen you must call exit(code); in the child, declare an int status; variable before the fork, and call wait(&status) in the parent.

To ensure all is working the parent should display the child's exit code after returning from wait.

The final resource you need to familarize yourself with is the manpages. To read a manpage hit 'space' to continue, 'q' to quit or '/' followed by a string you wish to find and [return].

Read through the manpages for system calls fork(2), exit(2) and wait(2). Note that the '2' specifies the section your call is found in. If you omit the '2' you will get the wrong wait. For example,

   $ man wait     # POSIX wait command
   $ man 2 wait   # Linux programmer's manual system call

For system coding you need special header files. In general, if your code does not compile check that you have the correct headers. For this lab you will need these headers:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>       /* to write time                 */
#include <sys/file.h>   /* open system call              */
#include <unistd.h>     /* header file for the POSIX API */
#include <sys/wait.h>   /* for wait system call          */

After a fork, the child does not share data with its parent!

You must declare a status variable that is visible to both parent and child (it does not need to be global):

int status;

The value passed in the exit call in the child is the exit code, where 0 denotes exit with success. The value of the exit code is copied by the kernel to the address of status:

   exit(33);

33 is arbitrary - pass anything you wish for testing.

The wait() in the parent passes the address of status - for the kernel:

   wait(&status);   /* returns pid of child if all OK */

You must use predefined macros to test the value of status and convert the exit code into something you can display:

   if (WIFEXITED(status)) 
       printf("exited, status=%d\n", WEXITSTATUS(status));

Code to show date and time
#include <time.h>
time_t T;
time(&T);
printf("time: %s\n", ctime(&T));

See: fork3.c for more detail.

Lab-2 instructions
All the code you need to complete this lab is in the examples for this week:

fork1.c, fork2.c, fork3.c, and fork4.c.

Your job is to write a program rvlab2.c following these specifications:

Write a function fib(int n) in rvlab2.c that will return the nth term
for the Fibonacci sequence.

In this course, a Fibonacci sequence starts with 1 1.

1. In main immediately read the first command line argument past the filename.
Call atoi to convert the argument into an integer n, which you will pass later
as an argument for fib and use as the child's exit code.

If no arg is passed, exit with code 1, and write this usage statement to stdout:
"Usage: ./lab2 n"

Note: samples code for main function arguments:  alarm.c  fork-wait.c  fork3.c

2. After grabbing the cmd-line arg, call fork() to create a child process.

3. The child calls open(2) to open a file for writing named 'log'.

4. The child calls write(2) to write the date and time and result of fib(n) to log.

5. The child calls close(2) to close log file.

6. The child exits and passes n as its exit code.

7. After the fork, the parent immediately calls wait.

8. After wait returns the parent calls write to display the child's exit code
   to stdout (see fork3.c) Display of child's pid is optional.

9. The parent terminates with exit code 0.
Sample run of lab program...
gordon@odin:~/3600/2$ ./lab2

Usage: ./lab2 n

gordon@odin:~/3600/2$ 

Sample run of lab program...
gordon@odin:~/3600/2$ ./lab2 22

my child (19897) exited with code: 22

gordon@odin:~/3600/2$ cat log
2022:02:01 05:58:05
fib 22 = 17711
gordon@odin:~/3600/2$

Sample run using strace...
gordon@odin:~/3600/2$ make
gcc rvlab2.c -Wall -olab2
gordon@odin:~/3600/2$ strace -q -f -e trace=write ./lab2 21 2>err
my child (14788) exited with code: 21
gordon@odin:~/3600/2$ cat log
2022:02:01 05:54:35
fib 21 = 10946
gordon@odin:~/3600/2$ cat err
[pid 14788] write(3, "2022:02:01 05:54:35\n", 20) = 20
[pid 14788] write(3, "fib 21 = 10946\n", 15) = 15
[pid 14788] +++ exited with 21 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=14788,
         si_uid=10042, si_status=21, si_utime=0, si_stime=0} ---
write(1, "my child (14788) exited with cod"..., 38) = 38
+++ exited with 0 +++
gordon@odin:~/3600/2$ 


Files to be collected at end of lab period...
		
   rvlab2.c
   Makefile

Do your work in your Odin directory: 3600/2/
Do not change permissions on your 3600 files or folders.