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
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/3600/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.
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 sample 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.
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 lab2.c following these specifications: Write a function fib(int n) in lab2.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 a usage statement to stdout: "Usage: ./lab2 n"Sample run of lab program...Note: make sure the executable name in the Usage statement matches the actual executable name.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 the 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.
gordon@odin:~/3600/2$ ./lab2 Usage: ./lab2 <n> gordon@odin:~/3600/2$
gordon@odin:~/3600/2$ ./a.out Usage: ./a.out <n> gordon@odin:~/3600/2$
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$
gordon@odin:~/3600/2$ make gcc lab2.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$
lab2.c Makefile Do your work in your Odin directory: 3600/2/