To sharpen your intuition about operating systems, this lab is a sort-of crash course in how to use the most common nouns and verbs in Unix to do interesting tricks.
We're going to be using various Unix system calls. Some useful introduction (or review) reading:
- Lecture 2 from Mazieres' CS140 lecture on processes.
- General processes and the Process system call API. from the useful Three Easy Pieces textbook.
- As always: the man pages. E.g.,
man 2 fork
.
The prelab will have you write two pieces of code:
-
A short 50-line program that will hopefully help you a lot in your later CS work.
-
A
start_watchdog(int watch_pid)
routine that make it easier to develop fork-exec programs (as we do in the lab) and avoid them leaving random zombie processes running because of an inopportune crash or mistake in your code.
This directory has a variety of small example programs demonstrating how to use Unix operations to accomplish goals related to this lab and others.
You should go through each one and see what it does and how it does it. I guarantee not skipping through this part will save you a lot of time later! Often they are pretty straight-forward. A few are a bit opaque, somewhat intentionally, so you have to reason about what is going on. There are various bits in the examples that will be useful to steal for the next lab and some others.
There's a reasonable chance some of these tricks serve you for the next couple of decades, at least here and there.
In my opinion, sublime
and other IDE's have crippled how people code --- after
saving a file, often there's 30-60seconds of switching screens, mouse
clicks, hunting for which foo.c
you want out of many tabs, etc before you
actually get an edit-compile-run done. I'd assert that you're more likely
to stay in a flow state and get more done if, as soon as you save code,
all the compile-run happens automatically and immediately.
So we are going to build a simple tool that that lets you do so. It works as follows.
-
You invoke it:
cmd-watch ./program-name [arg1 arg2 <...>]
-
cmd-watch
will monitor all the.c
,.h
,.S
,.s
files in the current directory and, if they are modified, execute the command./program-name
with any given arguments (e.g.,arg1
,arg2
, etc.) -
For example:
cmd-watch make
will run
make
if any of the files*.[chsS]
change in the current directory.This means you can be editing any file, save it, and then --- without moving a finger --- the code will be compiled and (depending on your makefile) run. IMO, the best coders have everything they do either require one keystroke or (better) zero. This is a good step in that direction.
Most of cmd-watch
uses similar methods as your unix-side
bootloader.
You can implement cmd-watch
as follows:
- Uses
opendir
to open the current directory and scan all entries usingreaddir
. - For any entry that has the suffix we are looking for, uses
stat
orfstat
(man 2 stat
) to get thestat
structure associated with that entry and check the modification time (s.st_mtime
). - If the modification time is more recent than the last change in the directory,
do a
fork-execvp
of the command-line arguments passed tocmd-watch
. - Have the parent process use
waitpid
to wait for the child to finish and also get its status. The parent will print if the child exited with an error.
Some gotcha's
-
If nothing changed, go to sleep for a human-scale amount, otherwise you'll burn up a lot of CPU time waiting for some entry to change. The claim is that people can't notice below 100ms, so you could sleep for 250ms without too much trouble.
-
If you keep opening the directory, make sure you close it after (
closedir
) otherwise you'll run out of file descriptors. -
Even if you find a recent-enough modification time to kick off the command, scan the rest of the directory. Otherwise you can kick off spurious executions.
There are a bunch of extensions you can do:
-
Make it easy to use different suffixes or scan in different locations.
-
Run multiple commands, for example
foo \; bar
which will runfoo
and then runbar
. Note if you use a semi-colon as a separator you will likely need to escape it or your shell will interpret it.
If a parent process P
forks a child process C
, and P
dies, then C
can keep running as a zombie. This is a general issue you have to deal
with when creating processes --- a careless mistake or an inopportune
crash can leave a bunch of them clogging up your system. This will almost
certainly happen in lab 5. So, you will build a little routine that will
stop this from occuring.
- After the parent process forks a child process with process id
pid
, the parent will callstart_watchdog(pid)
. start_watchdog
will fork a watchdog process that will use a pipe read to detect when its parent dies (see useful-examples for how you can do this!). When this occurs, it will usekill
to terminate the child and thenexit
.
Why this works:
- Because the watchdog is in a seperate process, it will not be affected by a parent crash.
- Because the
watchdog
is simple, it (hopefully) will not have bugs that lead to our original problem.
The basic idea:
- Create a pipe.
- Fork.
- If parent, return. Otherwise, in the child:
- Close all other file descriptors (for hygiene).
- Do a
read
on the pipe. When it returns send a kill to the child. - Check the return value of
kill
! In particular if it has an error it should only beESRCH
(seeuseful-examples
).
Make sure you test this code! If the parent crashes, it should never leave a child running.