CS 3113 Spring 19

Logo

This is the web page for Operation Systems at the University of Oklahoma.

View the Project on GitHub oudalab/cs3113sp19

Introduction to Operating Systems CS 3113, Spring 2019

Project 1 (Due 3/14 end of day)

A shell is any generic term used to type commands into type commands into the operating system. The first Unix shell was called sh developed in 1971 by Ken Thompson. The bash shell and zsh shells are newer, popular shell implementations.

In this project, your task is to create the ou shell oush interpreter. Takes a single file as an argument. This file commands; with one command per line. Each line contains a program that shat should be executed. The oush reads each program and executes them serially. In addition to command line programs, the batch file has two special operators PIPE and BG operator that control how the programs are executed.

Proper Academic Conduct

The code solution to this project must be done individually. Do not copy solutions to this problem from the network and do not look at or copy solutions from others. However, you may use the net for inspiration and you may discuss your general approach with others.

Specification

Your project will have the following functions:

./oush <batchfile>

To implement a shell, you should create a small c program that takes a single text file as an argument. The program should read and execute each line in the file. The shell should use the fork() and exec() system calls to execute each command.

To execute you program you can ./oush batch.oush. The program should read and execute each line. Below is the contents of an example batch.oush file:

cal 2020
cat /etc/issue
echo $!

In this case, the program will check and open batch.oush file. It will then create a child process to execute each each command, starting with the first command. The order of the commands are important. If one command fails, the next command should still execute. Commands should block unless they end with special strings PIPE or BG. A command will not have both PIPE and BG. Below we describe these strings.

PIPE

If a command in the oush batch file ends with the string PIPE, the STDOUT of that comamnd should be redirected to be the STDIN for the subsequent command. The following two commands are equivalent.

In bash:

<command1> | <command2> | <command3>

In oush:

<command1> PIPE
<command2> PIPE
<command3>

The oush shell does not understand the bash | operator.

BG

When the BG string is append to the end of a command, that command should run in the background. That is, the command should be run with exec and the next command should be run immediately afer. The following two commands are equivalent:

In bash:

<command1> & <command2>

In oush:

<command1> BG
<command2>

C Commands

fork

Process creation in UNIX is achieved by means of the kernel system call, fork(). When a process issues a fork request, the operating system performs the following functions (in kernel mode):

  1. It allocates a slot in the process table for the new process
  2. It assigns a unique process ID to the child process
  3. It makes a copy of the parent’s process control block
  4. It makes a copy of the process image of the parent (with the exception of any shared memory)
  5. It increments counters for any files owned by the parent to reflect that an additional process now also owns those files
  6. It assigns the child process to a Ready to Run state
  7. It returns the ID number of the child to the parent process and a 0 value to the child process - this function is called once and returns twice!
#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

//Returns: 0 in child, process ID of child in parent, -1 on error

The fork system call creates a new process that is essentially a clone of the existing one. The child is a complete copy of the parent. For example, the child gets a copy of the parent’s data space, heap and stack. Note that this is a copy. The parent and child do not share these portions of memory. The child also inherits all the open file handles (and streams) of the parent with the same current file offsets.

The parent and child processes are essentially identical except that the new process has a new process ID and the return value from the fork call is different in the two processes:

exec

To actually load and execute a different process, the fork request is used first to generate the new process. The kernel system call: exec(char* programfilename) is then used to load a new program image over the forked process:

The exec system call reinitializes a process from a designated program; the program changes while the process remains! The exec call does not change the process ID and process control block (apart from memory allocation and current execution point); the process inherits all the file handles etc. that were currently open before the call.

Without fork, exec is of limited use; without exec, fork is of limited use (A favorite exam questions is to ask in what circumstances you would/could use these functions on their own. Think about it and be prepared to discuss these scenarios).

exec variants:

System Call Argument Format Environment Passing PATH search
execl list auto no
execv array auto no
execle list manual no
execve array manual no
execlp list auto yes
execvp array auto yes
#include <unistd.h>
 
int execl(path,arg0,arg1,...,argn,null)
   char *path;     // path of program file
   char *arg0;     // first arg (file name) 
   char *arg1;     // second arg (1st command line parameter)
    ...
   char *argn;     // last arg
   char *null;     // NULL delimiter 
 
int execv(path,argv)
   char *path;
   char *argv[];   // array of ptrs to args,last ptr = NULL 
 
int execle(path,arg0,arg1,.,argn,null,envp)
   char *path;     // path of program file
   char *arg0;     // first arg (file name) 
   char *arg1;     // second arg (1st command line parameter)
    ...
   char *argn;     // last arg
   char *null;     // NULL delimiter
   char *envp[];   // array of ptrs to environment strings
                   // last ptr = NULL
 
int execve(path,argv,envp)
   char *path;
   char *argv[];   // array of ptrs to args,last ptr = NULL
   char *envp[];   // array of ptrs to environment strings
                   // last ptr = NULL
 
int execlp(file,arg0,arg1,...,argn,null)

