This is the web page for Operation Systems at the University of Oklahoma.
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.
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.
Your project will have the following functions:
PIPE
command between lines../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>
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):
#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:
The “parent” process gets the new process ID of the “child” returned from the fork call. If, for some reason the process can not be cloned, then -1 is returned
The “child” process is returned 0 (zero) from the fork call.
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 fork
ed process:
exec
identifies the required memory allocation for the new program and alters the memory allocation of the process to accommodate itmain()
routine.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);
}
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
block (if all of its children are still running), or
return immediately with the termination status of a child (if a child has terminated and is waiting for its termination status to be fetched), or
return immediately with an error (if it doesn’t have any child processes).
#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:
wait
can block the caller until a child process terminates, while waitpid has an option that prevents it from blocking.
waitpid
doesn’t wait for the first child to terminate - it has a number of options that control which process it waits for.
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.
pid == -1
: waits for any child process. In this respect, waitpid
is equivalent to wait
pid == 0
: waits for any child process in the process group of the callerpid > 0
: waits for the process with process ID pid
pid < -1
: waits for any process whose process group id equals the absolute value of pid
.The options
for waitpid
are a bitwise OR of any of the following options:
WNOHANG
: the call should not block if there are no processes that wish to report statusWUNTRACED
: children of the current process that are stopped due to a SIGTTIN
, SIGTTOU
, SIGTSTP
, or SIGSTOP
signal also have their status reportedAn 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.
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
fileThis 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 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
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.
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.
Grades will be assessed according to the following distribution:
echo
command does not read from stdin
.