UMBC CMSC421 UMBC | CSEE | CMSC421 | Fall 1999 (Section 0101)


Programming Project #2

CMSC 421, Section 0101 (Fall 1999)

Assigned: 14 Oct 1999
Design due: 21 Oct 1999 at 11 PM
Code due: 4 Nov 1999 at 11 PM


There are several related goals for this assignment:

To do this, you'll need to understand traps, process creation and deletion, basic file I/O, and keyboard I/O. You'll also have to do some work on synchronizing user processes, but you're welcome to use your Lock and Condition code from Project #1.


Fork() and Wait()

You'll need to implement two system calls, Fork() and Wait(). Both calls deal with user processes, a necessity for a timeshared system. Fork() takes two arguments — the name of the program to run in the new process and a single integer to be passed to the new process (as is done in Unix with argc and argv[]). Fork() returns an positive integer that can be used to identify the process in future calls to the operating system. For now, assume that the memory allocated to a process by ProcessFork() is sufficient to run it; this assumption will change in Project #3.

One thing that Fork() has to do is load a program into memory. There's code in the operating system that reads all of the code from a program file and prints it out. You can modify this routine, perhaps modifying it slightly, to deposit the program code & data into memory at the appropriate location. Don't forget that the operating system uses physical addresses and programs use virtual addresses — load the program treating the start of the process' memory as address 0 and adjusting other addresses the same way. Your Fork() call should also do error checking, such as making sure that the OS isn't out of processes (or memory) and that the program being run actually exists. If there's an error, return a negative number to indicate this. You can either return the same negative number for all errors, or distinguish between errors by returning different negative numbers.

Wait() takes a single argument — a process ID (returned from Fork()). The process calling Wait() is suspended until the process identified by the argument to Wait() completes. When it completes, the process that called Wait() is resumed. You'll probably want to use locks and condition variables to implement Wait()....

Input & output

Your operating system should support simple I/O routines — the ability to read & write a single character at a time. This functionality is provided to user programs via traps, but the OS has to do some work to make the traps work properly. Keep in mind that characters may come in before the user program has actually asked for them, so you may need to use a buffer to communicate between the keyboard trap handler and the trap handler that deals with requests to get a character. Also, your user programs may not use printf; they may only use Putchar.

Disk input and output must use the file system calls provided in dlxos; see the sample code in process.c for examples of how to open, read, and close a file.

User shell

Your user shell should allow a user to type in commands and have them execute. The commands are actually DLX programs you've compiled separately, and are loaded in and run when the command is typed. There are two basic ways to run a program: foreground and background. To run a program in the foreground, type
program 1234
This will fork off a new process running "program", and pass the numeric argument 1234 to it. The shell will then wait for program to finish using the Wait() system call. Programs run in the background are called like this:
program 5678 &
This will run "program" in the background, allowing it to run at the same time as the shell (and perhaps other background processes). Immediately after forking off this process, the shell should return and allow the user to enter more commands.

The shell will use the Fork() and Wait() traps, of course. It'll also need to use traps to get and put characters in the simulator — getchar() and putchar(). You'll need to write these traps as well, though the OS code for both of them is partially done already.


There are already some basic traps provided for you, such as open() (though the code for open() doesn't do anything. Yet...) You should look at these traps as examples for the traps you'll need to implement. If you create code for new traps, you should put them into a separate file called usertraps.s, and compile this file along with every user program you write. A sample usertraps.s file has been provided for you.

Remember that every trap (user or system) must be caught explicitly by the operating system. When the OS catches a trap, it should make sure the arguments are valid and not assume anything.

You'll need at least the following traps to implement your shell

Design hints

The shell is a user program, and isn't compiled into the operating system. As such, you shouldn't compile it with the rest of the OS files. Instead, compile it separately and load it in. Since the basic operating system comes with the ability to execute user-level processes (albeit by compiling their names into the kernel...), you might want to experiment with them first and use that information to design your shell.

What to hand in

As with other projects, you'll need to hand in your design documentation and your code. Because your code may include both operating system code and user programs (such as the shell), please make sure your Makefile builds them separately.

Syllabus | Schedule | News & Notes | Grades | Feedback
Submit | Homework: 1 2 3 4 5 6 | Project: 1 2 3 4

Last updated 3 Dec 1999 by Ethan Miller (