int execvp(file,argv)
// All six return -1 on error, no return on success

In the first four exec functions, the executable file has to be referenced either relatively or absolutely by the pathname. The last two search the directories in the PATH environment variable to search for the filename specified.

Example of use of fork and exec

switch (pid = fork ()) { 
   case -1:
      perror("fork"); 
   case 0:                 // child 
      execvp (args[0], args); 
      perror("exec");
   default:                // parent
      if (!dont_wait)
         waitpid(pid, &status, WUNTRACED);
}

wait

When a process terminates, either normally or abnormally, the parent is notified by the kernel sending the parent the SIGCHLD signal. The parent can choose to ignore the signal (the default) or it can provide a function that is called when the signal occurs. The system provides functions wait or waitpid that can

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *statloc);

pid_t waitpid(pid_t pid, int *statloc, int options);

//Both return: process ID if OK, 0 or -1 on error

The differences between the two functions are:

If a child has already terminated and is a zombie, wait returns immediately with that child’s status. Otherwise it blocks the caller until a child terminates. If the caller blocks and has multiple children, wait returns when one terminates - the function returns the process ID of the particular child.

Both functions return an integer status, *statloc. The format of this status is implementation dependent. Macros are defined in <sys/wait.h>.

The pid parameter of waitpid specifies the set of child processes for which to wait.

The options for waitpid are a bitwise OR of any of the following options:

dup and dup2

An existing file descriptor (filedes) is duplicated by either of the following functions:

#include <unistd.h>
 
int dup(int filedes);
 
int dup2(int filedes, int filedes2);
// Both return: new file descriptor if OK, -1 on error

The new file descriptor returned by dup is guaranteed to be the lowest numbered available file descriptor. Thus by closing one of the standard file descriptors (STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO, normally 0, 1 and 2 respectively) immediately before calling dup, we can guarantee (in a single threaded environment!) that filedes will be allotted that empty number.

With dup2 we specify the value of the new descriptor with the filedes2 argument and it is an atomic call. If filedes2 is already open, it is first closed. If filedes equals filedes2 , then dup2 returns filedes2 without closing it.

pipe

The Linux pipe is a data channel that allows data to be passed inbetween processes. This is done by calling the pipe() function on two contigious integers (e.g. int p[2]). When a fork is perform afterwards both processes will have access to the pipe. Each integer corresponds to file descriptors. In the example p above, p[0] will be the fd(file descriptor) for the read end of pipe. and p[1] will be the fd for the write end of pipe. For example, for the parent to write to the child, the parent will write to the file descriptor p[1] and the child can read the data from the fild descriptor in p[0]. The text book website has more resources on pipes

Makefile

Include a makefile with the standard targets all and clean. The all target should make each program and clean target should remove executables and temporary files. The Makefile should create an executable called oush.

README.md

The README file name should be all uppercase with either no extension or a .md extension. You should write your name in it, and example of how to run it, and a list of any web or external resources that you used for help. The README file should also contain a list of any bugs or assumptions made whhile writing the program. Note that you should not be copying code from any website not provided by the instructor.

Tests

The test_project1.bats file contains a bats script to test each file. You should create your own bats files to test your code. We strongly recommend performing tests above and beyond those that are provided for you. Our testing program will automatically compare the output of your program against a known file. In order to receive credit for the correctness of your program, your output must match ours exactly (down to the byte).

COLLABORATORS file

This file should contain a comma separated list describing who you worked with and a small text description describing the nature of the collaboration. This information should be listed in three fields as in the example is below:

Katherine Johnson, kj@nasa.gov, Helped me understand calculations
Dorothy Vaughan, doro@dod.gov, Helped me with multiplexed time management

Create a Repository on your instance and GitHub

Create a private repository GitHub called cs3113sp19-project1

Add collaborators cegme and Vinothini-Rajasekaran by going to Settings > Collaborators.

Then go to your instance, create a new folder /projects if it is not already created, and clone the repository into that folder. For example:

cd /
sudo mkdir /projects
sudo chown `whoami`:`whoami` /projects
chmod 777 /projects
cd projects
git clone https://github.com/cegme/cs3113sp19-project1

Submitting Code

When ready to submit, create a tag on your repository using git tag on the latest commit:

git tag v1.0
git push origin v1.0

The version v1.0 lets us know when and what version of code you would like us to grade. If you would like to submit a a second version before the 24 hour deadline, use the tag v2.0. If you would like to submit an updated version of your code before the first deadline, you can select v1.1.

If you need to update a tag, view the commands in the following StackOverflow post.

Deadline

Your submission/release is due on Thursday, March 3rd at 11:45pm. Submissions arriving between 11:45:01pm and 11:45pm on the following day will receive a 10% penalty; meaning these scores will only receive 90% of its value. Any later submissions will receive not receive credits. You must have your instance running and code available otherwise you will not receive credit.

Grading

Grades will be assessed according to the following distribution:

Notes

Addenda

2019-02-27

2019-03-05

2019-03-11