Starting from:

$30

CS2106-Lab 2: Process Operations in UNIX Solved

 Section 1. Exercises in Lab 2

The purpose of this lab is to learn about process operations in Unix-based OS. There are a total of three exercises in this lab. The first exercise, which is the demo exercise, requires you to try out two system calls covered in lecture 2b. The remaining two exercises will see you building a simple shell interpreter. Since exercises 2 and 3 are quite involved, we will describe them in a separate section (section 2).

1.1 Exercise 1 [Lab Demo Exercise]

In this exercise, you will use a combination of fork() and waitpid() to perform simple process creation and synchronization. The waitpid()library call is a variant in the wait() family which enables us to wait on a specific pid. Don’t forget to check the manual page (“man –s2 waitpid”). The requirement is best understood through a sample session.

Sample Input: 
 
3                       
//3 child processes
 

Sample Output: 
Child 1[818]: Hello!   //818 is the process id of child Parent: Child 1 [818] done. 

Child 3[820]: Hello! 

Child 2[819]: Hello!    //Order between child processes is not fixed Parent: Child 2[819] done. 

Parent: Child 3[820] done. 

Parent: Exiting. 
The program will read in an integer N between 1 to 9 (inclusive) which indicates the number of child processes to spawn. Each of the child processes print out a “Child X[PID]: Hello!” message, where X is the index number and PID is the process id of the child,. The parent process will then check for the termination for each of the child and print out the corresponding message, “Parent: Child X[PID] is done.”. The parent process should spawn all child processes before checking on them. This check is performed in the spawning order, i.e. Child 1, 2, … N. 

 

Note that the order in which the child process print out message is not fixed, so your output may not exactly match the sample session. However, certain order must always be respected, e.g. Child X must print the “Hello” message before the parent can print out “Child X is done” message. Due to the nature of the output, there is no sample test cases provided. [Testing hint: use the "sleep()" command to introduce random small delays for the child processes. Pay attention to the order of messages.]

 

Section 2. Command Line Interpreter (Exercise 2 and 3)
 

As shown during the lecture, a command line interpreter (aka command prompt or shell) involves most of the major process operations. So, you are going to implement an interpreter with various features in the remaining exercises. In exercise 2, only a few simple functionalities are required. You should take the opportunity to plan and design your code since the same code can be reused in exercise 3.

 

Note that when you unpacked the directories for these two exercises, you’ll find several C files and a file named “makefile” in each exercise directories in additional to the usual skeleton files ex2.c and ex3.c. These extra C files are tiny programs with various runtime behavior to aid your testing later. To build these programs, simply enter “make” command under the directories. The “make” utility invokes the gcc compiler automatically to prepare the executables for you. 

 

Summary of the extra programs:

clock
Prints out a message after X seconds and repeat for Y times. X and Y can be specified as command line arguments. See given code for more details
alarmClock 
Prints out a message after X seconds and terminates. X can be specified as command line argument. See given code for more details.
infinite 
Goes into infinite loop, never terminates.
return 
Return the user specified number X to the shell interpreter.
showCmdArg 
Shows the command line arguments passed in by user.
stringTokenizer 
Shows how to split user input into subparts (token).
Due to the nature of the interpreter, it is hard to test using sample input/output. So, instead of sample test cases, a sample usage session is included in the later sections of this document.

2.1 Exercise 2 (Basic Interpreter) 

Let us implement a simple working interpreter with limited features in this exercise.

 

General flow of basic interpreter 
1.      Prompt for user request.

2.      Carry out the user request.

3.      Unless user terminates the program, go to step 1.
 

TWO possible user requests: 
a. Quit the interpreter. Format: quit                    

 

Behavior: 

• Print message "Goodbye!" and terminates the interpreter.
b. Run a command. Format: command_path 

//Read the path of the command

//The command_path is assumed to be less than 20 characters

 

Behavior: 

a.    If command exist, run the command in a child process

• Wait until the child process is done

b.   Else print error message “XXXX not found”, where XXXX is the user command.
 

For (b), you need to check whether the command_path exists (i.e. valid). This can be achieved by various methods, one simple way is to make use of a library call:

 

stat() 

 

Find out more about this function by “man –s2 stat”. This library function has various usages, and some are quite involved. However, you don’t need to have the full knowledge to use this call effectively. [Hint: look carefully at the return type of this function.]

 

Make use of the fork() and execl() combo to run valid commands. The execl() function call discussed in the lecture is sufficient for this exercise. When using execl(), you can assume that the path of the program is the same as command line argument zero (i.e. name of the program).

 

Just like a real shell interpreter, your program will wait until the command finishes before asking for another user request.

 

Assumptions: 
a.      Non-terminating command will NOT be tested on your interpreter 

b.      No ctrl-z or ctrl-c will be used during testing. As we have not covered signal handling in Unix, it is hard for you to take care of these at the moment. 

c.      You can assume user requests are "syntactically correct". 

You can assume the command_path has less than 20 characters.  
 

 

Suggestions on approach:

 

Modularize your code! Try to find the correct code for each of the functionality independently. Test the function thoroughly then work on another part of the program. This allows your code to be reused in exercise 3.
 

Sample Session: 

 

User Input is shown as bold. The prompt is “YWIMC”, short for “Your whim is my command” J, just a way to show your shell interpreter is a devoted servant. Note that additional empty lines are added in between of user inputs to improve readability.

 

