Nachos Phase 2 Design COSC3407

1 Introduction

Nachos is divided into 2 parts: • The The operating system runs java programs as you saw in phase 1. • User programs User programs are written in C and compiled into MIPS machine code. This is a more realistic simulation of a , where the virtual nachos machine reads the machine code into memory and executes it. The main directory for this phase is the userprog directory. Each UserThread - which is a subclass of KThread - contains a reference to a UserProcess. The UserProcess method handleSyscall is called to handle system calls made by user programs. As in phase 1, read the documentation for phase 2 on the course web site very care- fully. Since the operating system should not crash because of an error in a user program, error handling is an important part of this phase.

2 Task I: - implement file system calls

There is a sample system call already done - halt(). The code for system calls should be done in UserProcess.java in the userprog directory. You will see the code to implement halt in the handleSyscall method. You can add the other system calls to this method. The documentation for the syscall numbers and parameters is listed there as well. There is also some documentation in syscall.h in the test directory.

2.1 FileSystem call - (creat, open, read, write, close, and unlink) The file system is already implemented, so all that is needed is to make them avail- able to user programs via system calls. ThreadedKernel loads a reference to the StubFileSystem in the fileSystem static variable. This fileSystem is used to imple- ment the system calls - for example ThreadedKernel.fileSystem.open(filename, true) would return a reference to an OpenFile for the file specified in the filename parame- ter (assuming it exists). You should keep track of all the open files in array, with the index to the array being the file descriptor. The descriptor is what the user program uses to refer to the file. When the file is opened or created the descriptor is returned to the user program. This is then used by the user program on subsequent read, write or close system calls to that file. File descriptors 0 and 1 are used for standard input and output. There is a static variable in UserKernel, console, that has a refer- ence to a SyncConsole object. The methods openForReading() and openForWriting() in the SyncConsole class return references to OpenFile objects, so use these for file descriptors 0 and 1. Unlink means delete, which uses the remove method in the StubFileSystem.

1 Nachos Phase 2 Design COSC3407

Some parameters are passed as virtual addresses (call by reference in logical ad- dress space), so use readVirtualMemory or readVirtualMemoryString in UserProcess to get these parameters. For example, the open method is listed as [int open(char *name)], meaning the name is passed as a pointer its first character, so use readVir- tualMemoryString to get this parameter. (more on virtual addresses in task 2). The read and write use the parameter *buffer, which is a pointer to an array of bytes, so use readVirtualMemory and writeVirtualMemory for these parameters. You also need to modify the halt method, so that is can only be executed by the root processes.

3 Task 2 - Implement support for multiprogram- ming.

Running more than one user program at a time requires that each program has its own memory. This means there must be a mapping from the program’s logical (or virtual memory) to physical memory. Physical memory is divided into frames. Logical memory is divided into pages the same size as frames.(See chapter 9 in the text book especially 9.4) The physical memory in Nachos is in 32 bit address space. It is an array of bytes called mainMemory in Processor.java in the machine directory. The page size is also in Processor.java listed as hex 400 , which is 1024 decimal or 210 . This means 10 bits are needed for the offset leaving 22 bits for the page number. You can get a reference to this memory with the Machine.processor().getMemory() method.

3.1 Frame Table You need to implement a way to track physical memory for this task. The documen- tation suggests a linked list of available frames (free space list). You may want to encapsulate this in a class called, for instance, FrameTable. There should be a method that returns a list of available frames for a request for memory. For example, if the caller requests 8 frames of memory, the method would return a list or array containing the frame numbers of 8 available frames which are then taken off the free space list. Alternatively, you could return just one frame number, and the calling program would call it in a loop, once for each page required. There should also be a method to mark a frame as available when the caller no longer needs it (i.e. add it to the free space list). Initially, all frames should be free. An example is shown in figure 1. This example assume a physical memory with only 13 frames. 1. 1 requires 4 frames of memory. 2. Process 2 requiring 2 frames and process 3 requiring 3 frame are allocated. 3. Process 2 ends, and its memory is added back to the free space list. 4. Process 4 requires 5 frames. Notices that its memory is non-contiguous.

2 Nachos Phase 2 Design COSC3407

Figure 1: Frame allocation

