General Overview of Unix Shells
A shell is an interactive command-line interpreter that runs
programs on behalf of the user. A shell repeatedly prints a
prompt, waits for a command line on stdin, and then
carries out some action, as directed by the contents of the command
line.
The command line is a sequence of ASCII text words delimited by
whitespace. The first word in the command line is either the name
of a built-in command or the pathname of an executable file. The
remaining words are command-line arguments. If the first word is a
built-in command, the shell immediately executes the command in the
current process. Otherwise, the word is assumed to be the pathname
of an executable program. In this case, the shell forks a child
process, then loads and runs the program in the context of the
child. The child processes created as a result of interpreting a
single command line are known collectively as a job. In
general, a job can consist of multiple child processes connected by Unix
pipes.
If the command line ends with an ampersand &, then the job runs in
the background, which means that the shell does not wait for the
job to terminate before printing the prompt and awaiting the next
command line. Otherwise, the job runs in the foreground,
which means that the shell waits for the job to terminate before
prompting for the next command line. Thus, at any point in time,
at most one job can be running in the foreground. However, an
arbitrary number of jobs can run in the background.
For example, typing the command line
tsh> jobs
causes the shell to execute the built-in jobs
command. Typing the command line
tsh> /bin/ls -l -d
runs the ls program in the foreground. By convention,
the shell ensures that when ls begins executing its main
routine:
int main(int argc, char *argv[])
the argc and argv arguments have the following
values:
argc == 3
argv[0] == "/bin/ls"
argv[1] == "-l"
argv[2] == "-d"
Alternatively, typing the command line
tsh> /bin/ls -l -d &
runs the ls program in the background.
Unix shells support the notion of job control, which allows users
to move jobs back and forth between background and foreground, and to
change the process state (running, stopped, or terminated) of the
processes in a job. Typing ctrl-C causes a
SIGINT signal to
be delivered to each process in the foreground job. The default
action for SIGINT is to terminate the process. Similarly, typing
ctrl-Z causes a SIGTSTP signal to be delivered to
each process in
the foreground job. The default action for SIGTSTP is to place a
process in the stopped state, where it remains until it is awakened by
the receipt of a SIGCONT signal. Unix shells also provide various
built-in commands that support job control. For example:
-
jobs : List the running and stopped background jobs.
-
bg <job> : Change a stopped background job to a running
background job.
-
fg <job> : Change a stopped or running background job to a
running foreground job.
-
kill <job> : Terminate a job.
The tsh Specification
Your tsh shell should have the following features:
- The prompt should be the string “
tsh> ”.
-
The command line typed by the user should consist of a name and
zero or more arguments, all separated by one or more spaces. If
name is a built-in command, then
tsh should handle
it immediately and wait for the next command line. Otherwise,
tsh should assume that name is the path of an
executable file, which it loads and runs in the context of an initial
child process. (In this context, the term job refers to this
initial child process.)
-
tsh need not support pipes (| ) or I/O redirection
(< and > ), but you are welcome to
implement these for extra credit.
-
Typing ctrl-
C (ctrl-Z ) should
cause a SIGINT (SIGTSTP)
signal to be sent to the current foreground job, as well as any
descendents of that job (e.g., any child processes that it
forked). If there is no foreground job (i.e., tsh is
in the foreground), then the signal should
have no effect.
-
If the command line ends with an ampersand, then
tsh should
run the job in the background. Otherwise, it should run the job in
the foreground.
-
Each job can be identified by either a process ID (PID) or a job ID
(JID), which is a small positive integer assigned by
tsh . JIDs should be denoted on the command line by
the prefix “%”. For example, “ %5”
denotes JID 5, and “5” denotes PID 5. (We have
provided you with all of the routines you need for manipulating the job
list.)
-
tsh should support the following built-in commands:
-
The
quit command terminates the shell.
-
The
jobs command lists all background jobs.
-
The
bg <job> command restarts <job> by
sending it a SIGCONT signal, and then runs it in the background. The
<job> argument can be either a PID or a JID.
-
The
fg <job> command restarts <job> by
sending it a SIGCONT signal, and then runs it in the foreground.
-
tsh should reap all of its zombie children. If any
job terminates because it receives a signal that it didn’t catch,
then tsh should recognize this event and print a message
with the job’s PID and a description of the offending
signal.
Checking Your Work
We have provided some tools to help you check your work.
Reference solution. The Linux executable tshref is
the reference solution for the shell. Run this program to resolve
any questions you have about how your shell should behave. Your
shell should emit output that is identical to the reference
solution
(modulo PIDs, of course, which change from run to run).
Past experience indicates that obtaining exact correspondence is
difficult. In some cases one might argue that different output
would be equally “good;” even so, for the sake of the
TA’s sanity, you will receive full
credit only for matching the reference solution.
Shell driver. The sdriver.pl program (a perl script)
executes a shell as a child process, sends it commands and signals as
directed by a trace file, and captures and displays the output from
the shell.
unix> ./sdriver.pl -h
Usage: sdriver.pl [-hv] -t <trace> -s <shellprog> -a <args>
Options:
-h Print this message
-v Be more verbose
-t <trace> Trace file
-s <shell> Shell program to test
-a <args> Shell arguments
-g Generate output for autograder
We have also provided 16 trace files (trace{01-16}.txt ) that you
will use in conjunction with the shell driver to test the correctness of your
shell. The lower-numbered trace files do very simple tests, and the
higher-numbered tests do more complicated tests.
You can run the shell driver on your shell using trace file
trace01.txt (for instance) by typing:
unix> ./sdriver.pl -t trace01.txt -s ./tsh -a "-p"
or
unix> make test01
(The -a "-p" argument tells your shell not to emit a
prompt.) And, if you want to run all of the tests on your shell,
you can type:
unix> make tests
Similarly, you can run the trace driver on the reference shell by typing:
unix> ./sdriver.pl -t trace01.txt -s ./tshref -a "-p"
or
unix> make rtest01
And you can run all the tests on the reference shell by typing
unix> make rtests
For your reference, tshref.out gives the output of the
reference solution on all 16 traces. This might be more convenient
for you than running the shell driver.
The neat thing about the trace files is that they generate the same
output you would have gotten had you run your shell interactively
(except for an initial comment that identifies the trace). For
example:
unix> make test15
./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found.
tsh> ./myspin 10
Job (9721) terminated by signal: Interrupt
tsh> ./myspin 3 &
[1] (9723) ./myspin 3 &
tsh> ./myspin 4 &
[2] (9725) ./myspin 4 &
tsh> jobs
[1] (9723) Running ./myspin 3 &
[2] (9725) Running ./myspin 4 &
tsh> fg %1
Job [1] (9723) stopped by signal: Stopped
tsh> jobs
[1] (9723) Stopped ./myspin 3 &
[2] (9725) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (9723) ./myspin 3 &
tsh> jobs
[1] (9723) Running ./myspin 3 &
[2] (9725) Running ./myspin 4 &
tsh> fg %1
tsh> quit
unix>
Note
In the shell assignment as defined by the authors, you were not required
to change ownership of the terminal. That meant that your processes
wouldn’t
be able to read from the terminal. You were to leave the terminal
attached to the shell, which would catch SIGINT and SIGSTP, and forward
them to the process group it is pretending is in the
foreground.
We’re requiring you to fix that this year. There’s one
test case (myhello.c ) that accepts user input; it’s
worth 10 pts. Your shell should
correctly handle this test case.
Hints
-
Read every word of Chapter 8 (Exceptional Control Flow) in the textbook.
-
Use the trace files to guide the development of your shell.
Starting with
trace01.txt , make sure that your shell
produces the identical output as the reference shell. Then
move on to trace file trace02.txt , and so on.
-
The
psignal , waitpid , kill ,
fork , execve , setpgid ,
sigprocmask , and tcsetpgrp functions will come
in very handy. The
WUNTRACED and WNOHANG options to waitpid will also be
useful.
-
Programs such as
more , less , vi ,
and emacs do strange things with the terminal
settings. Don’t run these programs from your shell.
Stick with simple text-based programs such /bin/ls ,
/bin/ps , and /bin/echo . If you run one
of the others by accident, you may be able to get back to normal by
typing stty reset . In the worst case, you may need to
terminate your terminal session and log back in again.
-
When you run your shell from the standard Unix shell, your shell is
running in the foreground process group. If your shell then
creates a child process, by default that child will also be a member of
the foreground process group. Since typing
ctrl-
C sends a
SIGINT to every process in the foreground group, typing
ctrl-C
will send a SIGINT to your shell, as well as to every process that your
shell created, which obviously isn’t correct.
Here is the solution: After the fork , but before the
execve , the child process should call setpgid(0,
0) , which puts the child in a new process group whose group ID is
identical to the child’s PID.
If the child is to run in the foreground, it should then call
tcsetpgrp(0, my_pid) .
This ensures that
when you type ctrl-C , the resulting SIGINT goes
either to your shell or to the appropriate foreground job (more
precisely, the process group that contains the foreground job), as
appropriate.
Note that you will need to call tcsetpgrp again after
reaping a foreground job. If you read the man page, you'll notice
that a process gets a SIGTTOU signal whenever it calls tcsetpgrp
from the background; to avoid this, you will need to call
signal once in your shell, during startup, to explicitly
ignore SIGTTOU.
-
You may notice that
tcsetpgrp fails when testing your shell
with the given test script. This is a known problem with the
script. It should not, however, prevent you from correctly running
any of the trace*.txt files, as they take no user input.
Your shell must, however, display correct behavior on programs, both
foreground and background, that do take user input—including correct
handling of ctrl-C , ctrl-Y , and
ctrl-Z events entered at the keyboard. We will
be testing these behaviors manually. If you are not sure what the
“correct” behavior ought to be, try the same operations in
your favorite Linux shell.
Evaluation
Your score will be computed out of a maximum of 100 points based on the
following distribution:
- [90] Correctness: 16 trace files at 5 points each,
myhello.c for 10 points.
- [10] Style points. We expect you to have good comments (5 pts)
and to check the return value of every system call (5 pts).
Your solution shell will be tested for correctness on a CSUG Linux
machine (likely cycle1), using the same shell driver and trace files
that were included in your lab directory. Your shell should
produce identical output on these traces as the reference shell,
with only two exceptions:
- The PIDs can (and will) be different.
-
The output of the
/bin/ps commands in
trace11.txt , trace12.txt , and
trace13.txt will be different from run to run.
However, the running states of any mysplit processes in the
output of the /bin/ps command should be identical.
We reserve the right to run additional checks if we suspect that your program
generates the “right” output for the wrong reason.
Extra Credit
There are many opportunities for extra credit on this assignment.
Possibilities of particular relevance to the current assignment include:
- multi-process jobs, linked by pipes
- redirection of stdin, stdout, and stderr
- quoting (including back quotes)
Read the man pages for your favorite shell for details and additional
ideas.
Because much of the grading for this assignment is automated, you will
need to draw the TA’s attention explicitly to any extra credit
features you implement. To do this, include a README file in the
directory in which you run the TURNIN script.
Be sure that any extra credit you implement does not alter the behavior
of your code on the standard test cases.
“Trivia” Assignment
Before noon, Thursday, March 31, send email to
containing answers to the
following questions (a single email per team is acceptable):
-
Are you working alone or in a team of two? If the latter, who is
your partner?
-
What output do you get when you run the “trace driver” on
the “reference shell”? Use
trace02.txt as
the trace file.
-
What is the difference between the
fork and execve
system calls?
-
How are command line arguments retrieved in a program (in C)? Name the
funtion(s) or variable(s) used.
-
Explain the meaning and purpose of the WNOHANG and WUNTRACED options to the
waitpid system call.
Turn In Instructions
The “trivia” assignment will be submitted via email.
The main assignment will be submitted using the script
/u/cs252/bin/TURNIN. From the directory you wish to turn in type:
/u/cs252/bin/TURNIN .
Watch the Blackboard discussion forum
for details, and for any clarifications or revisions to the assignment.
Before running the TURNIN script, be sure that you have
-
Included your full name and email address in the comment at the top of
tsh.c.
-
Removed any extraneous print statements.
-
Included any appropriate commentary on your code in a separate README file or
as C comments in tch.c.
DUE DATES:
For the “trivia” assignment: noon, Thursday, March 31.
For the main assignment: 11:59pm, Monday, April 11.
|