Assigned: Feb 6, 2020
Due: Feb 20, 2020 23:59:59
IMPORTANT PRELIMINARIES!!!
while(1)
, or equivalent ANYWHERE in your code.while(should_run)
from the textfor
loop insteadfor (int should_run = 0; should_run < 25; should_run++)
)exit
command.system()
function.This project consists of writing a C (or C++) program to serve as a shell interface that accepts user commands and executes each command in a separate process. A shell interface gives the user a prompt, after which the next command is entered. The example below illustrates the prompt osh>
and the user’s next command: cat prog.c
. (This command displays the file prog.c on the terminal using the Unix cat
command.)
osh> cat prog.c
The above is an example of a simple command (i.e., it does not contain any operators). We will extend this program to execute more complex commands which contains one or more simple command connected together by an operator such as:
osh> cat < code.c
osh> cat < code.c > out
osh> ps | cat > out
Your shell will support the following operators (much like the standard csh
or bash
shells in Unix).
>
- Redirect stdout to a file<
- Redirect stdin from a file>>
- Append stdout to a file&&
- Execute next command only on success.||
- Execute next command only on failure.;
- Execute next command regardless of success or failure.|
- Pipe stdout of one command into stdin of next.The functionality defined here for the above operators are basic, and may slightly deviate from the functionality defined by the standard shells available on Unix. But it is not the purpose of this project to write a fully functional Unix shell. Rather, it is to introduce and familiarize ourselves with various concepts useful in understanding operating systems and systems programming such file I/O, processes control, and inter-process communication. As such, we will only code parts of the shell that help introduce these topics.
This project is broken up into two programming assignments. In PA1 you will add support for:
fork
and exec
to create new processes|
(pipes)In PA2, you will build upon PA1 and add support for:
Use web handin to hand in your assignment. Submit a single zip file, <UNL username>_<pa#>.zip
(e.g., jdoe2_pa1.zip
) containing only:
Executing make
in your project directory should produce an executable named osh
. In the README file:
Remember to verify that your code compiles and runs on the CSE servers.
The PA2 zip contains 4 test scripts, alongside the corresponding expected output files. The table describes the test case, and the points awarded for each test case.
File | Answer Script | Test Case Description | Points |
---|---|---|---|
6.singlePipe.txt | ea6.txt | Two command connected by a single pipe | 20 |
7.moreLogical.txt | ea7.txt | Multiple commands connected by multiple logical operator (&&, ||, ;) | 20 |
8.morePipes.txt | ea8.txt | Multiple commands connected by multiple pipes | 20 |
9.simplePipeAndLogical.txt | ea9.txt | Multiple commands connected by pipe and logical operator | 20 |
makefile | The program compiles successfully on command ‘make’ | 10 | |
README file | As described in submission guidelines above | 10 | |
Total | 100 |
The test scripts can be used to test your program and are the test scripts we use for grading. A fully functional executable, osh
, is included in the distribution zip file for this project. You can use it to see what your outputs should look like.
osh
.osh>
On our end, after running make
to compile your program, we will run:
./osh -t < testscripts/testscript.txt > & tmp ; diff tmp testscripts/ea.txt ;
for each test script in the testscripts directory. Note that the “> &
” redirects stderr to stdout in csh. If there are no differences you get all the points, otherwise we take points off.
Although you won’t get points for these we will dock points for the following:
osh>
and the command outputThis is the most challenging part of the shell project. We’ll handle pipes in two steps. First we implement the logic to handle a single pipe, then extend it to handle any number of pipes in a command.
Pipes work similar to redirectors. But unlike a redirector, where the input/output is a file, pipe connects two commands. Consider the following command:
osh> ls | cat
This command will pass stdout from ls
to stdin of cat
. We could equivalently write this as,
osh> ls > tempfile; cat < tempfile
which consists of two steps:
ls
to point to tempfilecat
to point to tempfileBut creating a file is expensive if its purpose is just to act as a temporary buffer. If we can hold this buffer in memory, it would be much more efficient. Interprocess communication (IPC) can help us do this. We will use pipes in this PA to accomplish this IPC. There is a system call pipe()
that can be used:
pipe()
- http://man7.org/linux/man-pages/man2/pipe.2.html
“pipes” are either bidirectional or unidirectional data channels, used for IPC. For this assignment we assume pipes are unidirectional and only pass information in one direction. For this project, using pipes boils down to 3 steps:
stdout
of ls
to one end of the pipestdin
of cat
process to the other end of the pipeThe timing of doing all this is critical. The process image is copied upon fork()
so to pass the file descriptors of the pipe along to the child, the pipe needs to be created before the fork. Once this is done, connect the pipes at the beginning of your loop (after fork()
and before calling exec()
). We can generalize this as follows,
Once these connections are made, the rest of the procedure for exec()
, error handling, etc. remains unchanged. At a high level, your procedure should look like this:
prevpipe = null;
while(i<25) {
get command()
new process = fork()
if (current command is connected to previous command by pipe) {
// connect stdin of new process to prevpipe
}
if (current command is connected to next command by pipe) {
prevpipe = pipe();
// connect stdout of new process to prevpipe
}
// rest of code
}
NOTE: In the case of a pipe, we do not wait for the current process to exit before starting the next one. Instead we wait for the last process in the chain of commands connected by pipe. When we create a pipe, a buffer of size PIPE_BUF
will be allocated. When this buffer is full, it blocks the write (i.e., the current process writing to the pipe also blocks). So we will need another process at the other end reading from the pipe.
Finally, you should be able to handle commands with more than one pipe. It could be two pipes, or many more:
osh> ls | cat | cat
If you implemented the previously described algorithm properly, you don’t need to do anything more. It should just work.
Processes reading from pipes will hang (i.e., not receive an EOF) until all write file descriptors to the pipe have been closed. Creating the pipes in the parent and forking two children will require careful closing of the file descriptors for pipes to work. To minimize open write pipe descriptors, follow the recommendations below.
fork()
in order to replicate the pipe file descriptors to the children