This project will give you experience both working with system calls and understanding how system calls are implemented inside the operating system. The project consists of two parts. In the first part, you will write a C program making use of several UNIX system calls. In the second part, you will add a custom system call inside the Nachos instructional operating system (written in Java). You will also need to write a design document explaining how your program is organized (details below).
Part 1 - Clone Shell
We touched on how a few system calls (notably fork and exec) can be used to implement a command-line shell like Bash. In this exercise, you will implement
closh (Clone Shell), a simple shell-like program designed to run multiple copies of a program at once.
Like any other shell, closh takes as input the name of the program to run (e.g., hostname, pwd, echo, etc.). However, closh also takes three additional inputs:
- The number of copies (processes) of the program to run. This is an integer from 1 to 9.
- Whether the processes should execute concurrently or sequentially.
- Optionally, a timeout (also an integer from 1 to 9) specifying the maximum duration of each process in seconds (reset between processes if running sequentially). If a process takes longer than the timeout, it is terminated. A timeout value of zero specifies no timeout.
Closh executes the given program the specified number of times, then returns to the prompt once all processes have either completed or timed out. Here is a simple example of using closh:
sbarker@elnux2$ ./closh
closh> hostname
count> 3
[p]arallel or [s]equential> p
timeout> 5
elnux2
elnux2
elnux2
closh>
Additionally, each new process your program creates should print its process ID before executing the command, as well as any other output you would like that demonstrates how your program is operating.
We will provide a program skeleton (closh.c) that implements all required parsing and interface logic. The skeleton simply executes the given command once and exits. Your task is to replace this single system call with the real process logic of closh.
Run the following commands to download the starter files on EDLAB. While you are welcome to write code on your own machine, we will be evaluating your code on the EDLAB machines, so please make sure your final submission runs on EDLAB.
$ wget http://lass.cs.umass.edu/~shenoy/courses/377/labs/1/closh-starter.tar.gz
$ tar xzvf closh-starter.tar.gz
Tips
- Build closh using the included Makefile -- running make will regenerate the closh binary by compiling closh.c (this is just a shortcut to running "gcc -o closh closh.c"). If you get an error like 'as: unrecognized option '--32' when running make, then you've probably set up the Nachos cross compiler -- to restore the original gcc, comment out the ARCHDIR line from your .bashrc/.cshrc/.bash_profile and open up a new shell. The starter files will complain about unused variables when you initially build -- this is harmless.
- Useful functions and system calls include fork, exec (specifically the execvp variant, in conjunction with the cmdTokens variable), sleep, waitpid, and kill. You should use the SIGKILL signal value in kill to terminate a process.
- If you're trying to use waitpid and get a warning like "warning: implicit declaration of function 'waitpid'", you probably need to include an additional system header file. Add the line "#include <sys/wait.h>" to the top of your file alongside the other #include statements.
- Be careful when adding calls to fork -- if you write an infinite loop with a fork in it, a fork bomb will result. If in doubt, you can add a sleep(1); before each fork during debugging, which will slow the rate of process creation.
- Closh can execute a program with arguments, but cannot execute multiple programs using Bash constructs (e.g., 'sleep 3 && echo hello' to sleep for 3 seconds, then print hello). However, you can accomplish the same by making a new Bash file (e.g., the included 'sleephello' script) and calling that from within closh (e.g., './sleephello'). This is useful for testing that your program correctly handles both parallel and sequential execution. If you do this, make sure the script you are trying to call is executable ('chmod +x sleephello').
- If you have difficulties with C syntax or errors, email or see the TA. While minimal features of C are required for this assignment, we don't want you to spend too much time debugging issues with C itself.
Part 2 - Nachos Syscalls
The stock version of the Nachos operating system comes with only a single system call implemented - halt(). In this exercise, you will add another simple system call to gain a better understanding of how system calls are implemented in an OS. The procedure for implementing a system call in a real OS is similar.
- First, you need to have Nachos running on your system. Follow the Nachos setup tutorial here. As always, you are free to use your own machine rather than Edlab if you prefer, but please test your code on Edlab, as that is what we will be using for grading.
- We will be working from the included Nachos Project 2 (nachos/proj2) this week. Following the same procedure shown in the tutorial for building Nachos Project 1, build proj2 and ensure that you can run it. It should run a self-test of the Nachos console, then execute the halt system call, stopping Nachos.
- There are several key files we're interested in this week. The test/ directory contains the C test programs (*.c), the compiled MIPS programs that Nachos executes (*.coff), and some miscellaneous other system files. Project 2 (by default) executes the halt.coff binary (compiled from halt.c). Look in halt.c. Where is the halt() function defined? Include your answer (to this and any other questions below) in your README/design doc.
- Now let's look at how Nachos intercepts and executes system calls. The main Java class to look at is userprog/UserProcess.java. Remember that a trap is when a special event happens (such as a system call) that requires the OS to run some specific code. The Nachos trap handler is the method handleException() in UserProcess (in this context, exception is roughly equivalent to a trap). Right now, the only type of trap handled is a system call. How does the system call fetch data from the user program (e.g., function arguments) and return a value to the user program?
- Once Nachos is executing the trap handler, it needs to handle the specific system call, which is done in handleSyscall(), also in UserProcess. How does Nachos/the OS know what type of system call (e.g., halt()) is being executed? Handling the halt syscall simply involves a call to Machine.halt(), which shuts down the rest of the simulated Nachos machine.
- Now let's try adding a new system call, helloworld(int). This system call will simply print "Hello World" the specified number of times, and will return 0 on success and -1 on failure (e.g., if the user tries to print a negative number of times).
- First you need to define the new system call function and assign it an identifier to the OS. Both of these are done in test/syscall.h, and can follow the model used by other system calls. Note that #define in C is simply assigning an alias to use in place of the specified constant.
- Now we need to actually tell the OS that the new function corresponds to a system call. This is done in the tests/start.s file. This is some assembly code that the OS executes on startup that (among other things) registers system calls so the OS knows to trap on them. These syscall stubs associate the C name of the system call (exit, open, write, etc.) with the identifying constant of that syscall (which we just defined). Add a stub for the new system call.
- As an aside, we can find similar functionality by looking in our real OSes. For example, on the EDLAB machines, look in /usr/include/i386-linux-gnu/asm/unistd_64.h to see the system registering its syscalls. On Mac OS X or some other versions of Linux, a similar file may be found at /usr/include/sys/syscall.h. What's the largest system call identifier on your system?
- Now the system knows that helloworld is a system call. Let's modify halt.c by adding a call to helloworld prior to the halt call. Rebuild the test/ files (from within proj2, make test) and run Nachos (../bin/nachos). What happens and why?
- Now back within the Nachos code, add the implementation of the new system call. Remember to print multiple times as specified by the argument to the syscall. For actually doing the printing, don't use System.out -- remember, Nachos simulates its own device drivers, including those for I/O. Use the static UserKernel.console object, which is an instance of the SynchConsole class, to actually write output. To the OS, the console (otherwise known as standard out) is just a special file that you can write to like any other file to print output. To do this, you can call openForWriting() on the SynchConsole object, which will give you an instance of the OpenFile class (which actually represents the console in this case). The OpenFile class has a method write(byte[] buf, int offset, int length), which writes an array of bytes to the file (starting in the array at index offset and writing length bytes). To write a String using this method, you first need to convert the string to an array of bytes -- the getBytes() method of the String class will do this for you.
- Once you've implemented the call, re-run your modified halt.c code to verify that Nachos correctly executes your new system call.
- For this part of the project, do not submit your entire Nachos source tree - instead, submit only those files you modified (halt.c, syscall.h, start.s, and UserProcess.java), in addition to your README file.
How to Turn in Project 1
All of the following files must be submitted on Moodle as a zip file to get
full credit for this assignment.
- Your zip should contain one directory for Part 1 and a second directory for Part 2.
- Each directory should include a copy of all source files for that part.
- Each directory should also include a README file containing an outline of what you did.
It should also explain and document your design choices. Keep it short
and to the point. If your implementation does not work, you should document the
problems in the README, preferably with your explanation of why it does
not work and how you would solve it if you had more time. Of course,
you should also comment your code. We can't give you credit for something
we don't understand!
-
Fnally, each directory should contain a file showing sample output
from running your program.
- Note: We will strictly enforce policies on cheating.
Remember that we routinely run similarity checking programs on your
solutions to detect cheating. Please make sure you turn in your
own work.
You should be very careful about using code snippets you find on
the Internet. In general your code should be your own. It is OK to
read tutorials on the web and use these concepts in your
assignment. Blind use of code from web is strictly disallowed. Feel free to
check with us if you have questions on this policy. And be sure to document any
Internet sources/ tutorials you have used to complete the assignment
in your README file.
Project 1 Grading scheme
- (max 100) Total Grade
- (max 75) Part A
- (15) design document
- (25) use of fork and exec
- (5) parallel and sequential execution
- (10) process timeouts
- (10) code structure
- (10) code comments
- (max 25) Part B
- (10) design document
- (15) new syscall
Late Policy: Project 1 is due at 9 PM on Thursday, October 4.
Please refer to the course syllabus for late
policy on labs assignments. This late policy will be strictly
enforced.
Please start early so that you can submit the assignment on time.