Lab 3: System Calls and Multiprogramming
Computer Science 377
Due: Wednesday, November 17th, 11:59 PM


Please start immediately on this assignment. You will not be able to complete it in a single day.

 The second phase of Nachos is to implement system calls and support multiprogramming. As in the first assignment, we give you some of the code you need; your job is to complete the system and enhance it. This document first describes the features and files in Nachos that you need to focus on for this assignment.

 This lab is quite challenging and we strongly encourage you to form teams of two.

Understanding the Nachos Software, Part II

Before attempting to understand this assignment, you must first understand the parts of Nachos that you will need to work with. Read the following material before attempting to understand the assignment: Once you understand the relevant portions of Nachos, read the remainder of this handout in its entirety before beginning your project.

The first step is to read and understand the part of the system we have written for you. Our code can run a single user-level `C' program at a time. As a test case, we've provided
you with a trivial user program, `halt'; all halt does is to turn around and ask the operating system to shut the machine down. Run the program `nachos -x ../test/halt'. As before, trace
what happens as the user program gets loaded, runs, and invokes a system call.

The files for this assignment are:
addrspace.h & addrspace.cc, (found in the userprog directory)
    create and destroy address spaces for user programs.

syscall.h (found in the userprog directory)
    the system call interface. This file contains the signatures of the system calls you must implement and
    the codes that represent each system call when ExceptionHandler is called.

exception.cc (found in the userprog directory)
    The handler for system calls and other user-level exceptions, such as page faults. In the code we supply,
    only the `halt' system call is supported. This file explains how
    parameters are passed to system calls, where return results should be placed, and other useful hints on
    implementing the system calls.

filesys.h, openfile.h (found in the filesys directory)
    a stub defining the Nachos file system routines. For this assignment, we have implemented the Nachos
    file system by directly making the corresponding calls to the UNIX file system. This mechanism
    prevents you  from needing to write the file system as well.

machine.h (found in the machine directory)
    Emulates the part of the machine that executes user programs: main memory, processor registers, etc.
    You need to be able to read and write registers to retrieve the parameters from system calls and return
     values from system calls.

console.h & console.cc (found in the machine directory)
    emulates a terminal device using UNIX files. A terminal is (i) byte oriented, (ii) incoming bytes can be
    read and written at the same time, and (iii) bytes arrive asynchronously (as a result of user keystrokes),
    without being explicitly requested. You will call the methods provided here from your SynchConsole
    class.

system.cc (found in the threads directory)
    The file that initializes global variables when Nachos starts and cleans up before Nachos halts. If you
    add any new global variables, they should be initialized in the Initialize method in system.cc.

bitmap.h & bitmap.cc (found in the userprog directory)
    Routines for manipulating bitmaps (this might be useful for keeping track of physical page frames)

translate.h & translate.cc (found in the machine directory)
    Translation table routines. In the code we supply to run "halt", we assume that every virtual address is
    the same as its physical address -- this restricts us to running one user program at a time. You will
    generalize this to allow multiple user programs to be run concurrently. We will not ask you to
    implement virtual memory support until in assignment 3; for now, every page must be in physical
    memory.

progtest.cc (found in the userprog directory)
    Test routines for running user programs. Look at StartProcess to help understand what you need to do
    to implement the Exec system call.

timer.h (found in the machine directory)
    Defines a Timer class which implements time slices.

machine.h &  machine.cc (found in the machine directory)
    Emulates the part of the machine that executes user programs: main memory, processor registers, etc.

mipssim.h & mipssim.cc (found in the machine directory)
    Emulates the integer instruction set of a MIPS R2/3000 processor.
 

So far, all the code you have written for Nachos has been part of the operating system kernel. In a real operating system, the kernel not only uses its procedures internally, but allows user-level programs to access some of its routines them via ``system calls''.

In this assignment we are giving you a simulated CPU that models a real CPU. In fact, the simulated CPU is the same as the real CPU (a MIPS chip), but we cannot just run user programs as regular UNIX processes, because we want complete control over how many instructions are executed at a time, how the address spaces work, and how interrupts and exceptions (including system calls) are handled. We recommend that you spend some time going through machine.cc and skimming through mipsim.cc to understand the line between kernel code that's run directly on the delux machine and user code that is being simulated in nachos.

Our simulator can run normal programs compiled from C -- see the Makefile in the `test' subdirectory for an example. The compiled programs must be linked with some special flags, then converted into Nachos format, using the program ``coff2noff'' (which we supply). The only caveat is that floating point operations are not supported.