YWIMC > /bin/ls               //See note*

a.out     ex2.c     …               //output from the “ls” command

 

YWIMC > ./alarmClock                      //Use the provided program
Time’s up. 3 seconds elapsed.           //after ~3 second delay

 

YWIMC > ./anything

./anything not found                      // “./anything” does not exist

 

YWIMC > quit 

Goodbye!                      // Interpreter exits       
 Note*: The "ls" command may be located at a different location on your system. Use  "whereis ls" to find out the correct path for your system.  

2.2 Exercise 3 (Advanced Interpreter) 

 

This exercise extends the capabilities of the interpreter from exercise 2. Please note the enhanced or new requests below:

 

 
SIX possible user requests: 
 
a. [No change] Quit the interpreter. Format: quit                  //No change from ex2.
b. [Enhanced] Run a command. Format:

command_path [arg1 to arg4] 

//the user can supply up to 4 command line arguments

 

Behavior: 

a.    If the specified command exists, run the command in a child process with the supplied command line arguments.

•   Wait until the child process is done.

•   Capture the child process' return value (see "result" command).

b.   Else print error message “XXXX not found”, where XXXX is the user entered command.
c. [New] Run a command in the background. Format: command_path [arg1 to arg4] & 

//Note the "&" symbol at the end of the command. The user can supply up to 4 command line arguments, same as (b). 

 

Behavior: 

a.    If the specified command exists, run the command in a child process with the supplied command line arguments.

•   Print “Child XXXX in background”, XXXX should be the PID of the child process.

•   Continue to accept user request.

b.   Else print error message “XXXX not found”, where XXXX is the user entered command.
d. [New] Wait for a background process. Format:

wait job_pid 

//"wait" followed by an integer job_pid Behavior: 

a.      If the job_pid is a valid child pid (generated by the request in (c)) and has not been waited before:

•   Wait on this child process until it is done, i.e. the interpreter will stop accepting request.

•   Capture the child process' return value (see "result" command).

b.      Else print error message “XXXX not a valid child pid”, where
XXXX is job_pid entered by user.
e. [New] Print the pids of all unwaited background child process. Format: printchild   

Output format:

Unwaited Child Processes:      //Just an output header

<Pid of Unwaited Child 1>      //May be empty if there is no

<Pid of Unwaited Child 2>      // unwaited child  

…………………………… 

          Behavior:                   

a. PID of all "unwaited" background child processes (including terminated ones) are printed.  
f. [New] Print the return result (i.e. exit status) of a child process. Format: result

Output format:

<return result>      //A single integer number

Behavior: 

a. This command is only valid after a successful (b) or (d).
 

Assumptions: 
a.      Non-terminating command will NOT be tested on your interpreter.

b.      No ctrl-z or ctrl-c will be used during testing. As we have not covered signal handling in Unix, it is hard for you to take care of these at the moment.

c.      Each command line argument for (b) and (c) has less than 20 characters.

d.      There are at most 10 background jobs in a single test session.

e.      You can assume all user requests are "syntactically correct".
 

Notes: o You need to learn a few C library calls by exploring the manual pages. Most of them are variants of what we discussed in lecture.

Instead of execl(), it is easier to make use of execv() in this case. Read up on execv() by “man –s2 execv”.
Remember to use the waitpid() call in exercise 1.
You only need a simple way to keep track of the background job PIDs. (hint: a simple array will do….).
 

             

Suggestions on approach: 

This exercise is fairly involved. Again, implement the required functionalities incrementally is the best approach.
You will need to manipulate string to some extent. Try to write a program just to test out your code first. [Warning: String manipulation is pretty frustrating in C. Don’t be demoralized. J] [Hint: take a look at the sample programs…]
 

Sample Session: 

 

User Input is shown as bold. 

 

YWIMC > /bin/ls        //Path of ls may be different on your system a.out      ex3.c ......       //output from the “ls” command

 

YWIMC > /bin/ls –l  //same as executing “ls –l” total 144

-rwx------   1 sooyj    compsc      8548 Aug 13 12:06 a.out

....................          //other files not shown

 

YWIMC > result          //successful "ls" returns 0 to shell

0

 

YWIMC > ./alarmClock 20 & //Background job. See Note* Child 12345 in background //PID 12345 is just an example

 

YWIMC > ./showCmdArg one 2 3 four  

Arg 0: ./showCmdArg

Arg 1: one

Arg 2: 2

Arg 3: 3

Arg 4: four

 

YWIMC > wait 12340            //Try to wait for  PID12340

12340 not a valid child

 

YWIMC > printchild 

Unwaited Child Processes:

12345                          

 

YWIMC > wait 12345  // Will wait until the previous alarmClock              

                                                // process terminates. Note: It is possible that                                                 // alarmClock has already terminated by this time. 

                                                // In that case, this request will return immediately.

 

YWIMC > printchild 

Unwaited Child Processes:     // empty in this case

 

YWIMC > ./anything 1 2 3

./anything not found                      // “./anything” does not exist
Note*: The background job printing will intermix with your interpreter input / output. There is no need (and no way  J) to make background job print "nicely".

 

 

YWIMC > ./return 188 &        // simply return "188" to shell   

Child 12366 in background   //PID 12366 is just an example     

YWIMC > wait 12366             

 

YWIMC > result          //show the return result of the waited process

188

 

YWIMC> quit 

Goodbye!                      // Interpreter exits       
 

 

More products