3.2 Loading a Program into memory Nachos can already run a single user program. The execute method calls the load method to load the program into memory. It first checks that the program is contigu- ous in logical memory (pages). It then calls loadSections to do the actual loading of the coff (Common Object File Format - a file format for executables ) sections into memory. The loadSections uses the pageTable to translate from logical (or virtual) memory to physical. The page table is an array of TranslationEntries, one from each physical frame. The first 2 arguments of the translation entries are the virtual page and the physical page (frame). Right now the same value is loaded to both fields (i.e.

3 Nachos Phase 2 Design COSC3407 the logical page and physical frame are the same). To run multiple user programs each program must have a separate memory, so the logical pages and physical frames are usually different. You must request enough memory to hold the program from the frame table which returns an array of available frame numbers. These are the numbers used for the physical pages in the translation entries. Then each page from each section is loaded into memory using the loadPage in CoffSection. Again, the pa- rameters are the virtual pages and the physical page(frame) which must be changed so that the physical and logical pages are different. The unloadSections should call the frame table to release the physical memory used. This is called when the process is finished (the method implemented in part 3). Similar changes need to be made in the readVirtualMemoryString, readVirtualMem- ory and writeVirtualMemory methods so that the correct physical memory address is used. For example, in readVirtualMemory you will see System.arraycopy(memory, vaddr, data, offset, amount). This uses the virtual address to copy the memory, but you must use the physical address corresponding to the virtual address. (This copies the number of bytes specified in amount from the memory array to the data array).

3.3 Translating addresses Note that the page table is restored in the restoreState method each time the process regains the cpu. You can use readMem and writeMem in Machine.processor (our group did not use these methods, but it looks like it would make things much easier if you use them). These routines use the information in the page table to get the correct physical addresses using the virtual (or logical addresses).

4 Task III: Implement System Calls

4.1 exec This syscall creates and executes a user process. The user process to execute is passed in the first parameter and must be the name of a .coff file. The second parameter is the numbers of arguments and the third is a pointer to the arguments (an array of string pointers). Figure 2 shows how the parameters are stored. Remember that nachos uses a 32 bit address space, so each pointer actually takes 4 bytes of memory. The example shows exec being called with 3 parameters, “ABC”, “12” and “T”. There is a pointer for each parameter that points to the starting address of a null terminated string. This call returns the child process’s ID or -1 if an error has occurred. This process ID must be a unique number for each process. You will probably want to implement a process table - a table that keeps track of all the active processes. Some sort of synchronization (i.e. a lock) is needed to make sure two processes don’t get the same ID.

4 Nachos Phase 2 Design COSC3407

Figure 2: How parameters or stored

A new user process can be created using the newUserProcess method in User- Process. The process can be executed with the execute method, also in UserProcess. This method’s parameters are the file name (the .coff file) and parameters. You need to retrieve the parameters passed in the system call and pass them to the execute method.

4.2 join Join takes a process ID its argument. For user processes, a process can only join one of its child processes, so you need some way to keep track of a ’s child processes. Use the join method you implemented in phase 1 (in KThreads which is inherited by UserThreads). The exit status (whether to processes ended normally) of the child process should be returned to the parent process (see exit syscall). This is done by making the 2nd parameter of join a pointer to an int (call by reference) - the int being the exit status. So you must store the exit status in memory at the address specified by the 2nd parameter (again this is a virtual address which must be translated to a physical one).

4.3 exit The exit system call terminates a user process. It should closes all open files and deallocate the memory allocated to the process. Remember that if this process’s parent called join, it must know the exit status. You can store the exit status in the process table, but this means you cannot remove the process from the table until the parent process has retrieved the exit status.

5 Testing

When you get to the coding part of phase 2, you need to create user programs for testing. User programs are written in C and compiled into .coff files. Use the test directory for your test programs. To compile just add the name of your file to the TARGETS section in the Makefile (in the test directory). Then use gmake (in the test directory) to compile your program into a .coff file (if there are no compile errors). To run go to the proj2 directory. Run gmake. This will compile the programs in

5 Nachos Phase 2 Design COSC3407

the userprog directory. As in phase 1, if there are any new classes, add them to the Makefile in the nachos directory - this time under userprog instead of threads. To run your test program call nachos -x testfilename.coff in the proj2 directory.

6 Test cases

As in phase 1, you will lose marks if you omit or have incomplete test cases. Think carefully about how to test the requirements of each of the tasks. The test cases should:

1. Specify what particular requirement(s) of the task the test is for.

2. Specify how you are going to test this requirement . If it is not obvious, you should explain why this test will demonstrate that this requirement is imple- mented correctly.

6