Building

Overview

Problems 1 & 2 (System Calls and Exception Handling)
In the first part of this assignment, you will implement system calls and exception handling for Nachos. You will be working with the simulated CPU that is provided by Nachos. We use a simulated CPU, because we want complete control over how many instructions are executed at a time, how the address spaces work, and how interrupts and exceptions (including system calls) are handled. The Nachos CPU simulates a MIPS chip. The simulator can run normal C programs (except that floating point operations are not supported and the only system calls supported are the ones that you implement). At present, Nachos is a uniprogramming operating system; it can run a single C program at a time. As a test case, we've provided you with a trivial user program, halt(in the test directory); all halt does is to turn around and ask the operating system to shut the machine down. Run the program

nachos -x ../test/halt
from the userprog directory. As before, trace what happens as the user program gets loaded, runs, and invokes a system call.

In the assignment, we tell you to put output statements in your code at various places. To do this, use the DEBUG procedure call, passing '4' as the first argument, as in:

DEBUG ('4', "Allocating frame %d.\n", frame);

To see this output run nachos with the -d 4 flag. This should facilitate debugging on your part and will also facilitate testing and grading. These statements must be present to
receive full credit for a portion of the assignment. Feel free to add additional statements that you feel help clarify how your code is working. (Error messages that must be
reported to the nachos user, such as when a system call is given bad parameter values, should be output with normal printf statements so that they always appear.)

 See the Makefile in the `test' subdirectory for an example of how to generate an executable program for Nachos. Among other things, this Makefile can generate the executable for halt. Briefly, C programs are compiled and linked using gcc and gld (with special flags) and are then converted to Nachos object file format (noff) using coff2noff, a utility provided by Nachos. Since Nachos runs ordinary C programs, you can make your own test programs that make the system calls you are implementing. Remember that you cannot use system calls or library routines that use system calls in your test programs. This means you cannot put printf statements in your test programs, for example.

You will be working mostly in the userprog directory.

Note that you will need to "bullet-proof" the Nachos kernel from user program errors -- there should be nothing a user program can do to crash the operating system (with the exception of explicitly asking the system to halt). Therefore, your system call implementations should do whatever error checking is necessary to ensure that they will succeed or will cause the user's program to fail, but not nachos.

Problem 3 (Multiprogramming)
In this assignment, you will implement multiprogramming for Nachos. You will test your implementation by running user programs on Nachos. At present, Nachos can run a single user-level C program at a time. You will start a user program by running Nachos with the "-x" flag as for the last lab. From the
application program perspective, multiprogramming is accomplished by having a C program "exec" other programs. That is, there is no user shell where multiple application programs are started.

Before beginning this assignment, do the following:

  1.    In machine/machine.h, change
        #define NumPhysPages    32
        to
        #define NumPhysPages    48

  2.In vm/Makefile, delete -DUSE_TLB from the line:

    DEFINES = -DUSER_PROGRAM -DFILESYS_NEEDED -DFILESYS_STUB -DVM -DUSE_TLB

  3.Rebuild the makefile (gmake depend in the vm directory).

  4.Recompile nachos (gmake nachos in the vm directory).

The Assignment

Problem 1
(5 points each) Implement system calls that interface to the file system. The signatures of the system calls are defined in syscall.h. Your implementation goes in ExceptionHandler in exception.cc. (See how halt is defined in syscall.h and implemented in exception.cc.)

Your system call implementations should check the parameters passed in to make sure they are legal and then call the equivalent kernel routines already implemented in filesys/filesys.h and filesys/openfile.h. Note that we are using the stub implementation of the file system for this assignment.

    Create
      Create a file. Remember to copy the filename from the user specified string to a kernel string. The
        file exception.cc contains the function UserToKernelString which can be used for this purpose.

    Open
      Open a file. Keep track of the ids of the opened files so that exit can close them. You should allow
        up to 16 files to be open at one time. Remember to copy the filename from the user specified string
        to a kernel string.

    Close
      Close a file. Remember to update the list of opened files.

    Read
      Read from a file. Remember to copy the contents from the kernel buffer to the user buffer after
        reading. The file exception.cc contains a function, KernelToUser, which can be used for this
        purpose.

    Write
      Write to a file. Remember to copy the contents from the user buffer to the kernel buffer before
        writing. The file exception.cc contains a function, UserToKernel, which can be used for this
        purpose. Make sure that your implementation allows read and write from the console.

Problem 2

(10 points each) Impement the Exec, Join  and Exit System Calls

(10 points) Implement exception handlers for all the exceptions defined in machine.h. For all exceptions except SyscallException, your exception handlers should print an error message and exit the user's program.
 

Problem 3

(35 points) Implement multiprogramming with time-slicing. The code we have given you is restricted to running one user program at a time. You will need to:

  1.    Come up with a way of allocating physical memory frames so that multiple programs can be loaded
         into memory at once (bitmap.h might come in handy, here.)

  2.    Provide a way of copying data to/from the kernel from/to the user's virtual address space (now that
         the addresses the user program sees are not the same as the ones the kernel sees)

 3.    Use timer interrupts to force threads to yield after a certain number of ticks. Note that scheduler.cc
        now saves and restores user machine state on context switches.
        Implement a timer so that each user process gives up the CPU after its timeslice expires. In
        threads/system.cc, there is currently code to create a timer if the program is called with the -rs flag.
        You just need to change this so that the timer is always created. All other aspects of the timer are
        already implemented. (The length of the  time slice is defined by TimerTicks defined in
        machine/stats.h.)

Tip :
You must allow each AddrSpace to have its own page table so that multiple programs may be running at the same time. This has already been done for the most part; just use the pageTable that is owned by each address space. In this assignment, you will only need to modify the page tables that each address space already has. You do not need to deal with the TLB at all. The machine will know how to find your page tables, so do not change their format. All you need to do is correctly populate the page table with the translations made when loading the program. Your page table must allow arbitrary translations. You will probably find a system-wide bitmap helpful to determine which physical pages are free. When you load the program in AddrSpace, instead of putting it at a physical page equal to the virtual page, you will find a free page in physical memory to map to that virtual page (with the page table). Be sure to free the physical memory when the address space is done.

You do not need to implement demand paging. If there is not enough memory to meet a process's needs, you can simply display an error message and abort the process. Remember to free the memory when the process exits. Output a DEBUG message for each page loaded into memory identifying the virtual page number and the physical page frame.

Currently, Nachos zeros the entire main memory when an address space is created using a call to bzero. You need to be sure that you only zero the pages actually allocated to that process.

How to Turn in Lab 3

  1. Hand in a hard copy of all the files that you modified. Remember to mark your changes:
  2. // Start changes
       put your changes here
    // End changes
    You should put all your modified files in a lab3 subdirectory of your cs377 directory.
     
  3. Hand in a hard copy of a README file that explains your implementation for each part of the assignment. README should contain:
  4. Your ~/cs377/lab3 directory should also contain the README file.

Tips

Testing System Calls: Running User Programs

Here is a sample user program. You should make up your own test programs to provide more complete testing.
/* Program to test I/O */
/* Reads, writes to stdin and stdout; opens a file and writes stuff to it. */

#include "syscall.h"
#define numloop 500

int
main()
{
  int i;
  char *ch = "A";
  OpenFileId fd;
  char buffer[24];

  i= Read(buffer,2,ConsoleInput);   /* read from console */

  Write(buffer,2,ConsoleOutput);    /* write the same thing to console */

  fd = Open("pa.c.bak"); /* open a file */
  i = Read(buffer,7,fd); /* Read from it */
  Write(buffer,i,ConsoleOutput);     // write chars just read from
                                     // file on the screen 
  Close(fd);

  /* Write a bunch of A's on the screen */
  for (i=0;i<numloop;i++)
     Write(ch,1,ConsoleOutput);

   Exit(0);
}
Here are the changes made to the Makefile in the test directory in order to compile the user program printa.c:
all: halt shell matmult sort printa
printa.o: printa.c
        $(CC) $(CFLAGS) -c printa.c
printa: printa.o start.o
        $(LD) $(LDFLAGS) start.o printa.o -o printa.coff
        ../bin/coff2noff printa.coff printa
In order to compile your user programs, make appropriate changes to the Makefile in the test directory and type make all at the prompt while in the test directory.

Testing MultiProgramming Using Exec and Join

Here are some test programs for you to use. You should make up your own test programs to more thoroughly test your code.

    test_ExecJoin.c and test_Child.c are a pair of programs that result in the creation of multiple processes. Creation stops when Nachos runs out of physical memory, then the
    threads all finish in the reverse order from the order they started in.

    test_ExecJoin_II.c, test_Child_IIa.c, and test_Child_IIb.c operate in parallel creating and writing to three different files.

    test_SynchConsole.c, test_SynchConsoleA.c, test_SynchConsoleB.c, and test_SynchConsoleC.c are processes operating in parallel, writing to the console.