Normally, an operating system runs on the machine that it manages. Nachos is unusual in that the operating system runs "side-by-side" with the simulated machine that it controls. The program nachos implements both the machine simulation and the operating system. Understanding this "side-by-side" relationship between the simulated workstation and the operating system is critical to understanding Nachos.
Although side-by-side execution is rather unusual (and unrealistic), it has some important advantages. Most importantly, since nachos is just a UNIX C++ program, you can use all of the usual program development tools while you are working on the operating system code. In particular, you can, and should, use debuggers such as gdb to locate problems in your code.
When you are getting started with Nachos, your goal should be to understand how a user program gets loaded and executed, and to understand what happens when a running user program makes a system call to request a service from the Nachos operating system.
To cause a user program to be executed, the Nachos operating system must do several things. It must load the user program's machine language instructions and data into the simulated workstation's memory. It must load initial values into the important registers of the workstation's CPU. For example, it must load the memory address of the program's first instruction into the program counter. Once the simulated memory and registers have been set up, the operating system then simply orders the simulated machine to run, via a call to the method Machine::Run. At that point, the machine simulation begins running the loaded program. It does this by simulating the fetch/execution cycle of a real MIPS processor. Essentially, the processor fetches the next program instruction from the (simulated) memory, decodes and executes it (which may change values in the simulated registers or memory), increments the program counter, and then repeats.
An important thing to understand is that once the operating system calls Machine::Run, operating system code is no longer running; the machine simulation is. The call to Machine::Run never returns. The operating system has lost control while the simulated machine executes the user program.
When the exception handler is called, the operating system has control once again. Normally, its job is to figure out why the exception occurred, and to take some action to resolve it. If the exception was caused by a system call, the operating system performs the service requested by the user program, e.g., writing a character to the console, or opening a file. If the program performed an illegal operation, the operating system must decide what to do, e.g., it may simply terminate a user program that tries to divide by zero.
The operating system indicates that it has finished handling the exception by returning from its exception handling function. This returns control to the machine simulation, which once again begins fetching and executing user program instructions.
Interrupts are very similar to exceptions. However, they are not caused by instructions executed by the running user program. Rather, interrupts are causes by the other hardware components of the simulated machine: the disk, the console, the network interface, and the timer. For example, the simulated console generates an interrupt whenever a new input character is available from the keyboard. Each time it finishes executing a user program instruction, the hardware simulation checks whether one of these devices is ready to cause an interrupt. If so, the hardware simulation invokes an operating system interrupt handling function instead of executing the next user program instruction.
Like an exception handler, an interrupt handler is an operating system function. When the interrupt handler is finished, it returns. This returns control to the hardware simulation, which then proceeds with the execution of the next user program instruction.
There are several types of interrupts: one for the disk, one for the timer, and two each for the console and the network. For each type of interrupt, the operating system must provide a separate interrupt handler. The hardware simulation calls the appropriate handler when an interrupt of a particular type occurs.
Both file systems provide (nearly) identical interfaces, which allow the operating system to create and destroy files, and to read and write data from files. The interface itself, and the two implementations, can be found in the code/filesystem directory.
The "stub" file system is implemented by translating each interface call into UNIX file system calls. Thus, when the operating system creates a file called "foo", this will be implemented by creating a UNIX file "foo", which will appear in the directory in which you are running nachos. This "stub" implementation is unrealistic, since it is reaching outside of the simulation to implement files. However, it is a very simple and convenient initial way to provide file services to be used by the operating system. For example, when the operating system wants to execute a user program, it must load the program's code and data into the simulated machine from a file. If the "stub" file system is being used, this file can be a regular UNIX file (which you will already have created in the code/test directory).
The "real" file system is implemented using the simulated workstation disk. That is, all files are stored on the simulated disk. Thus, requests to read or write file data will take some simulated time, unlike requests to the "stub" file system.
Nachos provides several command line arguments that can be used to initialize and control the "real" file system, when it is being used. The format argument (-f) can be used to tell Nachos to format the simulated disk during initialization. This creates a new, empty file system on the disk, destroying any existing file system that might have been stored there. Another argument, -cp, allows you to copy a UNIX file into the Nachos file system. This is the only way to get "outside" (i.e., UNIX) files into the Nachos file system so that they can be read and written by the Nachos operating system. For example, when using the "real" file system, it is necessary to copy executable files into the Nachos file system using -cp before they can be executed, since Nachos will otherwise have no way to access them.
The post office facility is implemented using the underlying simulated Nachos network. The implementation can be found in the code/network directory.