Introduction to Homework 3
In this lab, we will be writing a shell program. A shell is the program that all of you use when
you ssh to a linux machine, or hit ctrl+
` to open a terminal in vscode. It’s the program which
prints out a prompt, reads in what you type, and then executes whatever instruction you told it to
do. It is different from ssh (which is the program you use to connect to the server), or linux
(which is the OS on which your shell is running). The assignment is not yet posted, but the basic
loop for our (and any) shell, will be:
- Prompt the user
- Read and parse the command the user types in
- Create a new process to run the command the user typed
- Wait for the new process to end print the previous command’s exit status if the user types the command
?
- Start over with the prompt
The high level structure of a shell
To start this week’s lab, accept Homework 3 via GitHub. This skeleton code
is a stripped down version of the basic shell that is presented in the book in Chapter 8. In
homework 3, we will be switching it over to using spawn
instead of fork
, and then we will be
adding the ability to handle signals (the topic of this week’s lecture) and file redirection (the
topic of next week’s lecture). This week’s lab will help you do the first step.
If you’d like to go over the absolute basics of creating new processes and running different programs, you can take a look at the code that Prof. Kanich used for the fork video here.
To get started, clone the repository and get it started - as usual, you can either do this on your laptop via a devcontainer or a systemsX machine on campus.
Take a close look at forkshell.c
, and give yourself some time to understand what each function does.
eval
takes as input one string written by the user.parseline
separates that one long string into anargv
style collection of pointers to the individual “words” of the command.builtin_command
checks to see whether the user entered a special command, likequit
. If there’s a special command, you don’t need to fork a new process, because it’s something the shell should take care of.
Suggested activity: convince yourself that you understand how
parseline
is manipulatingbuf
andargv
.
How a fork based shell works
In forkshell.c
, after eval
has determined that it is not running a builtin command, it goes
through the process of forking a new process. Take a look at that code in eval
and make sure you
understand what it’s doing.
To dig into how those functions work, take a look at the man pages for fork
, waitpid
, and
execve
. (To view a man page, type man fork
or man waitpid
into terminal.)
How a spawn based shell works
A spawn based shell is nearly identical to a fork based shell, however spawn only returns once, and
it returns success or failure, rather than returning the process id. You will have to create a
variable to store the process id, and pass it by reference to the posix_spawn
function.
Getting started on Homework 3
Accept Homework 3 if you haven’t already, and open it up. Your task for the remainder of the lab is
to modify your version of spawnshell.c
so that it uses posix_spawnp
rather than fork
. You can
take a look at the posix_spawn
man page, the fork video, and the vscode-lectures
example code to
get an idea of how to modify fork-based child process creation into a spawn-based child process
creation. Note that using posix_spawnp
makes life a little easier because it uses the $PATH
environment variable to find executables.
Testing
One very important component of all software engineering is testing. Rather than doing our testing for this assignment via the autograder right away, we will be showing you how you can test the full functionality of your shell. You’re encouraged to write your own test cases to determine whether your code is working correctly.
Using input redirection to test a shell
One important concept we’ll go over this week is file redirection. In short, file redirection allows you to read your program’s input from a file, rather than from the keyboard.
For instance, if you have a file input
that has the lines:
pwd
ls
date +%Y%m%d
You can feed it to a new instance of a shell program by redirecting the input from that file by
adding the <
character as an argument, then the name of the file:
ckanich@home-desktop:~/repos/shell-skel$ sh < input
Suggested activity: Ask yourself what you expect will happen when you run
sh < input
, then create this file on your machine and feed it to the shellsh
and see what happens.
In addition to using sh
, you can also run the skeleton code for the assignment in the same way,
and see that it works properly.
Ask yourself: how can you convince yourself that it is working properly? Hint: read the output of
man diff
and try to think of a way to use that tool to determine whether your shell is running properly (note: not that it’s using posix_spawn, just that it is working as you expect it to).
Receiving redirected input vs. redirecting input ourselves
Redirected input is a tricky topic. Every process has its own input and output files, and it doesn’t care whether they are connected to terminals, files, or other cool things like network connections. By testing our shell using input redirection, our shell doesn’t need to understand input redirection. However, over the course of the next two weeks, you’ll need to add input redirection to your shell, so that, for instance, you could run a set of commands like this:
pwd
ls > list_of_files
cat < list of files
…and it would work the way that sh
works when given that set of inputs.
What you need to do for this lab
You can find your pairings for the lab in this sheet.
The peer explanations for this lab are:
Session | Task | Points |
---|---|---|
Session A | Draw a before and after diagram of the contents of buf , argc , and argv when the user runs “echo hello world.” |
1 point |
Session B | Show your evaluator what you did to switch fork with posix_spawnp to run a child process. |
1 point |
Please grade your peers out of 1 point using this form.