User Programs and Nachos System Calls


Implementing multiprogramming for user programs in Nachos requires modifying the Nachos code in two main ways:

  1. Modify the way user programs are loaded into the Mips machine memory so that multiple user programs can be in memory at the same time.
  2. Extend the Nachos code for handling exceptions since all user program Nachos system calls will cause syscall exceptions.

These two modifications are not independent. For example, one of the system calls that a user program can make is the Nachos Exec system call. The Nachos Exec must create new a Nachos thread to execute a specified user program. In particular, the compiled user program for the new thread must be loaded into the Mips machine memory.

In spite of this fact, one can implement some of the system calls before having to modify the Nachos code that loads user programs into memory.

Nachos System Calls

User programs need to make system calls to Nachos. The set of these system call interfaces and brief descriptions appear in the header file userprog/syscall.h. Take a look at that file. The 11 system calls described there are

Halt, Exit, Exec, Join, Create, Open, Read, Write, Close, Fork, and Yield.

The compilation and linking of user programs is controlled by the Makefile in the test directory. In particular a user object file (.o) is linked with the start.o to produce an executable file (in coff format). This start.o file is produced from assembler code in the file start.s. Looking at start.s, you see that there are function stubs for all the Nachos system calls: Halt, Exit, Exec, ..., etc.

Each of the stubs in start.s merely loads Mips register r2 with the number of the system call requested. (The correspondence between these numbers and the system calls is given in userprog/syscall.h.) If the system call requires parameters, these must be loaded into registers. For example, Write has three parameters. These parameters are loaded into registers r4, r5, and r6. After loading the system call number into register r2 and any necessary parameters into registers r4, etc., the stub executes the Mips instruction syscall.

The syscall instruction on a real Mips machine generates an interrupt (a software interrupt, since it is caused by an instruction, rather than a device). This causes a mode switch and traps to kernel code, thus allowing an operating system to handle the system call request in kernel mode. The Mips machine simulator on which the user programs execute, handles the syscall instruction by calling the Nachos function

ExceptionHandler(ExceptionType which)

defined in userprog/exception.cc.

The ExceptionHandler in the initial Nachos code, only handles the Halt() system call. All other calls including Exit() simply call ASSERT(FALSE), which causes Nachos to abort! As a consequence, all of the supplied user programs in directory test, cause Nachos to abort except for halt.c.

User Programs and User Address Spaces

The Thread class and the ThreadTest function in the threads directory illustrate creating a Nachos system thread. But these Nachos system threads are part of the Nachos program code, not user programs, and therefore run on the real machine as does the Nachos simulation. To execute a user program, Nachos code must

  1. Create a Thread object to represent the (initial) Nachos thread to execute the user program, and
  2. an AddressSpace object to manage the memory requirements of the user program while it executes on the simulated Mips machine.

See the files userprog/addrspace.h and userprog/addrspace.cc. An Addrspace object has member variables for the number of pages in the virtual address space and the pageTable entries for the Nachos thread.

The constructor, AddrSpace(OpenFile *executable), should be consulted to see how the noff format is read in and used to determine the number of pages required, to determine how to load the rest of the file into the simulated Mips machine's memory, and to create and initialize the page table entries for the thread.

You should understand this code thoroughly. It will need to be modified. The modification will require a relative small amount of code, but can cause lots of debugging problems if done incorrectly. You should add your own calls to DEBUG in this and other modifications to facilitate testing your code.

Assignment

Implementing all the Nachos system calls and implementing user program multiprogramming will be assigned in several parts. This is the first part.

In this part, you should do the following:

  1. Modify the exception handling code in Nachos to provide stubs for each of the Nachos system calls. Initially, each stub can simply contain a DEBUG statement with the message "xxx system call not yet implemented", and then halt the system, interrupt->Halt(), as in the initial code for the Halt system call. (Note that this only makes the improvement that all user mode system calls will simply gracefully halt Nachos and the machine, rather than aborting the code! Not what we ultimately want, but a start.)

  2. Implement the Exit system call. This Nachos user thread system call should cause that thread to terminate, but not halt the Nachos system. Note, however, that since we do not yet have user multiprogramming there will not be any other threads to run. So Nachos will shutdown gracefully, but not because of a user system call to Halt, but rather because the Interrupt::Idle code will call Interrupt::Halt().

  3. Both Halt and Exit user system calls cause the calling user thread to terminate. So in these cases the user's Mips registers aren't restored. The next step is to partially implement a system call, Write, for which the user code should continue executing (on the Mips machine) after the system call.

  4. Write and (cross) compile user programs and run them under Nachos to test your system calls.

  5. Be prepared to demonstrate your code; e.g., for any user program written that only uses these system calls.

    There is a problem here which is discussed in Narten's guide. The problem is with the program counter registers - PCReg, NextPCReg. The question is how should these registers be adjusted before returning from a system call that simply continues execution of the user thread that made the system call. For a page fault exception, the Mips instruction that caused the page fault will need to be executed again. For a system call, we should not execute the Mips instruction that caused the exception, since it was the syscall instruction, as this would cause an infinite loop of calls.

    So the question is whether the PC registers have already been incremented when the syscall is made or not. The answer is that they have not been incremented. So before returning from the Write system call, the PC registers for the calling thread must be incremented properly. Narten provides the code for doing so:

    void AdjustPCRegs()
    {
      int pc;
    
      pc = machine->ReadRegister(PCReg);
      machine->WriteRegister(PrevPCReg, pc);
      pc = machine->ReadRegister(NextPCReg);
      machine->WriteRegister(PCReg, pc);
      pc += 4;
      machine->WriteRegister(NextPCReg, pc);
      
    }
    
Write

void Write(char *buffer, int size, OpenFileId id); parameters are

  1. buffer - the beginning virtual address in the calling thread's address space of the string to write,
  2. size - the number characters to write,
  3. id - an integer representing either standard output (1) or the an file descriptor for an open output file where to write the string.

When a user program makes a Nachos system call to Write, ExceptionHandler is called, and

  1. Register r2 will contain the integer SC_Write (defined as 7 in syscall.h),
  2. Register r4 will contain the value of buffer,
  3. Register r5 will contain the value of size,
  4. Register r6 will conatin the value of id (1 for standard output).

Your exception handling code for Write will have to read these registers. (ExceptionHandler already reads register r2.)

Your code will also have to get the string to be printed from the simulated Mips machine memory. Although this memory is directly accessible ( machine->mainMemory[physicalAddress] ), you should use the routines defined in machine.h for reading and writing to virtual memory addresses:

    bool ReadMem(int addr, int size, int* value);
    bool WriteMem(int addr, int size, int value);

These functions indirectly make calls to translate the virtual memory references to physical addresses and then access the physical memory locations. They return false if no correct translation of the virtual address could be made. The initial Nachos code for user programs assumes physical = virtual memory when it loads a user program. So you could temporarily directly access the physical memory as indicated above. However, this will not be true once multiprogramming is implemented, and virtual to physical address translation will be required anyway. So you should use the ReadMem and WriteMem functions to access user program memory.

The remaining steps for the Write system call will be discussed in class.