By the time you have completed this lab, you should be able to
As usual. If your regular partner is more than 5 minutes late, ask the TA to pair you with someone else for this week.
This lab's pilot should log in. You will (both) work in this account for the rest of the lab.
Create a ~/cs32/lab04 directory and make it your current directory:
mkdir ~/cs32/lab04 cd ~/cs32/lab04
Then copy forkplay.cpp from the class account as follows:
cp ~cs32/labs/lab04/forkplay.cpp ~/cs32/lab04
Type your name(s) and the date in a comment at the top of the file, as you will edit it in later steps.
ps
(process status)The ps
command is used to report a snapshot of the current processes. Try it
without any optons first:
bash-4.3$ ps PID TTY TIME CMD 1039 pts/44 00:00:00 bash 20985 pts/44 00:00:00 ps
What does PID mean? What is the PID of your current shell (e.g., 1039 in the sample above)?
Now try it with the -l
(ELL) option like this:
bash-4.3$ ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 25472 1039 1018 0 80 0 - 2527 wait pts/44 00:00:00 bash 0 R 25472 25168 1039 0 80 0 - 658 - pts/44 00:00:00 ps
Lots more information, including PPID. Hmmm ... what do UID and PPID mean? Verify that UID is the same for both processes, and the PPID of the ps command matches the PID of the shell. Isn't that comforting?
The -e
option is used to have ps
show information for every process
on the system, which of course gives even more information! Try it, and also find out the effects
of combining options: try -el, -ef, -eF, and -ely, and be ready to let the TA know the effects
of each of these option combinations if you are asked later.
By the way, to just see the first 10 lines (and thus column titles), you can "pipe" the ps command through the head program, like this:
bash-4.3$ ps -el | head
pstree
and top
The pstree
command is used to display a tree of all the processes
on the system. Here is the "head" of an example pstree on csil:
-bash-4.3$ pstree | head systemd-+-ModemManager-+-{gdbus} | `-{gmain} |-NetworkManager-+-dhclient | |-{gdbus} | `-{gmain} |-abrt-dump-journ |-abrt-watch-log |-abrtd---{gdbus} |-accounts-daemon-+-{gdbus} | `-{gmain} -bash-4.3$
It is a family tree, showing parent processes and their children. The process named systemd is the only one without a parent - it is the "init" system used by our Linux distribution.
The top
program provides a dynamic real-time view of a running system.
The name, top, is an acronym for Table of Processes. This command
can display system summary information as well as a list of tasks
currently being managed by the Linux kernel. The types of system summary
information shown and the types, order and size of information displayed
for tasks are all user-configurable and that configuration can be made persistent
across restarts.
Try top
now:
top
You need to use <Ctrl-C> or type "q" to stop the top program. If you type "u" and a user name, then the list will include only that user's processes. Typing "h" is a way to learn about other top commands.
If you want to run a process in the background, add "&" after the command. Then you may continue working while the program executes - so only do it for programs that don't require user input. Trying to find a file in a large directory structure, for instance, does not require user input and is a job that probably will take a long time to run, so it might as well run in the background. For now, to see how background processes work, play with the sleep command to just "do nothing" for a specified number of seconds, 3 for example:
-bash-4.3$ sleep 3
Did you try it? Okay, now sleep for 5 seconds in the background. Then hit <Enter> a few times times while it runs - you could be entering other commands if necessary - until the system tells you the job is done:
-bash-4.3$ sleep 5 & [1] 8333 -bash-4.3$ -bash-4.3$ -bash-4.3$ -bash-4.3$ [1]+ Done sleep 5
The PID of the sample job above is 8333, but the other number in brackets [1] is
the job number. Start sleep
in the background again, and use the jobs
command while it is running:
-bash-4.3$ sleep 5 & [1] 17349 -bash-4.3$ jobs [1]+ Running sleep 5 &
You can use the job number with fg
(e.g., `fg 1
`) to bring a particular
job to the foreground. Similarly, use bg
to send a stopped foreground job to the
background. Also, you can use <Ctrl-Z> to send any foreground process to the background.
By the way, if you want to run a command immune to hangups, with output to a
non-tty, you can use the nohup
command (see p. 131 in the Reader).
kill
The kill
command is used to interrupt a process with a signal to terminate.
The process to be signaled is identified by its PID or by '%#', where # is the job number.
Both ways are shown in this sample:
-bash-4.3$ sleep 1000 & [1] 6895 -bash-4.3$ sleep 2000 & [2] 8016 -bash-4.3$ jobs [1]- Running sleep 1000 & [2]+ Running sleep 2000 & -bash-4.3$ kill 8016 [2]+ Terminated sleep 2000 -bash-4.3$ jobs [1]+ Running sleep 1000 & -bash-4.3$ kill %1 -bash-4.3$ [1]+ Terminated sleep 1000
Sometimes the process won't respond to the signal. In that case, use the '-9
'
option, which is also known as the "sure kill" option (e.g., the second job in the sample
above would be terminated by `kill -9 8016
`).
Start and kill some background processes like the samples above, until you are comfortable with the concepts.
fork()
to create child process and execute commands from itHave you switched partner roles yet?
Now let's create a child process from an existing process with a C++ program, and execute
the ps
command from the child process. First look at
forkplay.cpp that you copied from the lab directory. It contains
the most fundamental example of creating a child process. Try to understand it - compile
and run it if necessary to figure it out.
After you understand how forkplay.cpp works, then your job is to change it to let the child
process execute the ps
shell command with the -l
option, as in
the following execution at the shell prompt:
-bash-4.3$ ps -l
The exec family of functions are used to replace the current process image with a new process image. There shall be no return from a successful exec, because the calling process image is overlaid by the new process image. The <unistd.h> library (often automatically included) defines the following:
extern char **environ; int execl(const char *path, const char *arg0, ... /*, (char *)0 */); int execv(const char *path, char *const argv[]); int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/); int execve(const char *path, char *const argv[], char *const envp[]); int execlp(const char *file, const char *arg0, ... /*, (char *)0 */); int execvp(const char *file, char *const argv[]);
Here is a link to example exec calls (and other details about the exec functions).
This time choose the execl
("lower case L") function, which we think
is the easiest one among them. But if you want to try other ones, it is also okay.
The following is how to ask the child process to run as a ps
command with
the -l
option:
execl("/bin/ps", "ps", "-l", (char*)0);
Put this line of code in the child branch, right after the call to showIDs
.
That's it. The execl
function will replace the current child process
image with the new ps
process image. Try to compile and execute it, to
see the program result. Ask a TA to help if you get stuck. By the way, read the output
of ps
too. Do you notice which process has the child's PID now?
It's okay to switch partner roles more than once you know.
Now let's learn something about zombie processes. First of all, what is a zombie process? One definition is like the following (from Wikipedia, 4/25/2017):
On Unix and Unix-like computer operating systems, a zombie process or defunct process is a process that has completed execution but still has an entry in the process table. ... The term zombie process derives from the common definition of zombie - an undead person. In the term's metaphor, the child process has "died" but has not yet been "reaped". Also, unlike normal processes, the kill command has no effect on a zombie process.
Actually, the current version of forkplay.cpp has a potential bug - it can create a zombie process. To witness this problem, first uncomment the line in the parent's branch to make the parent sleep for 10 seconds. Then recompile the program, but don't run it yet.
Above you learned how to use top
command. Now open
another shell window, then type 'top' at the new terminal prompt. See how many
zombie processes exist currently, and leave the top command running:
don't stop it, but monitor it while you work in the original shell window.
Run ./forkplay now, and also observe the number of zombie processes from the top
command result. You will find there is one more zombie process in the system now, because
the parent went on to another task (sleeping) without waiting for the child to exit. After 10
seconds, the added zombie process will disappear again, because after the parent
process exits the zombie will be taken over and terminated by the
init
process.
So how to solve this bug? The easiest way is to use the waitpid()
function to make the parent wait for the child to finish before going to sleep, and thus
clean the child process when it exits. One drawback of this way is that it
blocks the parent process - waitpid()
is a "blocking" method that suspends
the current process as it waits for a specific child to exit. The child is specified by
its PID as the first of 3 arguments.
Specifically, call waitpid(pid, 0, 0)
in the parent branch of forkplay.cpp
before the call to sleep
.
With most systems, you must also "#include <sys/wait.h>" to use the waitpid function. After modifying forkplay.cpp in this way, compile and run it again while also monitoring the result of the top command. This time, if you do it right, you will find no new zombie process is present during the 10 seconds the parent sleeps.
Get your TA's attention to inspect your work, and to record completion of your in-lab work. |
Don't leave early though ... begin the after-lab work below.
Step 5a. ONLY IF YOU RAN OUT OF TIME TO HAVE THE TA INSPECT YOUR WORK
If you must complete this assignment at CSIL, then submit it with the turnin program - but do NOT turn it in if the TA already checked you off. You MUST have both your name and your partner's name in forkplay.cpp in order to receive credit. Remember that the original pilot needs to do this step, since that is whose account you have been using in the lab.
Bring up a terminal window on CSIL, and cd into the original pilot's ~/cs32/lab04 directory. Then type the following to turn in the file:
turnin lab04@cs32 forkplay.cpp
Each pair of students must accomplish the following to earn full credit [50 points] for this lab:
SIGCHLD
signal from the
child process, by executing the following statement before the call to fork:
signal(SIGCHLD, SIG_IGN);Uncomment the sleep line again, then try this out to see the result.
signal
function from main while passing it a pointer to your handler.
In particular, do the following:
void sigChild(int arg)
above the main function.sigChild
to call wait(0)
and print the return
value of that function labelled as the PID of the process that issues the signal
(this will be the child process's ID when the program runs).signal(SIGCHLD, sigChld);Also in main, add a second call to the sleep function in the parent branch after the call to showIDs (because the first sleep will be awakened by the handler).
Prepared by Xiaofei (Gregory) Du, former CS 32 TA, and Michael Costanzo.