CS32, Fall 2017

Lab04:
Processes


Goals for this lab

By the time you have completed this lab, you should be able to

Step by Step Instructions

Step 0: Choose initial pair programming roles, and log in

As usual. If your regular partner is more than 5 minutes late, ask the TA to pair you with someone else for this week.

Step 1: Create a directory for this lab, and get a copy of the lab file

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.

Step 2: Practice managing processes with Linux commands

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.

Background and foreground processes

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`).

Practice Now

Start and kill some background processes like the samples above, until you are comfortable with the concepts.

Step 3: Use fork() to create child process and execute commands from it

Have 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?

Step 4: Learn to avoid the Zombie process

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.

Step 5: Show off your work and get credit for the in-lab requirements

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

Evaluation and Grading

Each pair of students must accomplish the following to earn full credit [50 points] for this lab:


After lab-work is done


Prepared by Xiaofei (Gregory) Du, former CS 32 TA, and Michael Costanzo.