You will turn in a log file of your activities. You should come
up with your solutions in advance, then use "cat" to show your programs,
then compile them, and run your programs enough times to demonstrate that
they work (altering the input if appropriate). Any answers to questions
can appear as in the output, i.e.
printf("I observe ....");
or you
can type your answers into files and use "cat" to show them. It is a good
idea to have a different program for each part, even though there might
be code copied from one to another. Don't forget to add the appropriate
"include" statements at the top of the programs.
Tasks:
The tasks are fork, sleep, wait, exec, kill, and exit, explained below.
Part 1 -- fork
By now you have seen examples of the fork() command, and know that it
creates a copy of the process. After the fork, there is a parent process
(the one that started originally) and a child process (the one created
by the fork command). To get started, you will make a small program that
includes
the fork command. You can look at the code in your Unix book and/or the
examples provided by your professor.
Have it examine the arguments passed by the command line. If the user passes "-h", it should print out some help information, then exit with a code of 0. This should happen before the fork, so that the program does not fork when "-h" is passed. In the event that the user passes "-h" with or without other things, your program should print the help information and quit, regardless of where on the command line it appears. Show that this works.
Have your program remember its PID, then use the fork command, then determine if it (the process) is the parent or the child. The child and the parent will have different PIDs. From this point, the parent and the child each should have their own block of commands.
The parent process should output that it is the parent, and what its PID is. For example, it should output "parent: PID 1234, starting". All further output from the parent should include "parent:" and its PID, along with whatever it is going to communicate. After this, it should output that it is going to wait for the child process. Then it should call the "wait" command. (An example of the wait command is in part 4.) Finally, it should output that it is exiting. It should then call the exit command, passing back 0.
The child process should output that it is the child, and what its PID is. For example, it should output "child: PID 5678, starting". All further output from the child should include "child:" and its PID, along with whatever it is going to communicate. The child process should output that it is going to sleep for 2 seconds. Have the child process call the "sleep" command, and sleep for 2 seconds. Next, have the child process output that it is exiting. It should then call the exit command, passing back 0.
What this does is create two processes, having them do two different things after the fork. The parent should wait for the child to exit, to avoid creating a zombie process. So far, this program does not do much, but it does provide a template for what you will use in the next parts.
Part 2 -- exec
Make a new program, calling it
lab14_part2a.c or something equally good.
Include comments indicating what it will do.
In this part, you will have the child process "exec" another program. There are several variants of the exec function, and this page from Oracle has some good information. We will use "execlp". It passes a list of arguments, and includes the path in the file name.
Remember to include the headers as needed.
#include <sys/errno.h> // for errno
#include <unistd.h> // for execlp
To get started, have it do this:
execlp("/usr/bin/grap", "grap", "he", "aminegg.txt", NULL);
printf("Could not exec program. Errno is %d\n", errno);
perror("Lab 14");
exit(1);
If you do not have the text file, you can
download aminegg.txt here.
Compile your program and run it.
...And the execlp call does not work. But you did just verify that the errno, perror, and exit calls work as expected. This is a good thing; when you test your programs, you should test its response if unexpected things happen. For example, it could be that the program you expect to run from "/usr/bin" is actually in a different directory.
Make a copy of the program from above, calling it
lab14_part2b.c or something equally good.
You probably see what is wrong in the "execlp" call; there is a "grep"
utility, but no "grap" utility.
Change the execlp call to the following:
execlp("/usr/bin/grep", "grep", "he", "aminegg.txt", NULL);
Then compile and run this version.
Questions: What is the "execlp" command doing in this code?
What is the equivalent command from the terminal prompt?
Part 3 -- signals SIGINT and SIGUSR1
In this part, you will implement another program of your own.
Call it "infinite_loop.c" or something equally good.
In it, have it do the following:
// Infinite loop!
while (1) {
printf("I'm in an infinite loop!\n");
sleep(2);
}
Obviously, this program has an infinite loop.
The sleep command helps, since it slows down the output to one line every
couple of seconds.
Test it out, and make sure that it works as expected.
Press control-C to stop it.
Next, add code to the infinite loop program.
There are two pieces. One piece is a couple of functions called "respond2INT"
and "respond2USR1", and each simply
prints a message and exits. The other piece goes in the main function,
and sets up "respond2INT" to be called whenever the program gets a SIGINT,
or interrupt, signal.
It also sets up "respond2USR1" to be called whenever the program gets a SIGUSR1
signal, which is a general-purpose signal that you can use.
When you press control-C, a SIGINT signal is
sent to the running process. What these modification do is set up your
own function to be called instead. This way, it could handle some
clean-up operations, such as closing any open files.
We will use SIGINT as an easy way to test it from the command line.
We will use SIGUSR1 as an easy way to test it in part 4.
Note that you will need to include "signal.h".
#include <signal.h>
void respond2INT() { // interrupt routine
printf("respond2INT: Received INT signal. Exiting.\n");
exit(0);
}
void respond2USR1() { // interrupt routine
printf("respond2USR1: Received USR1 signal. Exiting.\n");
exit(0);
}
And in the main function of the infinite loop program,
before the infinite loop, add the following:
// Set up SIGINT to call respond2INT()
// and SIGUSR1 to call respond2USR1()
signal(SIGINT, respond2INT);
signal(SIGUSR1, respond2USR1);
Instead of the "signal" command, it is possible to use
sigaction, which gives you more control.
Test out the respond2INT() with control-C,
and make sure that it works as expected.
That is, run the program like before, and press control-C to stop it.
We will test out respond2USR1()
in part 4.
Part 4 -- signal (kill)
This part of the lab puts everything together. Call your program lab14_part4.c or something equally good, and include the code from parts 1 and 2. It should fork. You will need to store the child's PID in variable "childProcess". Then the child process should run the program with the infinite loop by using the execlp function. The parent process will sleep for 10 seconds, then send a SIGUSR1 signal to the child, and wait for the child process to exit. Then it will exit, too.
Copy the following include statement to the top.
#include <sys/wait.h> // for wait
Copy the following code to the parent section.
sleep(10);
kill(childProcess, SIGUSR1);
int child_status;
wait(&child_status);
Test it out, and see that it works.
By the way, it is possible to test out the SIGUSR1 from the command line.
(You do not need to do this for this lab.)
You would need to either run the infinite looping program in the background,
or have more than one terminal. After launching the infinite looping program,
the "ps" command run on the other terminal gives the PIDs of running
processes. You would then locate the process by the command name,
then use "kill -USR1 PID". For example,
cascade> ps
PID TTY TIME CMD
989 ttys000 0:00.22 -bash
1163 ttys001 0:00.65 -bash
23543 ttys001 0:00.00 ./a.out
6891 ttys002 0:00.02 bash
cascade> kill -USR1 23543
In this case, the process is "a.out". Since it has PID 23543, that is
what is passed to the "kill" command. Note that the PID changes every
time the program runs.
Criteria:
The ultimate goal is to create programs that solve the problems.
Each task should have a test associated with it, to verify that it works.
When all tasks have working solutions, they should all work together to
solve the problem. Keep in mind that we will check to see what tasks
are completed, and if your program does not completely
solve the problem, it will still
receive partial credit.
When you are ready to turn this in, remember to follow the the directions on turning in labs/homeworks.