实现Linux的 Shell
工具,需要完成Background Process, Redirection, Piping和Signals的功能。
![Shell](https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Bash_demo.png/370px-
Bash_demo.png)
Introduction
The goal of this assignment is to become familiar with low-level Unix/POSIX
system calls related to processes, file access, and interprocess communication
(pipes and redirection). You will write a basic shell which supports a few
basic operations.
Takeaways
After completing this homework assignment, you should:
- Understand process execution, pipes and forks
- Have an advanced understanding of Unix commands and the command line
- Have gained experience with C libraries and system calls
- Have enhanced your C programming abilities
Hints and Tips
- Check the return codes of all system calls. This will help you catch errors. Refer to the lecture notes on writing error handling wrappers for conceptually how to handle this. Note, however your shell should not exit on error as is shown in the examples.
- Make sure to handle any errors! Throw lots of junk commands into your shell to test.
- Your shell should NEVER crash. We will be deducting points for each time your program crashes during grading. Make sure that your code handles invalid input gracefully. For this assignment, we will be grading your submissions by hand. That means that as far as error messages are concerned, you only need to make sure that all of your output is reasonable and human readable.
Getting Started
Download the base code for this assignment: hw4_basefiles.tar.
The structure is:
hw4
├── include
│ ├── helpers.h // All add functions that you want to declare go here
│ └── shell.h // or here
├── Makefile
└── src
├── helpers.c // All helper functions that you want to define go here, or in
└── shell.c // the base code for this assignment
shell.c file provides you a very simple shell that supports the following
basic functionalities:
- Prints a simple command prompt and waits for user input:
<netid>$
- Reads in a string (command) from the user (User must press enter)
- Tokenizes the entered command into a set of strings (tokens) based on whitespace characters
- The base code supports a command with at most 15 tokens.
- The provided tokenizer will treat all characters between double-quotes as a single token.
- Attempts to run the entered command as a foreground process using execvp()
- The shell will terminate if the user types exit.
All code and implementation must be placed in the shell.h/.c and helpers.h/.c
files. Do not create any additional C files.
Restrictions & Important Information
The provided base code will compile and run a simple shell program. It
demonstrates the basic concept of a shell program. However, it lacks some
important features:
- You cannot change directories in your shell. Changing directories is a shell built-in command (EC option).
- Executables may be stored anywhere on your computer. For example, the program cat may be located in /bin/cat while grep may be located in /usr/bin/grep. You never have to specifically type /bin/cat though. The reason for this is that your shell will automatically search common paths for programs. As such your shell can only run programs which are located within the specified PATH directory or the current directory (where your shell program was run from).
- In general, you may use almost any standard library function or system call to complete this homework. There is one important exception: you may not use the system(3) function. This function is effectively a wrapper for a shell and using it would defeat the purpose of the whole assignment. Speaking more broadly, any library function or system call that is effectively a wrapper for another shell is prohibited in this assignment. Using any of these functions will result in an automatic zero for the assignment.
Shell Implementation Requirements
In this homework, the goal is to add features to the base shell program to
make it more useful and practical. Below are the requirements:
- Support for a foreground and single background process simultaneously
- Support for I/O redirection (stdin, stdout, stderr) and piping between two programs
- Catching and handling signals
Before you begin, replace<netid>
in the shell prompt with your actual
netid.
Background Process
A background process executes without blocking the shell from taking
additional commands. This means the program is runs and the shell does not
wait for it to finish before returning to the user with a new prompt. This is
accomplished by spawning a child process to execute the command/program and
not waiting for the child to terminate. The standard operator to specify the
command to be run in as a background process is &. Only when this operator is
added to the command as the very last token will the shell recognize it as an
indicator for background processes.
An example:
Please note that for this homework, you only need to support ONE background
process at a time. If another background process is entered by the user while
the first is running, simply reject the second background process and continue
with the previous process. To track if a background process is running record
the pid of the child process and set it to -1 when there is no background
process running.
When the background process terminates, the shell must make sure the
terminated background process is reaped. There are two ways to handle this:
(a) using waitpid() or (b) creating a signal handler for SIGCHLD. For (a) when
calling waitpid(), you need to add the option WNOHANG to ensure the function
does not block waiting for a child to terminate. Refer to man waitpid(2) for
more details.
Determining when to check if the background process has terminated is an
important design decision in this assignment.
For (b), you will need to consider that case where a foreground and background
process are both running.
SIGCHLD could occur for either process and your shell should act accordingly
to reap/clean up either/both processes.
All test cases will have at least one whitespace character/newline character on either side of the & sign.
Redirection
The shell must support the following Unix I/O redirection options: >
, 2>
, >>
, &>
, and <
. Consult with this webpage and/or here to
review I/O redirection usage. We summarize additionally here.
One of the most powerful features of a Unix shell is the ability to compose a
series of simple applications to create a complex workflow. The feature that
enables this composition is output redirection. Basic redirection is
accomplished by three special characters: <
, >
, and |
(explained
in next section).
The <
operator indicates that you are to take standard input from the
specified file:
The input.txt file must exist and be openable. If not, an error occurs.
The >
operator indicates that you are to write standard output to the
specified file:
The output.txt file is created or overwritten if it exists.
Next we have the 2>
operator. While the >
operator redirects standard
output to a file, 2>
will redirect the output of stderr to a file. For
instance, in the following example, if the executable printToStderr prints
text to standard error, then it will be redirected to the file output.txt.
The output.txt file is created or overwritten if it exists.
A special case of the operator just discussed is &>
. This operator will
redirect both standard output and standard error to the same file, output.txt.
The file is created or overwritten if it exists.
The >>
operator works similar to the >
operator in that it also
redirects standard output to the specified file. However, it will append the
output to the end of the specified file, if it exists. If the file does not
exist, it is created. For example:
The result is that output.txt should have the following contents:
This will overwrite the file
This will append to the file
For all of the examples above, you can run them in your VM bash terminal to
observe the expected functionality.
In order to implement this functionality in your shell, you will need to do
the following items:
- open/create the required input or output files
- fork() and exec() a child process, and
- use dup() or dup2() (see man dup) to make copies and set the proper file descriptors
It is important to determine which process (the parent or child) should do
each of these items, the order in which to do them, and which file descriptors
and when/who should be closed.
Redirection operators always appear after all arguments to the program. This
means, the set of redirection operators and associated file arguments will
appear after all program arguments, but before the ‘&’ for background process.
Remember, your code is now the shell. Therefore, when the shell tokenizes the
user’s command the redirection symbols will be in the token list. It is your
job to detect them and to handle their operation correctly. Hint: You should
not pass these tokens to the program.
Your shell will be tested with single redirection operator (one of>
,2>
,>>
,&>
, and<
), as well as combinations of valid
redirection together. Valid combinations include: <
along with one of the output options (>
,2>
,>>
,&>
)<
along with>
and2>
>
and2>
together with no<
>>
and2>
together with no<
Redirection WILL NOT be tested in conjunction with piping (presented in the
next section).
All test cases will have at least one whitespace character on either side of
any of the redirection operators.
Piping
Your shell must also support piping. For this assignment, you will support
only a single pipe.
Additionally, for simplicity, your program does not need to support a pipe
along with any redirection operators in a single command. If you are new to
the concept of piping start with this link.
The pipe operator, |
, connects the standard output of the first program
to the standard input of the next program. For example,
the echo program will print the string to standard out which is then fed to
the grep program on standard in.
To accomplish this task you will need to use pipe()/pipe2() (see man pipe).
The basic steps, using the above example, are as follows:
- create a single pipe to connect echo to grep
- fork() two times, one for each command/program (echo and grep)
- the child for echo must connect its stdout file descriptor to the write end of the pipe
- the child for grep must connect its stdin file descriptor to the read end of the pipe
- the each child runs their command
All test cases will have at least one whitespace character on either side of
the|
.
Signals
Normally, exceptional control flow occurs in the operating system kernel. We
saw this when we studied exceptions. Signals are a way for your program to
hook into and handle/block some of these exceptions.
Review section 8.5 in your textbook before attempting this section of the
assignment. Additionally,
Implement the following signal operations:
- Block the SIGTSTP signal. When a user tries to stop your shell by using Ctrl-Z, your shell should do nothing at all.
- Handle the SIGUSR2 signal. The signal SIGUSR2 is a user defined signal and can be interpreted however we want. When this signal is sent to your process, you should print to standard out, “Hi User!\n”.
To test your handling of SIGUSR2 by opening a new vssh connection to your VM
and execute the following command where [pid] is the process ID of your
currently running shell:$ kill -s SIGUSR2 [pid]
Extra Credit
For this assignment we have included a number of Extra Credit options:
- Add the ability to change directories with the built-in cd command. Support cd dir and cd .. must be supported. See man chdir(2)
- Support a combination of redirection and piping in a single command. Note, that not all combinations are valid combination, and error detection for these cases should be performed.
- Support multiple (3+) pipes in a single command.
- Support multiple background processes at one time, as well as provide a built in command list, which will display on stdout the pid and command running for each background process.
- Implement the command, fg [pid], which will bring a background process back to the foreground. More information about this command can be found (here).
- Add additional information to your command prompt such as username, machine, date/time, and current directory path. Consider changing the color of the prompt (but not the commands).
Submission
Make sure your directory tree looks like this and that your homework compiles.
Tar and upload the following directory structure as hw4_netid.tar to the
Canvas assignment prior to the deadline.
hw4
├── include
│ ├── helpers.h // All add functions that you want to declare go here
│ └── shell.h // or here
├── Makefile
└── src
├── helpers.c // All helper functions that you want to define go here, or in
└── shell.c // the base code for this assignment
Make sure that your submission compiles by downloading your tar file,
extracting it in a new folder and compiling and running your program. If you
program does not run you will get a 0!
NOTE: When writing your program try to comment as much as possible. Try to
stay consistent with your formatting too! This will make it easier to debug
your program!