commit 33ebdf5cfdc0547f93e9db34f20e515f9f3389a5 Author: ginger88895 Date: Sun Mar 31 04:08:58 2019 +0800 Init diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e7cf883 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +JAVADOCPARAMS = -doctitle "Nachos 5.0 Java" -protected \ + -link http://java.sun.com/j2se/1.5.0/docs/api/ + +machine = Lib Config Stats Machine TCB \ + Interrupt Timer \ + Processor TranslationEntry \ + SerialConsole StandardConsole \ + OpenFile OpenFileWithPosition ArrayFile FileSystem StubFileSystem \ + ElevatorBank ElevatorTest ElevatorGui \ + ElevatorControls ElevatorEvent ElevatorControllerInterface \ + RiderControls RiderEvent RiderInterface \ + Kernel Coff CoffSection \ + NetworkLink Packet MalformedPacketException + +security = Privilege NachosSecurityManager + +ag = AutoGrader BoatGrader + +threads = ThreadedKernel KThread Alarm \ + Scheduler ThreadQueue RoundRobinScheduler \ + Semaphore Lock Condition SynchList \ + Condition2 Communicator Rider ElevatorController \ + PriorityScheduler LotteryScheduler Boat + +userprog = UserKernel UThread UserProcess SynchConsole + +vm = VMKernel VMProcess + +network = NetKernel NetProcess PostOffice MailMessage + +ALLDIRS = machine security ag threads userprog vm network + +PACKAGES := $(patsubst %,nachos.%,$(ALLDIRS)) + +CLASSFILES := $(foreach dir,$(DIRS),$(patsubst %,nachos/$(dir)/%.class,$($(dir)))) + +.PHONY: all rmtemp clean doc hwdoc swdoc + +all: $(CLASSFILES) + +nachos/%.class: ../%.java + javac -classpath . -d . -sourcepath ../.. -g $< + +clean: + rm -f */*/*.class + +doc: + mkdir -p ../doc + javadoc $(JAVADOCPARAMS) -d ../doc -sourcepath .. $(PACKAGES) + +test: + cd ../test ; gmake + +ag: $(patsubst ../ag/%.java,nachos/ag/%.class,$(wildcard ../ag/*.java)) diff --git a/README b/README new file mode 100644 index 0000000..b0902f4 --- /dev/null +++ b/README @@ -0,0 +1,270 @@ + Nachos for Java README + +Welcome to Nachos for Java. We believe that working in Java rather than +C++ will greatly simplify the development process by preventing bugs +arising from memory management errors, and improving debugging support. + +Getting Nachos: + +Download nachos-java.tar.gz from the Projects section of the class +homepage at: + + http://www-inst.EECS.Berkeley.EDU/~cs162/ + +Unpack it with these commands: + + gunzip -c nachos-java.tar.gz | tar xf - + +Additional software: + +Nachos requires the Java Devlopment Kit, version 1.5 or later. This is +installed on all instructional machines in: + /usr/sww/lang/jdk-1.5.0_05 +To use this version of the JDK, be sure that + /usr/sww/lang/jdk-1.5.0_05/bin +is on your PATH. (This should be the case for all class accounts +already.) + +If you are working at home, you will need to download the JDK. +It is available from: + http://java.sun.com/j2se/1.5/ +Please DO NOT DOWNLOAD the JDK into your class account! Use the +preinstalled version instead. + +The build process for Nachos relies on GNU make. If you are running on +one of the instructional machines, be sure you run 'gmake', as 'make' +does not support all the features used. If you are running Linux, the +two are equivalent. If you are running Windows, you will need to +download and install a port. The most popular is the Cygnus toolkit, +available at: + + http://sources.redhat.com/cygwin/mirrors.html + +The Cygnus package includes ports of most common GNU utilities to +Windows. + +For project 2, you will need a MIPS cross compiler, which is a +specially compiled GCC which will run on one architecture (e.g. +Sparc) and produce files for the MIPS processor. These compilers +are already installed on the instructional machines, and are +available in the directory specified by the $ARCHDIR environment +variable. + +If you are working at home, you will need to get a cross-compiler +for yourself. Cross-compilers for Linux and Win32 will be available +from the CS162 Projects web page. Download the cross compiler +distribution and unpack it with the following command: + + gunzip -c mips-x86-linux-xgcc.tar.gz | tar xf - + +(Substitute the appropriate file name for mips-x86.linux-xgcc in the +above command.) You need to add the mips-x86.linux-xgcc directory to +your PATH, and set an environment variable ARCHDIR to point to this +directory. (Again, this has already been done for you on the +instructional machines.) + +Compiling Nachos: + +You should now have a directory called nachos, containing a Makefile, +this README, and a number of subdirectories. + +First, put the 'nachos/bin' directory on your PATH. This directory +contains the script 'nachos', which simply runs the Nachos code. + +To compile Nachos, go to the subdirectory for the project you wish +to compile (I will assume 'proj1/' for Project 1 in my examples), +and run: + + gmake + +This will compile those portions of Nachos which are relevant to the +project, and place the compiled .class files in the proj1/nachos +directory. + +You can now test Nachos from the proj1/ directory with: + + nachos + +You should see output resembling the following: + + nachos 5.0j initializing... config interrupt timer elevators user-check grader + *** thread 0 looped 0 times + *** thread 1 looped 0 times + *** thread 0 looped 1 times + *** thread 1 looped 1 times + *** thread 0 looped 2 times + *** thread 1 looped 2 times + *** thread 0 looped 3 times + *** thread 1 looped 3 times + *** thread 0 looped 4 times + *** thread 1 looped 4 times + Machine halting! + + Ticks: total 24750, kernel 24750, user 0 + Disk I/O: reads 0, writes 0 + Console I/O: reads 0, writes 0 + Paging: page faults 0, TLB misses 0 + Network I/O: received 0, sent 0 + +This is the correct output for the "bare bones" Nachos, without any of +the features you will add during the projects. + +If you are working on a project which runs user programs (projects 2-4), +you will also need to compile the MIPS test programs with: + + gmake test + +Command Line Arguments: + +For a summary of the command line arguments, run: + + nachos -h + +The commands are: + + -d + Enable some debug flags, e.g. -d ti + + -h + Print this help message. + + -s + Specify the seed for the random number generator + + -x + Specify a program that UserKernel.run() should execute, + instead of the value of the configuration variable + Kernel.shellProgram + + -z + print the copyright message + + -- + Specify an autograder class to use, instead of + nachos.ag.AutoGrader + + -# + Specify the argument string to pass to the autograder. + + -[] + Specifiy a config file to use, instead of nachos.conf + + +Nachos offers the following debug flags: + + c: COFF loader info + i: HW interrupt controller info + p: processor info + m: disassembly + M: more disassembly + t: thread info + a: process info (formerly "address space", hence a) + +To use multiple debug flags, clump them all together. For example, to +monitor coff info and process info, run: + + nachos -d ac + +nachos.conf: + +When Nachos starts, it reads in nachos.conf from the current +directory. It contains a bunch of keys and values, in the simple +format "key = value" with one key/value pair per line. To change the +default scheduler, default shell program, to change the amount of +memory the simulator provides, or to reduce network reliability, modify +this file. + +Machine.stubFileSystem: + Specifies whether the machine should provide a stub file system. A + stub file system just provides direct access to the test directory. + Since we're not doing the file system project, this should always + be true. + +Machine.processor: + Specifies whether the machine should provide a MIPS processor. In + the first project, we only run kernel code, so this is false. In + the other projects it should be true. + +Machine.console: + Specifies whether the machine should provide a console. Again, the + first project doesn't need it, but the rest of them do. + +Machine.disk: + Specifies whether the machine should provide a simulated disk. No + file system project, so this should always be false. + +ElevatorBank.allowElevatorGUI: + Normally true. When we grade, this will be false, to prevent + malicious students from running a GUI during grading. + +NachosSecurityManager.fullySecure: + Normally false. When we grade, this will be true, to enable + additional security checks. + +Kernel.kernel: + Specifies what kernel class to dynmically load. For proj1, this is + nachos.threads.ThreadedKernel. For proj2, this should be + nachos.userprog.UserKernel. For proj3, nachos.vm.VMKernel. For + proj4, nachos.network.NetKernel. + +Processor.usingTLB: + Specifies whether the MIPS processor provides a page table + interface or a TLB interface. In page table mode (proj2), the + processor accesses an arbitrarily large kernel data structure to do + address translation. In TLB mode (proj3 and proj4), the processor + maintains a small TLB (4 entries). + +Processor.numPhysPages: + The number of pages of physical memory. Each page is 1K. This is + normally 64, but we can lower it in proj3 to see whether projects + thrash or crash. + +Documentation: + +The JDK provides a command to create a set of HTML pages showing all +classes and methods in program. We will make these pages available on +the webpage, but you can create your own for your home machine by doing +the following (from the nachos/ directory): + + mkdir ../doc + gmake doc + +Troubleshooting: + +If you receive an error about "class not found exception", it may be +because you have not set the CLASSPATH environment variable. Add the +following to your .cshrc: + + setenv CLASSPATH . + +Credits: + +Nachos was originally written by Wayne A. Christopher, Steven J. +Procter, and Thomas E. Anderson. It incorporates the SPIM simulator +written by John Ousterhout. Nachos was rewritten in Java by Daniel +Hettena. + +Copyright: + +Copyright (c) 1992-2001 The Regents of the University of California. +All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without written +agreement is hereby granted, provided that the above copyright notice +and the following two paragraphs appear in all copies of this +software. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE +PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF +CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +ENHANCEMENTS, OR MODIFICATIONS. + diff --git a/ag/AutoGrader.java b/ag/AutoGrader.java new file mode 100644 index 0000000..b81279c --- /dev/null +++ b/ag/AutoGrader.java @@ -0,0 +1,279 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.ag; + +import nachos.machine.*; +import nachos.security.*; +import nachos.threads.*; + +import java.util.Hashtable; +import java.util.StringTokenizer; + +/** + * The default autograder. Loads the kernel, and then tests it using + * Kernel.selfTest(). + */ +public class AutoGrader { + /** + * Allocate a new autograder. + */ + public AutoGrader() { + } + + /** + * Start this autograder. Extract the -# arguments, call + * init(), load and initialize the kernel, and call + * run(). + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + */ + public void start(Privilege privilege) { + Lib.assertTrue(this.privilege == null, + "start() called multiple times"); + this.privilege = privilege; + + String[] args = Machine.getCommandLineArguments(); + + extractArguments(args); + + System.out.print(" grader"); + + init(); + + System.out.print("\n"); + + kernel = + (Kernel) Lib.constructObject(Config.getString("Kernel.kernel")); + kernel.initialize(args); + + run(); + } + + private void extractArguments(String[] args) { + String testArgsString = Config.getString("AutoGrader.testArgs"); + if (testArgsString == null) { + testArgsString = ""; + } + + for (int i=0; i 0 && arg.charAt(0) == '-') { + if (arg.equals("-#")) { + Lib.assertTrue(i < args.length, + "-# switch missing argument"); + testArgsString = args[i++]; + } + } + } + + StringTokenizer st = new StringTokenizer(testArgsString, ",\n\t\f\r"); + + while (st.hasMoreTokens()) { + StringTokenizer pair = new StringTokenizer(st.nextToken(), "="); + + Lib.assertTrue(pair.hasMoreTokens(), + "test argument missing key"); + String key = pair.nextToken(); + + Lib.assertTrue(pair.hasMoreTokens(), + "test argument missing value"); + String value = pair.nextToken(); + + testArgs.put(key, value); + } + } + + String getStringArgument(String key) { + String value = (String) testArgs.get(key); + Lib.assertTrue(value != null, + "getStringArgument(" + key + ") failed to find key"); + return value; + } + + int getIntegerArgument(String key) { + try { + return Integer.parseInt(getStringArgument(key)); + } + catch (NumberFormatException e) { + Lib.assertNotReached("getIntegerArgument(" + key + ") failed: " + + "value is not an integer"); + return 0; + } + } + + boolean getBooleanArgument(String key) { + String value = getStringArgument(key); + + if (value.equals("1") || value.toLowerCase().equals("true")) { + return true; + } + else if (value.equals("0") || value.toLowerCase().equals("false")) { + return false; + } + else { + Lib.assertNotReached("getBooleanArgument(" + key + ") failed: " + + "value is not a boolean"); + return false; + } + } + + long getTime() { + return privilege.stats.totalTicks; + } + + void targetLevel(int targetLevel) { + this.targetLevel = targetLevel; + } + + void level(int level) { + this.level++; + Lib.assertTrue(level == this.level, + "level() advanced more than one step: test jumped ahead"); + + if (level == targetLevel) + done(); + } + + private int level = 0, targetLevel = 0; + + void done() { + System.out.print("\nsuccess\n"); + privilege.exit(162); + } + + private Hashtable testArgs = + new Hashtable(); + + void init() { + } + + void run() { + kernel.selfTest(); + kernel.run(); + kernel.terminate(); + } + + Privilege privilege = null; + Kernel kernel; + + /** + * Notify the autograder that the specified thread is the idle thread. + * KThread.createIdleThread() must call this method before + * forking the idle thread. + * + * @param idleThread the idle thread. + */ + public void setIdleThread(KThread idleThread) { + } + + /** + * Notify the autograder that the specified thread has moved to the ready + * state. KThread.ready() must call this method before + * returning. + * + * @param thread the thread that has been added to the ready set. + */ + public void readyThread(KThread thread) { + } + + /** + * Notify the autograder that the specified thread is now running. + * KThread.restoreState() must call this method before + * returning. + * + * @param thread the thread that is now running. + */ + public void runningThread(KThread thread) { + privilege.tcb.associateThread(thread); + currentThread = thread; + } + + /** + * Notify the autograder that the current thread has finished. + * KThread.finish() must call this method before putting + * the thread to sleep and scheduling its TCB to be destroyed. + */ + public void finishingCurrentThread() { + privilege.tcb.authorizeDestroy(currentThread); + } + + /** + * Notify the autograder that a timer interrupt occurred and was handled by + * software if a timer interrupt handler was installed. Called by the + * hardware timer. + * + * @param privilege proves the authenticity of this call. + * @param time the actual time at which the timer interrupt was + * issued. + */ + public void timerInterrupt(Privilege privilege, long time) { + Lib.assertTrue(privilege == this.privilege, + "security violation"); + } + + /** + * Notify the autograder that a user program executed a syscall + * instruction. + * + * @param privilege proves the authenticity of this call. + * @return true if the kernel exception handler should be called. + */ + public boolean exceptionHandler(Privilege privilege) { + Lib.assertTrue(privilege == this.privilege, + "security violation"); + return true; + } + + /** + * Notify the autograder that Processor.run() was invoked. This + * can be used to simulate user programs. + * + * @param privilege proves the authenticity of this call. + */ + public void runProcessor(Privilege privilege) { + Lib.assertTrue(privilege == this.privilege, + "security violation"); + } + + /** + * Notify the autograder that a COFF loader is being constructed for the + * specified file. The autograder can use this to provide its own COFF + * loader, or return null to use the default loader. + * + * @param file the executable file being loaded. + * @return a loader to use in loading the file, or null to use + * the default. + */ + public Coff createLoader(OpenFile file) { + return null; + } + + /** + * Request permission to send a packet. The autograder can use this to drop + * packets very selectively. + * + * @param privilege proves the authenticity of this call. + * @return true if the packet should be sent. + */ + public boolean canSendPacket(Privilege privilege) { + Lib.assertTrue(privilege == this.privilege, + "security violation"); + return true; + } + + /** + * Request permission to receive a packet. The autograder can use this to + * drop packets very selectively. + * + * @param privilege proves the authenticity of this call. + * @return true if the packet should be delivered to the kernel. + */ + public boolean canReceivePacket(Privilege privilege) { + Lib.assertTrue(privilege == this.privilege, + "security violation"); + return true; + } + + private KThread currentThread; +} diff --git a/ag/BoatGrader.java b/ag/BoatGrader.java new file mode 100644 index 0000000..e642844 --- /dev/null +++ b/ag/BoatGrader.java @@ -0,0 +1,81 @@ +package nachos.ag; + +public class BoatGrader { + + /** + * BoatGrader consists of functions to be called to show that + * your solution is properly synchronized. This version simply + * prints messages to standard out, so that you can watch it. + * You cannot submit this file, as we will be using our own + * version of it during grading. + + * Note that this file includes all possible variants of how + * someone can get from one island to another. Inclusion in + * this class does not imply that any of the indicated actions + * are a good idea or even allowed. + */ + + //NEW ADDITION FOR 2014 + //MUST BE CALLED AT THE START OF CHILDITINERARY! + public void initializeChild(){ + System.out.println("A child has forked."); + } + + //NEW ADDITION FOR 2014 + //MUST BE CALLED AT THE START OF ADULTITINERARY! + public void initializeAdult(){ + System.out.println("An adult as forked."); + } + + /* ChildRowToMolokai should be called when a child pilots the boat + from Oahu to Molokai */ + public void ChildRowToMolokai() { + System.out.println("**Child rowing to Molokai."); + } + + /* ChildRowToOahu should be called when a child pilots the boat + from Molokai to Oahu*/ + public void ChildRowToOahu() { + System.out.println("**Child rowing to Oahu."); + } + + /* ChildRideToMolokai should be called when a child not piloting + the boat disembarks on Molokai */ + public void ChildRideToMolokai() { + System.out.println("**Child arrived on Molokai as a passenger."); + } + + /* ChildRideToOahu should be called when a child not piloting + the boat disembarks on Oahu */ + public void ChildRideToOahu() { + System.out.println("**Child arrived on Oahu as a passenger."); + } + + /* AdultRowToMolokai should be called when a adult pilots the boat + from Oahu to Molokai */ + public void AdultRowToMolokai() { + System.out.println("**Adult rowing to Molokai."); + } + + /* AdultRowToOahu should be called when a adult pilots the boat + from Molokai to Oahu */ + public void AdultRowToOahu() { + System.out.println("**Adult rowing to Oahu."); + } + + /* AdultRideToMolokai should be called when an adult not piloting + the boat disembarks on Molokai */ + public void AdultRideToMolokai() { + System.out.println("**Adult arrived on Molokai as a passenger."); + } + + /* AdultRideToOahu should be called when an adult not piloting + the boat disembarks on Oahu */ + public void AdultRideToOahu() { + System.out.println("**Adult arrived on Oahu as a passenger."); + } +} + + + + diff --git a/ag/package.html b/ag/package.html new file mode 100644 index 0000000..04aa1f6 --- /dev/null +++ b/ag/package.html @@ -0,0 +1,3 @@ + +Provides classes that can be used to automatically grade Nachos projects. + diff --git a/bin/nachos b/bin/nachos new file mode 100644 index 0000000..46c6bce --- /dev/null +++ b/bin/nachos @@ -0,0 +1,15 @@ +#!/bin/sh + +# Shell-script front-end to run Nachos. +# Simply sets terminal to a minimum of one byte to complete a read and +# disables character echo. Restores original terminal state on exit. + +onexit () { + stty $OLDSTTYSTATE +} + +OLDSTTYSTATE=`stty -g` +trap onexit 0 +stty -icanon min 1 -echo +java nachos.machine.Machine $* + diff --git a/machine/ArrayFile.java b/machine/ArrayFile.java new file mode 100644 index 0000000..e2133fa --- /dev/null +++ b/machine/ArrayFile.java @@ -0,0 +1,44 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * A read-only OpenFile backed by a byte array. + */ +public class ArrayFile extends OpenFileWithPosition { + /** + * Allocate a new ArrayFile. + * + * @param array the array backing this file. + */ + public ArrayFile(byte[] array) { + this.array = array; + } + + public int length() { + return array.length; + } + + public void close() { + array = null; + } + + public int read(int position, byte[] buf, int offset, int length) { + Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= buf.length); + + if (position < 0 || position >= array.length) + return 0; + + length = Math.min(length, array.length-position); + System.arraycopy(array, position, buf, offset, length); + + return length; + } + + public int write(int position, byte[] buf, int offset, int length) { + return 0; + } + + private byte[] array; +} + diff --git a/machine/Coff.java b/machine/Coff.java new file mode 100644 index 0000000..c25fedb --- /dev/null +++ b/machine/Coff.java @@ -0,0 +1,151 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import java.io.EOFException; + +/** + * A COFF (common object file format) loader. + */ +public class Coff { + /** + * Allocate a new Coff object. + */ + protected Coff() { + file = null; + entryPoint = 0; + sections = null; + } + + /** + * Load the COFF executable in the specified file. + * + *

+ * Notes: + *

    + *
  1. If the constructor returns successfully, the file becomes the + * property of this loader, and should not be accessed any further. + *
  2. The autograder expects this loader class to be used. Do not load + * sections through any other mechanism. + *
  3. This loader will verify that the file is backed by a file system, + * by asserting that read() operations take non-zero simulated time to + * complete. Do not supply a file backed by a simulated cache (the primary + * purpose of this restriction is to prevent sections from being loaded + * instantaneously while handling page faults). + *
+ * + * @param file the file containing the executable. + * @exception EOFException if the executable is corrupt. + */ + public Coff(OpenFile file) throws EOFException { + this.file = file; + + Coff coff = Machine.autoGrader().createLoader(file); + + if (coff != null) { + this.entryPoint = coff.entryPoint; + this.sections = coff.sections; + } + else { + byte[] headers = new byte[headerLength+aoutHeaderLength]; + + if (file.length() < headers.length) { + Lib.debug(dbgCoff, "\tfile is not executable"); + throw new EOFException(); + } + + Lib.strictReadFile(file, 0, headers, 0, headers.length); + + int magic = Lib.bytesToUnsignedShort(headers, 0); + int numSections = Lib.bytesToUnsignedShort(headers, 2); + int optionalHeaderLength = Lib.bytesToUnsignedShort(headers, 16); + int flags = Lib.bytesToUnsignedShort(headers, 18); + entryPoint = Lib.bytesToInt(headers, headerLength+16); + + if (magic != 0x0162) { + Lib.debug(dbgCoff, "\tincorrect magic number"); + throw new EOFException(); + } + if (numSections < 2 || numSections > 10) { + Lib.debug(dbgCoff, "\tbad section count"); + throw new EOFException(); + } + if ((flags & 0x0003) != 0x0003) { + Lib.debug(dbgCoff, "\tbad header flags"); + throw new EOFException(); + } + + int offset = headerLength + optionalHeaderLength; + + sections = new CoffSection[numSections]; + for (int s=0; s0 through getNumSections() - + * 1. + * + * @param sectionNumber the section to select. + * @return an object that can be used to access the specified section. + */ + public CoffSection getSection(int sectionNumber) { + Lib.assertTrue(sectionNumber >= 0 && sectionNumber < sections.length); + + return sections[sectionNumber]; + } + + /** + * Return the program entry point. This is the value that to which the PC + * register should be initialized to before running the program. + * + * @return the program entry point. + */ + public int getEntryPoint() { + Lib.assertTrue(file != null); + + return entryPoint; + } + + /** + * Close the executable file and release any resources allocated by this + * loader. + */ + public void close() { + file.close(); + + sections = null; + } + + private OpenFile file; + + /** The virtual address of the first instruction of the program. */ + protected int entryPoint; + /** The sections in this COFF executable. */ + protected CoffSection sections[]; + + private static final int headerLength = 20; + private static final int aoutHeaderLength = 28; + + private static final char dbgCoff = 'c'; +} diff --git a/machine/CoffSection.java b/machine/CoffSection.java new file mode 100644 index 0000000..2fb684d --- /dev/null +++ b/machine/CoffSection.java @@ -0,0 +1,228 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; + +import java.io.EOFException; +import java.util.Arrays; + +/** + * A CoffSection manages a single section within a COFF executable. + */ +public class CoffSection { + /** + * Allocate a new COFF section with the specified parameters. + * + * @param coff the COFF object to which this section belongs. + * @param name the COFF name of this section. + * @param executable true if this section contains code. + * @param readOnly true if this section is read-only. + * @param numPages the number of virtual pages in this section. + * @param firstVPN the first virtual page number used by this. + */ + protected CoffSection(Coff coff, String name, boolean executable, + boolean readOnly, int numPages, int firstVPN) { + this.coff = coff; + this.name = name; + this.executable = executable; + this.readOnly = readOnly; + this.numPages = numPages; + this.firstVPN = firstVPN; + + file = null; + size = 0; + contentOffset = 0; + initialized = true; + } + + /** + * Load a COFF section from an executable. + * + * @param file the file containing the executable. + * @param headerOffset the offset of the section header in the + * executable. + * + * @exception EOFException if an error occurs. + */ + public CoffSection(OpenFile file, Coff coff, + int headerOffset) throws EOFException { + this.file = file; + this.coff = coff; + + Lib.assertTrue(headerOffset >= 0); + if (headerOffset+headerLength > file.length()) { + Lib.debug(dbgCoffSection, "\tsection header truncated"); + throw new EOFException(); + } + + byte[] buf = new byte[headerLength]; + Lib.strictReadFile(file, headerOffset, buf, 0, headerLength); + + name = Lib.bytesToString(buf, 0, 8); + int vaddr = Lib.bytesToInt(buf, 12); + size = Lib.bytesToInt(buf, 16); + contentOffset = Lib.bytesToInt(buf, 20); + int numRelocations = Lib.bytesToUnsignedShort(buf, 32); + int flags = Lib.bytesToInt(buf, 36); + + if (numRelocations != 0) { + Lib.debug(dbgCoffSection, "\tsection needs relocation"); + throw new EOFException(); + } + + switch (flags & 0x0FFF) { + case 0x0020: + executable = true; + readOnly = true; + initialized = true; + break; + case 0x0040: + executable = false; + readOnly = false; + initialized = true; + break; + case 0x0080: + executable = false; + readOnly = false; + initialized = false; + break; + case 0x0100: + executable = false; + readOnly = true; + initialized = true; + break; + default: + Lib.debug(dbgCoffSection, "\tinvalid section flags: " + flags); + throw new EOFException(); + } + + if (vaddr%Processor.pageSize != 0 || size < 0 || + initialized && (contentOffset < 0 || + contentOffset+size > file.length())) { + Lib.debug(dbgCoffSection, "\tinvalid section addresses: " + + "vaddr=" + vaddr + " size=" + size + + " contentOffset=" + contentOffset); + throw new EOFException(); + } + + numPages = Lib.divRoundUp(size, Processor.pageSize); + firstVPN = vaddr / Processor.pageSize; + } + + /** + * Return the COFF object used to load this executable instance. + * + * @return the COFF object corresponding to this section. + */ + public Coff getCoff() { + return coff; + } + + /** + * Return the name of this section. + * + * @return the name of this section. + */ + public String getName() { + return name; + } + + /** + * Test whether this section is read-only. + * + * @return true if this section should never be written. + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Test whether this section is initialized. Loading a page from an + * initialized section requires a disk access, while loading a page from an + * uninitialized section requires only zero-filling the page. + * + * @return true if this section contains initialized data in the + * executable. + */ + public boolean isInitialzed() { + return initialized; + } + + /** + * Return the length of this section in pages. + * + * @return the number of pages in this section. + */ + public int getLength() { + return numPages; + } + + /** + * Return the first virtual page number used by this section. + * + * @return the first virtual page number used by this section. + */ + public int getFirstVPN() { + return firstVPN; + } + + /** + * Load a page from this segment into physical memory. + * + * @param spn the page number within this segment. + * @param ppn the physical page to load into. + */ + public void loadPage(int spn, int ppn) { + Lib.assertTrue(file != null); + + Lib.assertTrue(spn>=0 && spn=0 && ppn 0) + Lib.strictReadFile(file, faddr, memory, paddr, initlen); + + Arrays.fill(memory, paddr+initlen, paddr+pageSize, (byte) 0); + } + + /** The COFF object to which this section belongs. */ + protected Coff coff; + /** The COFF name of this section. */ + protected String name; + /** True if this section contains code. */ + protected boolean executable; + /** True if this section is read-only. */ + protected boolean readOnly; + /** True if this section contains initialized data. */ + protected boolean initialized; + + /** The number of virtual pages in this section. */ + protected int numPages; + /** The first virtual page number used by this section. */ + protected int firstVPN; + + private OpenFile file; + private int contentOffset, size; + + /** The length of a COFF section header. */ + public static final int headerLength = 40; + + private static final char dbgCoffSection = 'c'; +} diff --git a/machine/Config.java b/machine/Config.java new file mode 100644 index 0000000..60b69c9 --- /dev/null +++ b/machine/Config.java @@ -0,0 +1,283 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import java.util.HashMap; +import java.io.File; +import java.io.FileReader; +import java.io.Reader; +import java.io.StreamTokenizer; + +/** + * Provides routines to access the Nachos configuration. + */ +public final class Config { + /** + * Load configuration information from the specified file. Must be called + * before the Nachos security manager is installed. + * + * @param fileName the name of the file containing the + * configuration to use. + */ + public static void load(String fileName) { + System.out.print(" config"); + + Lib.assertTrue(!loaded); + loaded = true; + + configFile = fileName; + + try { + config = new HashMap(); + + File file = new File(configFile); + Reader reader = new FileReader(file); + StreamTokenizer s = new StreamTokenizer(reader); + + s.resetSyntax(); + s.whitespaceChars(0x00, 0x20); + s.wordChars(0x21, 0xFF); + s.eolIsSignificant(true); + s.commentChar('#'); + s.quoteChar('"'); + + int line = 1; + + s.nextToken(); + + while (true) { + if (s.ttype == StreamTokenizer.TT_EOF) + break; + + if (s.ttype == StreamTokenizer.TT_EOL) { + line++; + s.nextToken(); + continue; + } + + if (s.ttype != StreamTokenizer.TT_WORD) + loadError(line); + + String key = s.sval; + + if (s.nextToken() != StreamTokenizer.TT_WORD || + !s.sval.equals("=")) + loadError(line); + + if (s.nextToken() != StreamTokenizer.TT_WORD && s.ttype != '"') + loadError(line); + + String value = s.sval; + + // ignore everything after first string + while (s.nextToken() != StreamTokenizer.TT_EOL && + s.ttype != StreamTokenizer.TT_EOF); + + if (config.get(key) != null) + loadError(line); + + config.put(key, value); + line++; + } + } + catch (Throwable e) { + System.err.println("Error loading " + configFile); + System.exit(1); + } + } + + private static void loadError(int line) { + System.err.println("Error in " + configFile + " line " + line); + System.exit(1); + } + + private static void configError(String message) { + System.err.println(""); + System.err.println("Error in " + configFile + ": " + message); + System.exit(1); + } + + /** + * Get the value of a key in nachos.conf. + * + * @param key the key to look up. + * @return the value of the specified key, or null if it is not + * present. + */ + public static String getString(String key) { + return (String) config.get(key); + } + + /** + * Get the value of a key in nachos.conf, returning the specified + * default if the key does not exist. + * + * @param key the key to look up. + * @param defaultValue the value to return if the key does not exist. + * @return the value of the specified key, or defaultValue if it + * is not present. + */ + public static String getString(String key, String defaultValue) { + String result = getString(key); + + if (result == null) + return defaultValue; + + return result; + } + + private static Integer requestInteger(String key) { + try { + String value = getString(key); + if (value == null) + return null; + + return new Integer(value); + } + catch (NumberFormatException e) { + configError(key + " should be an integer"); + + Lib.assertNotReached(); + return null; + } + } + + /** + * Get the value of an integer key in nachos.conf. + * + * @param key the key to look up. + * @return the value of the specified key. + */ + public static int getInteger(String key) { + Integer result = requestInteger(key); + + if (result == null) + configError("missing int " + key); + + return result.intValue(); + } + + /** + * Get the value of an integer key in nachos.conf, returning the + * specified default if the key does not exist. + * + * @param key the key to look up. + * @param defaultValue the value to return if the key does not exist. + * @return the value of the specified key, or defaultValue if the + * key does not exist. + */ + public static int getInteger(String key, int defaultValue) { + Integer result = requestInteger(key); + + if (result == null) + return defaultValue; + + return result.intValue(); + } + + private static Double requestDouble(String key) { + try { + String value = getString(key); + if (value == null) + return null; + + return new Double(value); + } + catch (NumberFormatException e) { + configError(key + " should be a double"); + + Lib.assertNotReached(); + return null; + } + } + + /** + * Get the value of a double key in nachos.conf. + * + * @param key the key to look up. + * @return the value of the specified key. + */ + public static double getDouble(String key) { + Double result = requestDouble(key); + + if (result == null) + configError("missing double " + key); + + return result.doubleValue(); + } + + /** + * Get the value of a double key in nachos.conf, returning the + * specified default if the key does not exist. + * + * @param key the key to look up. + * @param defaultValue the value to return if the key does not exist. + * @return the value of the specified key, or defaultValue if the + * key does not exist. + */ + public static double getDouble(String key, double defaultValue) { + Double result = requestDouble(key); + + if (result == null) + return defaultValue; + + return result.doubleValue(); + } + + private static Boolean requestBoolean(String key) { + String value = getString(key); + + if (value == null) + return null; + + if (value.equals("1") || value.toLowerCase().equals("true")) { + return Boolean.TRUE; + } + else if (value.equals("0") || value.toLowerCase().equals("false")) { + return Boolean.FALSE; + } + else { + configError(key + " should be a boolean"); + + Lib.assertNotReached(); + return null; + } + } + + /** + * Get the value of a boolean key in nachos.conf. + * + * @param key the key to look up. + * @return the value of the specified key. + */ + public static boolean getBoolean(String key) { + Boolean result = requestBoolean(key); + + if (result == null) + configError("missing boolean " + key); + + return result.booleanValue(); + } + + /** + * Get the value of a boolean key in nachos.conf, returning the + * specified default if the key does not exist. + * + * @param key the key to look up. + * @param defaultValue the value to return if the key does not exist. + * @return the value of the specified key, or defaultValue if the + * key does not exist. + */ + public static boolean getBoolean(String key, boolean defaultValue) { + Boolean result = requestBoolean(key); + + if (result == null) + return defaultValue; + + return result.booleanValue(); + } + + private static boolean loaded = false; + private static String configFile; + private static HashMap config; +} diff --git a/machine/ElevatorBank.java b/machine/ElevatorBank.java new file mode 100644 index 0000000..4daf517 --- /dev/null +++ b/machine/ElevatorBank.java @@ -0,0 +1,620 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; +import nachos.threads.KThread; +import nachos.threads.Semaphore; + +import java.util.Vector; +import java.util.LinkedList; +import java.util.Iterator; + +/** + * A bank of elevators. + */ +public final class ElevatorBank implements Runnable { + /** Indicates an elevator intends to move down. */ + public static final int dirDown = -1; + /** Indicates an elevator intends not to move. */ + public static final int dirNeither = 0; + /** Indicates an elevator intends to move up. */ + public static final int dirUp = 1; + + /** + * Allocate a new elevator bank. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + */ + public ElevatorBank(Privilege privilege) { + System.out.print(" elevators"); + + this.privilege = privilege; + + simulationStarted = false; + } + + /** + * Initialize this elevator bank with the specified number of elevators and + * the specified number of floors. The software elevator controller must + * also be specified. This elevator must not already be running a + * simulation. + * + * @param numElevators the number of elevators in the bank. + * @param numFloors the number of floors in the bank. + * @param controller the elevator controller. + */ + public void init(int numElevators, int numFloors, + ElevatorControllerInterface controller) { + Lib.assertTrue(!simulationStarted); + + this.numElevators = numElevators; + this.numFloors = numFloors; + + manager = new ElevatorManager(controller); + + elevators = new ElevatorState[numElevators]; + for (int i=0; i(); + + enableGui = false; + gui = null; + } + + /** + * Add a rider to the simulation. This method must not be called after + * run() is called. + * + * @param rider the rider to add. + * @param floor the floor the rider will start on. + * @param stops the array to pass to the rider's initialize() + * method. + * @return the controls that will be given to the rider. + */ + public RiderControls addRider(RiderInterface rider, + int floor, int[] stops) { + Lib.assertTrue(!simulationStarted); + + RiderControls controls = new RiderState(rider, floor, stops); + ridersVector.addElement(controls); + numRiders++; + return controls; + } + + /** + * Create a GUI for this elevator bank. + */ + public void enableGui() { + Lib.assertTrue(!simulationStarted); + Lib.assertTrue(Config.getBoolean("ElevatorBank.allowElevatorGUI")); + + enableGui = true; + } + + /** + * Run a simulation. Initialize all elevators and riders, and then + * fork threads to each of their run() methods. Return when the + * simulation is finished. + */ + public void run() { + Lib.assertTrue(!simulationStarted); + simulationStarted = true; + + riders = new RiderState[numRiders]; + ridersVector.toArray(riders); + + if (enableGui) { + privilege.doPrivileged(new Runnable() { + public void run() { initGui(); } + }); + } + + for (int i=0; i= 0 && elevator < numElevators); + direction = elevators[elevator].direction; + } + + RiderEvent e = new RiderEvent(event, floor, elevator, direction); + for (int i=0; i= 0 && elevator < numElevators); + postRiderEvent(RiderEvent.eventDoorsOpened, + elevators[elevator].openDoors(), elevator); + + if (gui != null) { + if (elevators[elevator].direction == dirUp) + gui.clearUpButton(elevators[elevator].floor); + else if (elevators[elevator].direction == dirDown) + gui.clearDownButton(elevators[elevator].floor); + + gui.openDoors(elevator); + } + } + + public void closeDoors(int elevator) { + Lib.assertTrue(elevator >= 0 && elevator < numElevators); + postRiderEvent(RiderEvent.eventDoorsClosed, + elevators[elevator].closeDoors(), elevator); + + if (gui != null) + gui.closeDoors(elevator); + } + + public boolean moveTo(int floor, int elevator) { + Lib.assertTrue(floor >= 0 && floor < numFloors); + Lib.assertTrue(elevator >= 0 && elevator < numElevators); + + if (!elevators[elevator].moveTo(floor)) + return false; + + schedule(Stats.ElevatorTicks); + return true; + } + + public int getFloor(int elevator) { + Lib.assertTrue(elevator >= 0 && elevator < numElevators); + return elevators[elevator].floor; + } + + public void setDirectionDisplay(int elevator, int direction) { + Lib.assertTrue(elevator >= 0 && elevator < numElevators); + elevators[elevator].direction = direction; + + if (elevators[elevator].doorsOpen) { + postRiderEvent(RiderEvent.eventDirectionChanged, + elevators[elevator].floor, elevator); + } + + if (gui != null) { + if (elevators[elevator].doorsOpen) { + if (direction == dirUp) + gui.clearUpButton(elevators[elevator].floor); + else if (direction == dirDown) + gui.clearDownButton(elevators[elevator].floor); + } + + gui.setDirectionDisplay(elevator, direction); + } + } + + public void finish() { + finished = true; + + Lib.assertTrue(KThread.currentThread() == thread); + + done.V(); + KThread.finish(); + } + + public ElevatorEvent getNextEvent() { + if (events.isEmpty()) + return null; + else + return (ElevatorEvent) events.removeFirst(); + } + + void schedule(int when) { + privilege.interrupt.schedule(when, "elevator", interrupt); + } + + void postEvent(int event, int floor, int elevator, boolean schedule) { + events.add(new ElevatorEvent(event, floor, elevator)); + + if (schedule) + schedule(1); + } + + void interrupt() { + for (int i=0; i events = new LinkedList(); + Semaphore done = new Semaphore(0); + boolean finished = false; + } + + private class ElevatorState { + ElevatorState(int floor) { + this.floor = floor; + destination = floor; + } + + int openDoors() { + Lib.assertTrue(!doorsOpen && !moving); + doorsOpen = true; + return floor; + } + + int closeDoors() { + Lib.assertTrue(doorsOpen); + doorsOpen = false; + return floor; + } + + boolean moveTo(int newDestination) { + Lib.assertTrue(!doorsOpen); + + if (!moving) { + // can't move to current floor + if (floor == newDestination) + return false; + + destination = newDestination; + nextETA = Machine.timer().getTime() + Stats.ElevatorTicks; + + moving = true; + return true; + } + else { + // moving, shouldn't be at destination + Lib.assertTrue(floor != destination); + + // make sure it's ok to stop + if ((destination > floor && newDestination <= floor) || + (destination < floor && newDestination >= floor)) + return false; + + destination = newDestination; + return true; + } + } + + boolean enter(RiderState rider, int onFloor) { + Lib.assertTrue(!riders.contains(rider)); + + if (!doorsOpen || moving || onFloor != floor || + riders.size() == maxRiders) + return false; + + riders.addElement(rider); + return true; + } + + boolean exit(RiderState rider, int onFloor) { + Lib.assertTrue(riders.contains(rider)); + + if (!doorsOpen || moving || onFloor != floor) + return false; + + riders.removeElement(rider); + return true; + } + + boolean atNextFloor() { + if (!moving || Machine.timer().getTime() < nextETA) + return false; + + Lib.assertTrue(destination != floor); + if (destination > floor) + floor++; + else + floor--; + + for (Iterator i=riders.iterator(); i.hasNext(); ) { + RiderState rider = (RiderState) i.next(); + + rider.floor = floor; + } + + return true; + } + + boolean atDestination() { + if (!moving || destination != floor) + return false; + + moving = false; + return true; + } + + static final int maxRiders = 4; + + int floor, destination; + long nextETA; + + boolean doorsOpen = false, moving = false; + int direction = dirNeither; + public Vector riders = new Vector(); + } + + private class RiderState implements RiderControls { + RiderState(RiderInterface rider, int floor, int[] stops) { + this.rider = rider; + this.floor = floor; + this.stops = stops; + + interrupt = new Runnable() { public void run() { interrupt(); }}; + } + + public int getNumFloors() { + return numFloors; + } + + public int getNumElevators() { + return numElevators; + } + + public void setInterruptHandler(Runnable handler) { + this.handler = handler; + } + + public int getFloor() { + return floor; + } + + public int[] getFloors() { + int[] array = new int[floors.size()]; + for (int i=0; i= 0 && elevator < numElevators); + return elevators[elevator].direction; + } + + public RiderEvent getNextEvent() { + if (events.isEmpty()) + return null; + else + return (RiderEvent) events.removeFirst(); + } + + public boolean pressDirectionButton(boolean up) { + if (up) + return pressUpButton(); + else + return pressDownButton(); + } + + public boolean pressUpButton() { + Lib.assertTrue(!inElevator && floor < numFloors-1); + + for (int elevator=0; elevator 0); + + for (int elevator=0; elevator= 0 && elevator < numElevators); + if (!elevators[elevator].enter(this, floor)) + return false; + + if (gui != null) + gui.enterElevator(floor, elevator); + + inElevator = true; + this.elevator = elevator; + return true; + } + + public boolean pressFloorButton(int floor) { + Lib.assertTrue(inElevator && floor >= 0 && floor < numFloors); + + if (elevators[elevator].doorsOpen && + elevators[elevator].floor == floor) + return false; + + manager.postEvent(ElevatorEvent.eventFloorButtonPressed, + floor, elevator, true); + + if (gui != null) + gui.pressFloorButton(floor, elevator); + + return true; + } + + public boolean exitElevator(int floor) { + Lib.assertTrue(inElevator && floor >= 0 && floor < numFloors); + + if (!elevators[elevator].exit(this, floor)) + return false; + + inElevator = false; + floors.add(new Integer(floor)); + + if (gui != null) + gui.exitElevator(floor, elevator); + + return true; + } + + public void finish() { + finished = true; + + int[] floors = getFloors(); + Lib.assertTrue(floors.length == stops.length); + for (int i=0; i events = new LinkedList(); + Vector floors = new Vector(); + Semaphore done = new Semaphore(0); + KThread thread; + } + + private int numFloors, numElevators; + private ElevatorManager manager; + private ElevatorState[] elevators; + + private int numRiders; + private Vector ridersVector; + private RiderState[] riders; + + private boolean simulationStarted, enableGui; + private Privilege privilege; + private ElevatorGui gui; +} diff --git a/machine/ElevatorControllerInterface.java b/machine/ElevatorControllerInterface.java new file mode 100644 index 0000000..d64d954 --- /dev/null +++ b/machine/ElevatorControllerInterface.java @@ -0,0 +1,40 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * A controller for all the elevators in an elevator bank. The controller + * accesses the elevator bank through an instance of ElevatorControls. + */ +public interface ElevatorControllerInterface extends Runnable { + /** + * Initialize this elevator controller. The controller will access the + * elevator bank through controls. This constructor should return + * immediately after this controller is initialized, but not until the + * interupt handler is set. The controller will start receiving events + * after this method returns, but potentially before run() is + * called. + * + * @param controls the controller's interface to the elevator + * bank. The controller must not attempt to access + * the elevator bank in any other way. + */ + public void initialize(ElevatorControls controls); + + /** + * Cause the controller to use the provided controls to receive and process + * requests from riders. This method should not return, but instead should + * call controls.finish() when the controller is finished. + */ + public void run(); + + /** The number of ticks doors should be held open before closing them. */ + public static final int timeDoorsOpen = 500; + + /** Indicates an elevator intends to move down. */ + public static final int dirDown = -1; + /** Indicates an elevator intends not to move. */ + public static final int dirNeither = 0; + /** Indicates an elevator intends to move up. */ + public static final int dirUp = 1; +} diff --git a/machine/ElevatorControls.java b/machine/ElevatorControls.java new file mode 100644 index 0000000..1aa3d4b --- /dev/null +++ b/machine/ElevatorControls.java @@ -0,0 +1,96 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * A set of controls that can be used by an elevator controller. + */ +public interface ElevatorControls { + /** + * Return the number of floors in the elevator bank. If n is the + * number of floors in the bank, then the floors are numbered 0 + * (the ground floor) through n - 1 (the top floor). + * + * @return the number of floors in the bank. + */ + public int getNumFloors(); + + /** + * Return the number of elevators in the elevator bank. If n is the + * number of elevators in the bank, then the elevators are numbered + * 0 through n - 1. + * + * @return the numbe rof elevators in the bank. + */ + public int getNumElevators(); + + /** + * Set the elevator interrupt handler. This handler will be called when an + * elevator event occurs, and when all the riders have reaced their + * destinations. + * + * @param handler the elevator interrupt handler. + */ + public void setInterruptHandler(Runnable handler); + + /** + * Open an elevator's doors. + * + * @param elevator which elevator's doors to open. + */ + public void openDoors(int elevator); + + /** + * Close an elevator's doors. + * + * @param elevator which elevator's doors to close. + */ + public void closeDoors(int elevator); + + /** + * Move an elevator to another floor. The elevator's doors must be closed. + * If the elevator is already moving and cannot safely stop at the + * specified floor because it has already passed or is about to pass the + * floor, fails and returns false. If the elevator is already + * stopped at the specified floor, returns false. + * + * @param floor the floor to move to. + * @param elevator the elevator to move. + * @return true if the elevator's destination was changed. + */ + public boolean moveTo(int floor, int elevator); + + /** + * Return the current location of the elevator. If the elevator is in + * motion, the returned value will be within one of the exact location. + * + * @param elevator the elevator to locate. + * @return the floor the elevator is on. + */ + public int getFloor(int elevator); + + /** + * Set which direction the elevator bank will show for this elevator's + * display. The direction argument should be one of the dir* + * constants in the ElevatorBank class. + * + * @param elevator the elevator whose direction display to set. + * @param direction the direction to show (up, down, or neither). + */ + public void setDirectionDisplay(int elevator, int direction); + + /** + * Call when the elevator controller is finished. + */ + public void finish(); + + /** + * Return the next event in the event queue. Note that there may be + * multiple events pending when an elevator interrupt occurs, so this + * method should be called repeatedly until it returns null. + * + * @return the next event, or null if no further events are + * currently pending. + */ + public ElevatorEvent getNextEvent(); +} diff --git a/machine/ElevatorEvent.java b/machine/ElevatorEvent.java new file mode 100644 index 0000000..302cc43 --- /dev/null +++ b/machine/ElevatorEvent.java @@ -0,0 +1,33 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * An event that affects elevator software. + */ +public final class ElevatorEvent { + public ElevatorEvent(int event, int floor, int elevator) { + this.event = event; + this.floor = floor; + this.elevator = elevator; + } + + /** The event identifier. Refer to the event* constants. */ + public final int event; + /** The floor pertaining to the event, or -1 if not applicable. */ + public final int floor; + /** The elevator pertaining to the event, or -1 if not applicable. */ + public final int elevator; + + /** An up button was pressed. */ + public static final int eventUpButtonPressed = 0; + /** A down button was pressed. */ + public static final int eventDownButtonPressed = 1; + /** A floor button was pressed inside an elevator. */ + public static final int eventFloorButtonPressed = 2; + /** An elevator has arrived and stopped at its destination floor. */ + public static final int eventElevatorArrived = 3; + /** All riders have finished; the elevator controller should terminate. */ + public static final int eventRidersDone = 4; +} + diff --git a/machine/ElevatorGui.java b/machine/ElevatorGui.java new file mode 100644 index 0000000..27176f4 --- /dev/null +++ b/machine/ElevatorGui.java @@ -0,0 +1,424 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import java.awt.Frame; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Panel; +import java.awt.ScrollPane; +import java.awt.Canvas; + +import java.awt.GridLayout; +import java.awt.FlowLayout; +import java.awt.Insets; + +import java.awt.Rectangle; +import java.awt.Graphics; +import java.awt.Color; + +/** + * A graphical visualization for the ElevatorBank class. + */ +public final class ElevatorGui extends Frame { + private final static int w=90, h=75; + + private int numFloors, numElevators; + + private ElevatorShaft[] elevators; + private Floor[] floors; + + private int totalWidth, totalHeight; + + ElevatorGui(int numFloors, int numElevators, int[] numRidersPerFloor) { + this.numFloors = numFloors; + this.numElevators = numElevators; + + totalWidth = w*(numElevators+1); + totalHeight = h*numFloors; + + setTitle("Elevator Bank"); + + Panel floorPanel = new Panel(new GridLayout(numFloors, 1, 0, 0)); + + floors = new Floor[numFloors]; + for (int i=numFloors-1; i>=0; i--) { + floors[i] = new Floor(i, numRidersPerFloor[i]); + floorPanel.add(floors[i]); + } + + Panel panel = new Panel(new GridLayout(1, numElevators+1, 0, 0)); + + panel.add(floorPanel); + + elevators = new ElevatorShaft[numElevators]; + for (int i=0; i 0) + paintRider(g, x0 + i*t, y0 - j*t, r); + } + } + } + + private class Floor extends Canvas { + int floor, numRiders; + + boolean upSet = false; + boolean downSet = false; + + Floor(int floor, int numRiders) { + this.floor = floor; + this.numRiders = numRiders; + + setBackground(Color.black); + } + + public Dimension getPreferredSize() { + return new Dimension(w, h); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + public Dimension getMaximumSize() { + return getPreferredSize(); + } + + public void repaint() { + super.repaint(); + + if (TCB.isNachosThread()) { + try { + Thread.sleep(100); + } + catch (InterruptedException e) { + } + } + } + + void pressUpButton() { + if (!upSet) { + upSet = true; + repaint(); + } + } + + void pressDownButton() { + if (!downSet) { + downSet = true; + repaint(); + } + } + + void clearUpButton() { + if (upSet) { + upSet = false; + repaint(); + } + } + + void clearDownButton() { + if (downSet) { + downSet = false; + repaint(); + } + } + + void addRider() { + numRiders++; + + repaint(); + } + + void removeRider() { + numRiders--; + + repaint(); + } + + public void paint(Graphics g) { + g.setColor(Color.lightGray); + g.drawLine(0, 0, w, 0); + + paintRiders(g, 0, 5, 3*w/4, h-10, numRiders); + + paintButtons(g); + } + + private void paintButtons(Graphics g) { + int s = 3*w/4; + + int x1 = s+w/32; + int x2 = w-w/32; + int y1 = h/8; + int y2 = h-h/8; + + g.setColor(Color.darkGray); + g.drawRect(x1, y1, x2-x1, y2-y1); + g.setColor(Color.lightGray); + g.fillRect(x1+1, y1+1, x2-x1-2, y2-y1-2); + + int r = Math.min((x2-x1)/3, (y2-y1)/6); + int xc = (x1+x2)/2; + int yc1 = (y1+y2)/2 - (3*r/2); + int yc2 = (y1+y2)/2 + (3*r/2); + + g.setColor(Color.red); + + if (floor < numFloors-1) { + if (upSet) + g.fillOval(xc-r, yc1-r, 2*r, 2*r); + else + g.drawOval(xc-r, yc1-r, 2*r, 2*r); + } + + if (floor > 0) { + if (downSet) + g.fillOval(xc-r, yc2-r, 2*r, 2*r); + else + g.drawOval(xc-r, yc2-r, 2*r, 2*r); + } + } + } + + private class ElevatorShaft extends Canvas { + ElevatorShaft(int elevator) { + this.elevator = elevator; + + floorsSet = new boolean[numFloors]; + for (int i=0; iElevatorBank module, using a single elevator and a single + * rider. + */ +public final class ElevatorTest { + /** + * Allocate a new ElevatorTest object. + */ + public ElevatorTest() { + } + + /** + * Run a test on Machine.bank(). + */ + public void run() { + Machine.bank().init(1, 2, new ElevatorController()); + + int[] stops = { 1 }; + + Machine.bank().addRider(new Rider(), 0, stops); + + Machine.bank().run(); + } + + private class ElevatorController implements ElevatorControllerInterface { + public void initialize(ElevatorControls controls) { + this.controls = controls; + + eventWait = new Semaphore(0); + + controls.setInterruptHandler(new Runnable() { + public void run() { interrupt(); } + }); + } + + public void run() { + ElevatorEvent e; + + Lib.assertTrue(controls.getFloor(0) == 0); + + e = getNextEvent(); + Lib.assertTrue(e.event == ElevatorEvent.eventUpButtonPressed && + e.floor == 0); + + controls.setDirectionDisplay(0, dirUp); + controls.openDoors(0); + + e = getNextEvent(); + Lib.assertTrue(e.event == ElevatorEvent.eventFloorButtonPressed && + e.floor == 1); + + controls.closeDoors(0); + controls.moveTo(1, 0); + + e = getNextEvent(); + Lib.assertTrue(e.event == ElevatorEvent.eventElevatorArrived && + e.floor == 1 && + e.elevator == 0); + + controls.openDoors(0); + + e = getNextEvent(); + Lib.assertTrue(e.event == ElevatorEvent.eventRidersDone); + + controls.finish(); + Lib.assertNotReached(); + } + + private void interrupt() { + eventWait.V(); + } + + private ElevatorEvent getNextEvent() { + ElevatorEvent event; + while (true) { + if ((event = controls.getNextEvent()) != null) + break; + + eventWait.P(); + } + return event; + } + + private ElevatorControls controls; + private Semaphore eventWait; + } + + private class Rider implements RiderInterface { + public void initialize(RiderControls controls, int[] stops) { + this.controls = controls; + Lib.assertTrue(stops.length == 1 && stops[0] == 1); + + eventWait = new Semaphore(0); + + controls.setInterruptHandler(new Runnable() { + public void run() { interrupt(); } + }); + } + + public void run() { + RiderEvent e; + + Lib.assertTrue(controls.getFloor() == 0); + + controls.pressUpButton(); + + e = getNextEvent(); + Lib.assertTrue(e.event == RiderEvent.eventDoorsOpened && + e.floor == 0 && + e.elevator == 0); + Lib.assertTrue(controls.getDirectionDisplay(0) == dirUp); + + Lib.assertTrue(controls.enterElevator(0)); + controls.pressFloorButton(1); + + e = getNextEvent(); + Lib.assertTrue(e.event == RiderEvent.eventDoorsClosed && + e.floor == 0 && + e.elevator == 0); + + e = getNextEvent(); + Lib.assertTrue(e.event == RiderEvent.eventDoorsOpened && + e.floor == 1 && + e.elevator == 0); + + Lib.assertTrue(controls.exitElevator(1)); + + controls.finish(); + Lib.assertNotReached(); + } + + private void interrupt() { + eventWait.V(); + } + + private RiderEvent getNextEvent() { + RiderEvent event; + while (true) { + if ((event = controls.getNextEvent()) != null) + break; + + eventWait.P(); + } + return event; + } + + private RiderControls controls; + private Semaphore eventWait; + } +} diff --git a/machine/FileSystem.java b/machine/FileSystem.java new file mode 100644 index 0000000..dbdaa31 --- /dev/null +++ b/machine/FileSystem.java @@ -0,0 +1,35 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * A file system that allows the user to create, open, and delete files. + */ +public interface FileSystem { + /** + * Atomically open a file, optionally creating it if it does not + * already exist. If the file does not + * already exist and create is false, returns + * null. If the file does not already exist and create + * is true, creates the file with zero length. If the file already + * exists, opens the file without changing it in any way. + * + * @param name the name of the file to open. + * @param create true to create the file if it does not + * already exist. + * @return an OpenFile representing a new instance of the opened + * file, or null if the file could not be opened. + */ + public OpenFile open(String name, boolean create); + + /** + * Atomically remove an existing file. After a file is removed, it cannot + * be opened until it is created again with open. If the file is + * already open, it is up to the implementation to decide whether the file + * can still be accessed or if it is deleted immediately. + * + * @param name the name of the file to remove. + * @return true if the file was successfully removed. + */ + public boolean remove(String name); +} diff --git a/machine/Interrupt.java b/machine/Interrupt.java new file mode 100644 index 0000000..8b4663f --- /dev/null +++ b/machine/Interrupt.java @@ -0,0 +1,253 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; + +import java.util.TreeSet; +import java.util.Iterator; +import java.util.SortedSet; + +/** + * The Interrupt class emulates low-level interrupt hardware. The + * hardware provides a method (setStatus()) to enable or disable + * interrupts. + * + *

+ * In order to emulate the hardware, we need to keep track of all pending + * interrupts the hardware devices would cause, and when they are supposed to + * occur. + * + *

+ * This module also keeps track of simulated time. Time advances only when the + * following occur: + *

    + *
  • interrupts are enabled, when they were previously disabled + *
  • a MIPS instruction is executed + *
+ * + *

+ * As a result, unlike real hardware, interrupts (including time-slice context + * switches) cannot occur just anywhere in the code where interrupts are + * enabled, but rather only at those places in the code where simulated time + * advances (so that it becomes time for the hardware simulation to invoke an + * interrupt handler). + * + *

+ * This means that incorrectly synchronized code may work fine on this hardware + * simulation (even with randomized time slices), but it wouldn't work on real + * hardware. But even though Nachos can't always detect when your program + * would fail in real life, you should still write properly synchronized code. + */ +public final class Interrupt { + /** + * Allocate a new interrupt controller. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + */ + public Interrupt(Privilege privilege) { + System.out.print(" interrupt"); + + this.privilege = privilege; + privilege.interrupt = new InterruptPrivilege(); + + enabled = false; + pending = new TreeSet(); + } + + /** + * Enable interrupts. This method has the same effect as + * setStatus(true). + */ + public void enable() { + setStatus(true); + } + + /** + * Disable interrupts and return the old interrupt state. This method has + * the same effect as setStatus(false). + * + * @return true if interrupts were enabled. + */ + public boolean disable() { + return setStatus(false); + } + + /** + * Restore interrupts to the specified status. This method has the same + * effect as setStatus(status). + * + * @param status true to enable interrupts. + */ + public void restore(boolean status) { + setStatus(status); + } + + /** + * Set the interrupt status to be enabled (true) or disabled + * (false) and return the previous status. If the interrupt + * status changes from disabled to enabled, the simulated time is advanced. + * + * @param status true to enable interrupts. + * @return true if interrupts were enabled. + */ + public boolean setStatus(boolean status) { + boolean oldStatus = enabled; + enabled = status; + + if (oldStatus == false && status == true) + tick(true); + + return oldStatus; + } + + /** + * Tests whether interrupts are enabled. + * + * @return true if interrupts are enabled. + */ + public boolean enabled() { + return enabled; + } + + /** + * Tests whether interrupts are disabled. + * + * @return true if interrupts are disabled. + */ + public boolean disabled() { + return !enabled; + } + + private void schedule(long when, String type, Runnable handler) { + Lib.assertTrue(when>0); + + long time = privilege.stats.totalTicks + when; + PendingInterrupt toOccur = new PendingInterrupt(time, type, handler); + + Lib.debug(dbgInt, + "Scheduling the " + type + + " interrupt handler at time = " + time); + + pending.add(toOccur); + } + + private void tick(boolean inKernelMode) { + Stats stats = privilege.stats; + + if (inKernelMode) { + stats.kernelTicks += Stats.KernelTick; + stats.totalTicks += Stats.KernelTick; + } + else { + stats.userTicks += Stats.UserTick; + stats.totalTicks += Stats.UserTick; + } + + if (Lib.test(dbgInt)) + System.out.println("== Tick " + stats.totalTicks + " =="); + + enabled = false; + checkIfDue(); + enabled = true; + } + + private void checkIfDue() { + long time = privilege.stats.totalTicks; + + Lib.assertTrue(disabled()); + + if (Lib.test(dbgInt)) + print(); + + if (pending.isEmpty()) + return; + + if (((PendingInterrupt) pending.first()).time > time) + return; + + Lib.debug(dbgInt, "Invoking interrupt handlers at time = " + time); + + while (!pending.isEmpty() && + ((PendingInterrupt) pending.first()).time <= time) { + PendingInterrupt next = (PendingInterrupt) pending.first(); + pending.remove(next); + + Lib.assertTrue(next.time <= time); + + if (privilege.processor != null) + privilege.processor.flushPipe(); + + Lib.debug(dbgInt, " " + next.type); + + next.handler.run(); + } + + Lib.debug(dbgInt, " (end of list)"); + } + + private void print() { + System.out.println("Time: " + privilege.stats.totalTicks + + ", interrupts " + (enabled ? "on" : "off")); + System.out.println("Pending interrupts:"); + + for (Iterator i=pending.iterator(); i.hasNext(); ) { + PendingInterrupt toOccur = (PendingInterrupt) i.next(); + System.out.println(" " + toOccur.type + + ", scheduled at " + toOccur.time); + } + + System.out.println(" (end of list)"); + } + + private class PendingInterrupt implements Comparable { + PendingInterrupt(long time, String type, Runnable handler) { + this.time = time; + this.type = type; + this.handler = handler; + this.id = numPendingInterruptsCreated++; + } + + public int compareTo(Object o) { + PendingInterrupt toOccur = (PendingInterrupt) o; + + // can't return 0 for unequal objects, so check all fields + if (time < toOccur.time) + return -1; + else if (time > toOccur.time) + return 1; + else if (id < toOccur.id) + return -1; + else if (id > toOccur.id) + return 1; + else + return 0; + } + + long time; + String type; + Runnable handler; + + private long id; + } + + private long numPendingInterruptsCreated = 0; + + private Privilege privilege; + + private boolean enabled; + private TreeSet pending; + + private static final char dbgInt = 'i'; + + private class InterruptPrivilege implements Privilege.InterruptPrivilege { + public void schedule(long when, String type, Runnable handler) { + Interrupt.this.schedule(when, type, handler); + } + + public void tick(boolean inKernelMode) { + Interrupt.this.tick(inKernelMode); + } + } +} diff --git a/machine/Kernel.java b/machine/Kernel.java new file mode 100644 index 0000000..d1260e6 --- /dev/null +++ b/machine/Kernel.java @@ -0,0 +1,45 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * An OS kernel. + */ +public abstract class Kernel { + /** Globally accessible reference to the kernel. */ + public static Kernel kernel = null; + + /** + * Allocate a new kernel. + */ + public Kernel() { + // make sure only one kernel is created + Lib.assertTrue(kernel == null); + kernel = this; + } + + /** + * Initialize this kernel. + */ + public abstract void initialize(String[] args); + + /** + * Test that this module works. + * + * Warning: this method will not be invoked by the autograder when + * we grade your projects. You should perform all initialization in + * initialize(). + */ + public abstract void selfTest(); + + /** + * Begin executing user programs, if applicable. + */ + public abstract void run(); + + /** + * Terminate this kernel. Never returns. + */ + public abstract void terminate(); +} + diff --git a/machine/Lib.java b/machine/Lib.java new file mode 100644 index 0000000..62ada71 --- /dev/null +++ b/machine/Lib.java @@ -0,0 +1,638 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.security.PrivilegedAction; +import java.util.Random; + +/** + * Thrown when an assertion fails. + */ +class AssertionFailureError extends Error { + AssertionFailureError() { + super(); + } + + AssertionFailureError(String message) { + super(message); + } +} + +/** + * Provides miscellaneous library routines. + */ +public final class Lib { + /** + * Prevent instantiation. + */ + private Lib() { + } + + private static Random random = null; + + /** + * Seed the random number generater. May only be called once. + * + * @param randomSeed the seed for the random number generator. + */ + public static void seedRandom(long randomSeed) { + assertTrue(random == null); + random = new Random(randomSeed); + } + + /** + * Return a random integer between 0 and range - 1. Must not be + * called before seedRandom() seeds the random number generator. + * + * @param range a positive value specifying the number of possible + * return values. + * @return a random integer in the specified range. + */ + public static int random(int range) { + assertTrue(range > 0); + return random.nextInt(range); + } + + /** + * Return a random double between 0.0 (inclusive) and 1.0 (exclusive). + * + * @return a random double between 0.0 and 1.0. + */ + public static double random() { + return random.nextDouble(); + } + + /** + * Asserts that expression is true. If not, then Nachos + * exits with an error message. + * + * @param expression the expression to assert. + */ + public static void assertTrue(boolean expression) { + if (!expression) + throw new AssertionFailureError(); + } + + /** + * Asserts that expression is true. If not, then Nachos + * exits with the specified error message. + * + * @param expression the expression to assert. + * @param message the error message. + */ + public static void assertTrue(boolean expression, String message) { + if (!expression) + throw new AssertionFailureError(message); + } + + /** + * Asserts that this call is never made. Same as assertTrue(false). + */ + public static void assertNotReached() { + assertTrue(false); + } + + /** + * Asserts that this call is never made, with the specified error messsage. + * Same as assertTrue(false, message). + * + * @param message the error message. + */ + public static void assertNotReached(String message) { + assertTrue(false, message); + } + + /** + * Print message if flag was enabled on the command line. To + * specify which flags to enable, use the -d command line option. For + * example, to enable flags a, c, and e, do the following: + * + *

+ *

nachos -d ace
+ * + *

+ * Nachos uses several debugging flags already, but you are encouraged to + * add your own. + * + * @param flag the debug flag that must be set to print this message. + * @param message the debug message. + */ + public static void debug(char flag, String message) { + if (test(flag)) + System.out.println(message); + } + + /** + * Tests if flag was enabled on the command line. + * + * @param flag the debug flag to test. + * + * @return true if this flag was enabled on the command line. + */ + public static boolean test(char flag) { + if (debugFlags == null) + return false; + else if (debugFlags[(int) '+']) + return true; + else if (flag >= 0 && flag < 0x80 && debugFlags[(int) flag]) + return true; + else + return false; + } + + /** + * Enable all the debug flags in flagsString. + * + * @param flagsString the flags to enable. + */ + public static void enableDebugFlags(String flagsString) { + if (debugFlags == null) + debugFlags = new boolean[0x80]; + + char[] newFlags = flagsString.toCharArray(); + for (int i=0; i= 0 && c < 0x80) + debugFlags[(int) c] = true; + } + } + + /** Debug flags specified on the command line. */ + private static boolean debugFlags[]; + + /** + * Read a file, verifying that the requested number of bytes is read, and + * verifying that the read operation took a non-zero amount of time. + * + * @param file the file to read. + * @param position the file offset at which to start reading. + * @param buf the buffer in which to store the data. + * @param offset the buffer offset at which storing begins. + * @param length the number of bytes to read. + */ + public static void strictReadFile(OpenFile file, int position, + byte[] buf, int offset, int length) { + long startTime = Machine.timer().getTime(); + assertTrue(file.read(position, buf, offset, length) == length); + long finishTime = Machine.timer().getTime(); + assertTrue(finishTime>startTime); + } + + /** + * Load an entire file into memory. + * + * @param file the file to load. + * @return an array containing the contents of the entire file, or + * null if an error occurred. + */ + public static byte[] loadFile(OpenFile file) { + int startOffset = file.tell(); + + int length = file.length(); + if (length < 0) + return null; + + byte[] data = new byte[length]; + + file.seek(0); + int amount = file.read(data, 0, length); + file.seek(startOffset); + + if (amount == length) + return data; + else + return null; + } + + /** + * Take a read-only snapshot of a file. + * + * @param file the file to take a snapshot of. + * @return a read-only snapshot of the file. + */ + public static OpenFile cloneFile(OpenFile file) { + OpenFile clone = new ArrayFile(loadFile(file)); + + clone.seek(file.tell()); + + return clone; + } + + /** + * Convert a short into its little-endian byte string representation. + * + * @param array the array in which to store the byte string. + * @param offset the offset in the array where the string will start. + * @param value the value to convert. + */ + public static void bytesFromShort(byte[] array, int offset, short value) { + array[offset+0] = (byte) ((value>>0)&0xFF); + array[offset+1] = (byte) ((value>>8)&0xFF); + } + + /** + * Convert an int into its little-endian byte string representation. + * + * @param array the array in which to store the byte string. + * @param offset the offset in the array where the string will start. + * @param value the value to convert. + */ + public static void bytesFromInt(byte[] array, int offset, int value) { + array[offset+0] = (byte) ((value>>0) &0xFF); + array[offset+1] = (byte) ((value>>8) &0xFF); + array[offset+2] = (byte) ((value>>16)&0xFF); + array[offset+3] = (byte) ((value>>24)&0xFF); + } + + /** + * Convert an int into its little-endian byte string representation, and + * return an array containing it. + * + * @param value the value to convert. + * @return an array containing the byte string. + */ + public static byte[] bytesFromInt(int value) { + byte[] array = new byte[4]; + bytesFromInt(array, 0, value); + return array; + } + + /** + * Convert an int into a little-endian byte string representation of the + * specified length. + * + * @param array the array in which to store the byte string. + * @param offset the offset in the array where the string will start. + * @param length the number of bytes to store (must be 1, 2, or 4). + * @param value the value to convert. + */ + public static void bytesFromInt(byte[] array, int offset, + int length, int value) { + assertTrue(length==1 || length==2 || length==4); + + switch (length) { + case 1: + array[offset] = (byte) value; + break; + case 2: + bytesFromShort(array, offset, (short) value); + break; + case 4: + bytesFromInt(array, offset, value); + break; + } + } + + /** + * Convert to a short from its little-endian byte string representation. + * + * @param array the array containing the byte string. + * @param offset the offset of the byte string in the array. + * @return the corresponding short value. + */ + public static short bytesToShort(byte[] array, int offset) { + return (short) ((((short) array[offset+0] & 0xFF) << 0) | + (((short) array[offset+1] & 0xFF) << 8)); + } + + /** + * Convert to an unsigned short from its little-endian byte string + * representation. + * + * @param array the array containing the byte string. + * @param offset the offset of the byte string in the array. + * @return the corresponding short value. + */ + public static int bytesToUnsignedShort(byte[] array, int offset) { + return (((int) bytesToShort(array, offset)) & 0xFFFF); + } + + /** + * Convert to an int from its little-endian byte string representation. + * + * @param array the array containing the byte string. + * @param offset the offset of the byte string in the array. + * @return the corresponding int value. + */ + public static int bytesToInt(byte[] array, int offset) { + return (int) ((((int) array[offset+0] & 0xFF) << 0) | + (((int) array[offset+1] & 0xFF) << 8) | + (((int) array[offset+2] & 0xFF) << 16) | + (((int) array[offset+3] & 0xFF) << 24)); + } + + /** + * Convert to an int from a little-endian byte string representation of the + * specified length. + * + * @param array the array containing the byte string. + * @param offset the offset of the byte string in the array. + * @param length the length of the byte string. + * @return the corresponding value. + */ + public static int bytesToInt(byte[] array, int offset, int length) { + assertTrue(length==1 || length==2 || length==4); + + switch (length) { + case 1: + return array[offset]; + case 2: + return bytesToShort(array, offset); + case 4: + return bytesToInt(array, offset); + default: + return -1; + } + } + + /** + * Convert to a string from a possibly null-terminated array of bytes. + * + * @param array the array containing the byte string. + * @param offset the offset of the byte string in the array. + * @param length the maximum length of the byte string. + * @return a string containing the specified bytes, up to and not + * including the null-terminator (if present). + */ + public static String bytesToString(byte[] array, int offset, int length) { + int i; + for (i=0; i> lowest); + else + return ((bits >> lowest) & ((1<> lowest); + else + return ((bits >> lowest) & ((1L<> extra); + } + + /** Test if a bit is set in a bit string. + * + * @param flag the flag to test. + * @param bits the bit string. + * @return true if (bits & flag) is non-zero. + */ + public static boolean test(long flag, long bits) { + return ((bits & flag) != 0); + } + + /** + * Creates a padded upper-case string representation of the integer + * argument in base 16. + * + * @param i an integer. + * @return a padded upper-case string representation in base 16. + */ + public static String toHexString(int i) { + return toHexString(i, 8); + } + + /** + * Creates a padded upper-case string representation of the integer + * argument in base 16, padding to at most the specified number of digits. + * + * @param i an integer. + * @param pad the minimum number of hex digits to pad to. + * @return a padded upper-case string representation in base 16. + */ + public static String toHexString(int i, int pad) { + String result = Integer.toHexString(i).toUpperCase(); + while (result.length() < pad) + result = "0" + result; + return result; + } + + /** + * Divide two non-negative integers, round the quotient up to the nearest + * integer, and return it. + * + * @param a the numerator. + * @param b the denominator. + * @return ceiling(a / b). + */ + public static int divRoundUp(int a, int b) { + assertTrue(a >= 0 && b > 0); + + return ((a + (b-1)) / b); + } + + /** + * Load and return the named class, or return null if the class + * could not be loaded. + * + * @param className the name of the class to load. + * @return the loaded class, or null if an error occurred. + */ + public static Class tryLoadClass(String className) { + try { + return ClassLoader.getSystemClassLoader().loadClass(className); + } + catch (Throwable e) { + return null; + } + } + + /** + * Load and return the named class, terminating Nachos on any error. + * + * @param className the name of the class to load. + * @return the loaded class. + */ + public static Class loadClass(String className) { + try { + return ClassLoader.getSystemClassLoader().loadClass(className); + } + catch (Throwable e) { + Machine.terminate(e); + return null; + } + } + + /** + * Create and return a new instance of the named class, using the + * constructor that takes no arguments. + * + * @param className the name of the class to instantiate. + * @return a new instance of the class. + */ + public static Object constructObject(String className) { + try { + // kamil - workaround for Java 1.4 + // Thanks to Ka-Hing Cheung for the suggestion. + // Fixed for Java 1.5 by geels + Class[] param_types = new Class[0]; + Object[] params = new Object[0]; + return loadClass(className).getConstructor(param_types).newInstance(params); + } + catch (Throwable e) { + Machine.terminate(e); + return null; + } + } + + /** + * Verify that the specified class extends or implements the specified + * superclass. + * + * @param cls the descendant class. + * @param superCls the ancestor class. + */ + public static void checkDerivation(Class cls, Class superCls) { + Lib.assertTrue(superCls.isAssignableFrom(cls)); + } + + /** + * Verifies that the specified class is public and not abstract, and that a + * constructor with the specified signature exists and is public. + * + * @param cls the class containing the constructor. + * @param parameterTypes the list of parameters. + */ + public static void checkConstructor(Class cls, Class[] parameterTypes) { + try { + Lib.assertTrue(Modifier.isPublic(cls.getModifiers()) && + !Modifier.isAbstract(cls.getModifiers())); + Constructor constructor = cls.getConstructor(parameterTypes); + Lib.assertTrue(Modifier.isPublic(constructor.getModifiers())); + } + catch (Exception e) { + Lib.assertNotReached(); + } + } + + /** + * Verifies that the specified class is public, and that a non-static + * method with the specified name and signature exists, is public, and + * returns the specified type. + * + * @param cls the class containing the non-static method. + * @param methodName the name of the non-static method. + * @param parameterTypes the list of parameters. + * @param returnType the required return type. + */ + public static void checkMethod(Class cls, String methodName, + Class[] parameterTypes, Class returnType) { + try { + Lib.assertTrue(Modifier.isPublic(cls.getModifiers())); + Method method = cls.getMethod(methodName, parameterTypes); + Lib.assertTrue(Modifier.isPublic(method.getModifiers()) && + !Modifier.isStatic(method.getModifiers())); + Lib.assertTrue(method.getReturnType() == returnType); + } + catch (Exception e) { + Lib.assertNotReached(); + } + } + + /** + * Verifies that the specified class is public, and that a static method + * with the specified name and signature exists, is public, and returns the + * specified type. + * + * @param cls the class containing the static method. + * @param methodName the name of the static method. + * @param parameterTypes the list of parameters. + * @param returnType the required return type. + */ + public static void checkStaticMethod(Class cls, String methodName, + Class[] parameterTypes, + Class returnType) { + try { + Lib.assertTrue(Modifier.isPublic(cls.getModifiers())); + Method method = cls.getMethod(methodName, parameterTypes); + Lib.assertTrue(Modifier.isPublic(method.getModifiers()) && + Modifier.isStatic(method.getModifiers())); + Lib.assertTrue(method.getReturnType() == returnType); + } + catch (Exception e) { + Lib.assertNotReached(); + } + } + + /** + * Verifies that the specified class is public, and that a non-static field + * with the specified name and type exists, is public, and is not final. + * + * @param cls the class containing the field. + * @param fieldName the name of the field. + * @param fieldType the required type. + */ + public static void checkField(Class cls, String fieldName, + Class fieldType) { + try { + Lib.assertTrue(Modifier.isPublic(cls.getModifiers())); + Field field = cls.getField(fieldName); + Lib.assertTrue(field.getType() == fieldType); + Lib.assertTrue(Modifier.isPublic(field.getModifiers()) && + !Modifier.isStatic(field.getModifiers()) && + !Modifier.isFinal(field.getModifiers())); + } + catch (Exception e) { + Lib.assertNotReached(); + } + } + + /** + * Verifies that the specified class is public, and that a static field + * with the specified name and type exists and is public. + * + * @param cls the class containing the static field. + * @param fieldName the name of the static field. + * @param fieldType the required type. + */ + public static void checkStaticField(Class cls, String fieldName, + Class fieldType) { + try { + Lib.assertTrue(Modifier.isPublic(cls.getModifiers())); + Field field = cls.getField(fieldName); + Lib.assertTrue(field.getType() == fieldType); + Lib.assertTrue(Modifier.isPublic(field.getModifiers()) && + Modifier.isStatic(field.getModifiers())); + } + catch (Exception e) { + Lib.assertNotReached(); + } + } +} diff --git a/machine/Machine.java b/machine/Machine.java new file mode 100644 index 0000000..7463ac5 --- /dev/null +++ b/machine/Machine.java @@ -0,0 +1,489 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; +import nachos.ag.*; + +import java.io.File; + +/** + * The master class of the simulated machine. Processes command line arguments, + * constructs all simulated hardware devices, and starts the grader. + */ +public final class Machine { + /** + * Nachos main entry point. + * + * @param args the command line arguments. + */ + public static void main(final String[] args) { + System.out.print("nachos 5.0j initializing..."); + + Lib.assertTrue(Machine.args == null); + Machine.args = args; + + processArgs(); + + Config.load(configFileName); + + // get the current directory (.) + baseDirectory = new File(new File("").getAbsolutePath()); + // get the nachos directory (./nachos) + nachosDirectory = new File(baseDirectory, "nachos"); + + String testDirectoryName = + Config.getString("FileSystem.testDirectory"); + + // get the test directory + if (testDirectoryName != null) { + testDirectory = new File(testDirectoryName); + } + else { + // use ../test + testDirectory = new File(baseDirectory.getParentFile(), "test"); + } + + securityManager = new NachosSecurityManager(testDirectory); + privilege = securityManager.getPrivilege(); + + privilege.machine = new MachinePrivilege(); + + TCB.givePrivilege(privilege); + privilege.stats = stats; + + securityManager.enable(); + createDevices(); + checkUserClasses(); + + autoGrader = (AutoGrader) Lib.constructObject(autoGraderClassName); + + new TCB().start(new Runnable() { + public void run() { autoGrader.start(privilege); } + }); + } + + /** + * Yield to non-Nachos threads. Use in non-preemptive JVM's to give + * non-Nachos threads a chance to run. + */ + public static void yield() { + Thread.yield(); + } + + /** + * Terminate Nachos. Same as TCB.die(). + */ + public static void terminate() { + TCB.die(); + } + + /** + * Terminate Nachos as the result of an unhandled exception or error. + * + * @param e the exception or error. + */ + public static void terminate(Throwable e) { + if (e instanceof ThreadDeath) + throw (ThreadDeath) e; + + e.printStackTrace(); + terminate(); + } + + /** + * Print stats, and terminate Nachos. + */ + public static void halt() { + System.out.print("Machine halting!\n\n"); + stats.print(); + terminate(); + } + + /** + * Return an array containing all command line arguments. + * + * @return the command line arguments passed to Nachos. + */ + public static String[] getCommandLineArguments() { + String[] result = new String[args.length]; + + System.arraycopy(args, 0, result, 0, args.length); + + return result; + } + + private static void processArgs() { + for (int i=0; i 0 && arg.charAt(0) == '-') { + if (arg.equals("-d")) { + Lib.assertTrue(i < args.length, "switch without argument"); + Lib.enableDebugFlags(args[i++]); + } + else if (arg.equals("-h")) { + System.out.print(help); + System.exit(1); + } + else if (arg.equals("-m")) { + Lib.assertTrue(i < args.length, "switch without argument"); + try { + numPhysPages = Integer.parseInt(args[i++]); + } + catch (NumberFormatException e) { + Lib.assertNotReached("bad value for -m switch"); + } + } + else if (arg.equals("-s")) { + Lib.assertTrue(i < args.length, "switch without argument"); + try { + randomSeed = Long.parseLong(args[i++]); + } + catch (NumberFormatException e) { + Lib.assertNotReached("bad value for -s switch"); + } + } + else if (arg.equals("-x")) { + Lib.assertTrue(i < args.length, "switch without argument"); + shellProgramName = args[i++]; + } + else if (arg.equals("-z")) { + System.out.print(copyright); + System.exit(1); + } + // these switches are reserved for the autograder + else if (arg.equals("-[]")) { + Lib.assertTrue(i < args.length, "switch without argument"); + configFileName = args[i++]; + } + else if (arg.equals("--")) { + Lib.assertTrue(i < args.length, "switch without argument"); + autoGraderClassName = args[i++]; + } + } + } + + Lib.seedRandom(randomSeed); + } + + private static void createDevices() { + interrupt = new Interrupt(privilege); + timer = new Timer(privilege); + + if (Config.getBoolean("Machine.bank")) + bank = new ElevatorBank(privilege); + + if (Config.getBoolean("Machine.processor")) { + if (numPhysPages == -1) + numPhysPages = Config.getInteger("Processor.numPhysPages"); + processor = new Processor(privilege, numPhysPages); + } + + if (Config.getBoolean("Machine.console")) + console = new StandardConsole(privilege); + + if (Config.getBoolean("Machine.stubFileSystem")) + stubFileSystem = new StubFileSystem(privilege, testDirectory); + + if (Config.getBoolean("Machine.networkLink")) + networkLink = new NetworkLink(privilege); + } + + private static void checkUserClasses() { + System.out.print(" user-check"); + + Class aclsInt = (new int[0]).getClass(); + Class clsObject = Lib.loadClass("java.lang.Object"); + Class clsRunnable = Lib.loadClass("java.lang.Runnable"); + Class clsString = Lib.loadClass("java.lang.String"); + + Class clsKernel = Lib.loadClass("nachos.machine.Kernel"); + Class clsFileSystem = Lib.loadClass("nachos.machine.FileSystem"); + Class clsRiderControls = Lib.loadClass("nachos.machine.RiderControls"); + Class clsElevatorControls = + Lib.loadClass("nachos.machine.ElevatorControls"); + Class clsRiderInterface = + Lib.loadClass("nachos.machine.RiderInterface"); + Class clsElevatorControllerInterface = + Lib.loadClass("nachos.machine.ElevatorControllerInterface"); + + Class clsAlarm = Lib.loadClass("nachos.threads.Alarm"); + Class clsThreadedKernel = + Lib.loadClass("nachos.threads.ThreadedKernel"); + Class clsKThread = Lib.loadClass("nachos.threads.KThread"); + Class clsCommunicator = Lib.loadClass("nachos.threads.Communicator"); + Class clsSemaphore = Lib.loadClass("nachos.threads.Semaphore"); + Class clsLock = Lib.loadClass("nachos.threads.Lock"); + Class clsCondition = Lib.loadClass("nachos.threads.Condition"); + Class clsCondition2 = Lib.loadClass("nachos.threads.Condition2"); + Class clsRider = Lib.loadClass("nachos.threads.Rider"); + Class clsElevatorController = + Lib.loadClass("nachos.threads.ElevatorController"); + + Lib.checkDerivation(clsThreadedKernel, clsKernel); + + Lib.checkStaticField(clsThreadedKernel, "alarm", clsAlarm); + Lib.checkStaticField(clsThreadedKernel, "fileSystem", clsFileSystem); + + Lib.checkMethod(clsAlarm, "waitUntil", new Class[] { long.class }, + void.class); + + Lib.checkConstructor(clsKThread, new Class[] { }); + Lib.checkConstructor(clsKThread, new Class[] { clsRunnable }); + + Lib.checkStaticMethod(clsKThread, "currentThread", new Class[] {}, + clsKThread); + Lib.checkStaticMethod(clsKThread, "finish", new Class[] {}, + void.class); + Lib.checkStaticMethod(clsKThread, "yield", new Class[] {}, void.class); + Lib.checkStaticMethod(clsKThread, "sleep", new Class[] {}, void.class); + + Lib.checkMethod(clsKThread, "setTarget", new Class[]{ clsRunnable }, + clsKThread); + Lib.checkMethod(clsKThread, "setName", new Class[] { clsString }, + clsKThread); + Lib.checkMethod(clsKThread, "getName", new Class[] { }, clsString); + Lib.checkMethod(clsKThread, "fork", new Class[] { }, void.class); + Lib.checkMethod(clsKThread, "ready", new Class[] { }, void.class); + Lib.checkMethod(clsKThread, "join", new Class[] { }, void.class); + + Lib.checkField(clsKThread, "schedulingState", clsObject); + + Lib.checkConstructor(clsCommunicator, new Class[] {}); + Lib.checkMethod(clsCommunicator, "speak", new Class[] { int.class }, + void.class); + Lib.checkMethod(clsCommunicator, "listen", new Class[] { }, int.class); + + Lib.checkConstructor(clsSemaphore, new Class[] { int.class }); + Lib.checkMethod(clsSemaphore, "P", new Class[] { }, void.class); + Lib.checkMethod(clsSemaphore, "V", new Class[] { }, void.class); + + Lib.checkConstructor(clsLock, new Class[] { }); + Lib.checkMethod(clsLock, "acquire", new Class[] { }, void.class); + Lib.checkMethod(clsLock, "release", new Class[] { }, void.class); + Lib.checkMethod(clsLock, "isHeldByCurrentThread", new Class[]{ }, + boolean.class); + + Lib.checkConstructor(clsCondition, new Class[] { clsLock }); + Lib.checkConstructor(clsCondition2, new Class[] { clsLock }); + + Lib.checkMethod(clsCondition, "sleep", new Class[] { }, void.class); + Lib.checkMethod(clsCondition, "wake", new Class[] { }, void.class); + Lib.checkMethod(clsCondition, "wakeAll", new Class[] { }, void.class); + Lib.checkMethod(clsCondition2, "sleep", new Class[] { }, void.class); + Lib.checkMethod(clsCondition2, "wake", new Class[] { }, void.class); + Lib.checkMethod(clsCondition2, "wakeAll", new Class[] { }, void.class); + + Lib.checkDerivation(clsRider, clsRiderInterface); + + Lib.checkConstructor(clsRider, new Class[] { }); + Lib.checkMethod(clsRider, "initialize", + new Class[] { clsRiderControls, aclsInt }, void.class); + + Lib.checkDerivation(clsElevatorController, + clsElevatorControllerInterface); + + Lib.checkConstructor(clsElevatorController, new Class[] { }); + Lib.checkMethod(clsElevatorController, "initialize", + new Class[] { clsElevatorControls }, void.class); + } + + /** + * Prevent instantiation. + */ + private Machine() { + } + + /** + * Return the hardware interrupt manager. + * + * @return the hardware interrupt manager. + */ + public static Interrupt interrupt() { return interrupt; } + + /** + * Return the hardware timer. + * + * @return the hardware timer. + */ + public static Timer timer() { return timer; } + + /** + * Return the hardware elevator bank. + * + * @return the hardware elevator bank, or null if it is not + * present. + */ + public static ElevatorBank bank() { return bank; } + + /** + * Return the MIPS processor. + * + * @return the MIPS processor, or null if it is not present. + */ + public static Processor processor() { return processor; } + + /** + * Return the hardware console. + * + * @return the hardware console, or null if it is not present. + */ + public static SerialConsole console() { return console; } + + /** + * Return the stub filesystem. + * + * @return the stub file system, or null if it is not present. + */ + public static FileSystem stubFileSystem() { return stubFileSystem; } + + /** + * Return the network link. + * + * @return the network link, or null if it is not present. + */ + public static NetworkLink networkLink() { return networkLink; } + + /** + * Return the autograder. + * + * @return the autograder. + */ + public static AutoGrader autoGrader() { return autoGrader; } + + private static Interrupt interrupt = null; + private static Timer timer = null; + private static ElevatorBank bank = null; + private static Processor processor = null; + private static SerialConsole console = null; + private static FileSystem stubFileSystem = null; + private static NetworkLink networkLink = null; + private static AutoGrader autoGrader = null; + + private static String autoGraderClassName = "nachos.ag.AutoGrader"; + + /** + * Return the name of the shell program that a user-programming kernel + * must run. Make sure UserKernel.run() always uses this + * method to decide which program to run. + * + * @return the name of the shell program to run. + */ + public static String getShellProgramName() { + if (shellProgramName == null) + shellProgramName = Config.getString("Kernel.shellProgram"); + + Lib.assertTrue(shellProgramName != null); + return shellProgramName; + } + + private static String shellProgramName = null; + + /** + * Return the name of the process class that the kernel should use. In + * the multi-programming project, returns + * nachos.userprog.UserProcess. In the VM project, returns + * nachos.vm.VMProcess. In the networking project, returns + * nachos.network.NetProcess. + * + * @return the name of the process class that the kernel should use. + * + * @see nachos.userprog.UserKernel#run + * @see nachos.userprog.UserProcess + * @see nachos.vm.VMProcess + * @see nachos.network.NetProcess + */ + public static String getProcessClassName() { + if (processClassName == null) + processClassName = Config.getString("Kernel.processClassName"); + + Lib.assertTrue(processClassName != null); + return processClassName; + } + + private static String processClassName = null; + + private static NachosSecurityManager securityManager; + private static Privilege privilege; + + private static String[] args = null; + + private static Stats stats = new Stats(); + + private static int numPhysPages = -1; + private static long randomSeed = 0; + + private static File baseDirectory, nachosDirectory, testDirectory; + private static String configFileName = "nachos.conf"; + + private static final String help = + "\n" + + "Options:\n" + + "\n" + + "\t-d \n" + + "\t\tEnable some debug flags, e.g. -d ti\n" + + "\n" + + "\t-h\n" + + "\t\tPrint this help message.\n" + + "\n" + + "\t-m \n" + + "\t\tSpecify how many physical pages of memory to simulate.\n" + + "\n" + + "\t-s \n" + + "\t\tSpecify the seed for the random number generator (seed is a\n" + + "\t\tlong).\n" + + "\n" + + "\t-x \n" + + "\t\tSpecify a program that UserKernel.run() should execute,\n" + + "\t\tinstead of the value of the configuration variable\n" + + "\t\tKernel.shellProgram\n" + + "\n" + + "\t-z\n" + + "\t\tprint the copyright message\n" + + "\n" + + "\t-- \n" + + "\t\tSpecify an autograder class to use, instead of\n" + + "\t\tnachos.ag.AutoGrader\n" + + "\n" + + "\t-# \n" + + "\t\tSpecify the argument string to pass to the autograder.\n" + + "\n" + + "\t-[] \n" + + "\t\tSpecifiy a config file to use, instead of nachos.conf\n" + + "" + ; + + private static final String copyright = "\n" + + "Copyright 1992-2001 The Regents of the University of California.\n" + + "All rights reserved.\n" + + "\n" + + "Permission to use, copy, modify, and distribute this software and\n" + + "its documentation for any purpose, without fee, and without\n" + + "written agreement is hereby granted, provided that the above\n" + + "copyright notice and the following two paragraphs appear in all\n" + + "copies of this software.\n" + + "\n" + + "IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY\n" + + "PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL\n" + + "DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS\n" + + "DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN\n" + + "ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + + "\n" + + "THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY\n" + + "WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n" + + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE\n" + + "SOFTWARE PROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS, AND THE\n" + + "UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE\n" + + "MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.\n" + ; + + private static class MachinePrivilege + implements Privilege.MachinePrivilege { + public void setConsole(SerialConsole console) { + Machine.console = console; + } + } + + // dummy variables to make javac smarter + private static Coff dummy1 = null; +} diff --git a/machine/MalformedPacketException.java b/machine/MalformedPacketException.java new file mode 100644 index 0000000..c468bd8 --- /dev/null +++ b/machine/MalformedPacketException.java @@ -0,0 +1,14 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * Thrown when a malformed packet is processed. + */ +public class MalformedPacketException extends Exception { + /** + * Allocate a new MalformedPacketException. + */ + public MalformedPacketException() { + } +} diff --git a/machine/NetworkLink.java b/machine/NetworkLink.java new file mode 100644 index 0000000..dbcfcb3 --- /dev/null +++ b/machine/NetworkLink.java @@ -0,0 +1,329 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.net.SocketException; + +/** + * A full-duplex network link. Provides ordered, unreliable delivery of + * limited-size packets to other machines on the network. Packets are + * guaranteed to be uncorrupted as well. + * + *

+ * Recall the general layering of network protocols: + *

    + *
  • Session/Transport + *
  • Network + *
  • Link + *
  • Physical + *
+ * + *

+ * The physical layer provides a bit stream interface to the link layer. This + * layer is very hardware-dependent. + * + *

+ * The link layer uses the physical layer to provide a packet interface to the + * network layer. The link layer generally provides unreliable delivery of + * limited-size packets, but guarantees that packets will not arrive out of + * order. Some links protect against packet corruption as well. The ethernet + * protocol is an example of a link layer. + * + *

+ * The network layer exists to connect multiple networks together into an + * internet. The network layer provides globally unique addresses. Routers + * (a.k.a. gateways) move packets across networks at this layer. The network + * layer provides unordered, unreliable delivery of limited-size uncorrupted + * packets to any machine on the same internet. The most commonly used network + * layer protocol is IP (Internet Protocol), which is used to connect the + * Internet. + * + *

+ * The session/transport layer provides a byte-stream interface to the + * application. This means that the transport layer must deliver uncorrupted + * bytes to the application, in the same order they were sent. Byte-streams + * must be connected and disconnected, and exist between ports, not machines. + * + *

+ * This class provides a link layer abstraction. Since we do not allow + * different Nachos networks to communicate with one another, there is no need + * for a network layer in Nachos. This should simplify your design for the + * session/transport layer, since you can assume packets never arrive out of + * order. + */ +public class NetworkLink { + /** + * Allocate a new network link. + * + *

+ * nachos.conf specifies the reliability of the network. The + * reliability, between 0 and 1, is the probability that any particular + * packet will not get dropped by the network. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + */ + public NetworkLink(Privilege privilege) { + System.out.print(" network"); + + this.privilege = privilege; + + try { + localHost = InetAddress.getLocalHost(); + } + catch (UnknownHostException e) { + localHost = null; + } + + Lib.assertTrue(localHost != null); + + reliability = Config.getDouble("NetworkLink.reliability"); + Lib.assertTrue(reliability > 0 && reliability <= 1.0); + + socket = null; + + for (linkAddress=0;linkAddress + * The receive interrupt handler is called every time a packet arrives + * and can be read using receive(). + * + *

+ * The send interrupt handler is called every time a packet sent with + * send() is finished being sent. This means that another + * packet can be sent. + * + * @param receiveInterruptHandler the callback to call when a packet + * arrives. + * @param sendInterruptHandler the callback to call when another + * packet can be sent. + */ + public void setInterruptHandlers(Runnable receiveInterruptHandler, + Runnable sendInterruptHandler) { + this.receiveInterruptHandler = receiveInterruptHandler; + this.sendInterruptHandler = sendInterruptHandler; + } + + private void scheduleReceiveInterrupt() { + privilege.interrupt.schedule(Stats.NetworkTime, "network recv", + receiveInterrupt); + } + + private synchronized void receiveInterrupt() { + Lib.assertTrue(incomingPacket == null); + + if (incomingBytes != null) { + if (Machine.autoGrader().canReceivePacket(privilege)) { + try { + incomingPacket = new Packet(incomingBytes); + + privilege.stats.numPacketsReceived++; + } + catch (MalformedPacketException e) { + } + } + + incomingBytes = null; + notify(); + + if (incomingPacket == null) + scheduleReceiveInterrupt(); + else if (receiveInterruptHandler != null) + receiveInterruptHandler.run(); + } + else { + scheduleReceiveInterrupt(); + } + } + + /** + * Return the next packet received. + * + * @return the next packet received, or null if no packet is + * available. + */ + public Packet receive() { + Packet p = incomingPacket; + + if (incomingPacket != null) { + incomingPacket = null; + scheduleReceiveInterrupt(); + } + + return p; + } + + private void receiveLoop() { + while (true) { + synchronized(this) { + while (incomingBytes != null) { + try { + wait(); + } + catch (InterruptedException e) { + } + } + } + + byte[] packetBytes; + + try { + byte[] buffer = new byte[Packet.maxPacketLength]; + + DatagramPacket dp = new DatagramPacket(buffer, buffer.length); + + socket.receive(dp); + + packetBytes = new byte[dp.getLength()]; + + System.arraycopy(buffer,0, packetBytes,0, packetBytes.length); + } + catch (IOException e) { + return; + } + + synchronized(this) { + incomingBytes = packetBytes; + } + } + } + + private void scheduleSendInterrupt() { + privilege.interrupt.schedule(Stats.NetworkTime, "network send", + sendInterrupt); + } + + private void sendInterrupt() { + Lib.assertTrue(outgoingPacket != null); + + // randomly drop packets, according to its reliability + if (Machine.autoGrader().canSendPacket(privilege) && + Lib.random() <= reliability) { + // ok, no drop + privilege.doPrivileged(new Runnable() { + public void run() { sendPacket(); } + }); + } + else { + outgoingPacket = null; + } + + if (sendInterruptHandler != null) + sendInterruptHandler.run(); + } + + private void sendPacket() { + Packet p = outgoingPacket; + outgoingPacket = null; + + try { + socket.send(new DatagramPacket(p.packetBytes, p.packetBytes.length, + localHost, portBase+p.dstLink)); + + privilege.stats.numPacketsSent++; + } + catch (IOException e) { + } + } + + /** + * Send another packet. If a packet is already being sent, the result is + * not defined. + * + * @param pkt the packet to send. + */ + public void send(Packet pkt) { + if (outgoingPacket == null) + scheduleSendInterrupt(); + + outgoingPacket = pkt; + } + + private static final int hash; + private static final int portBase; + + /** + * The address of the network to which are attached all network links in + * this JVM. This is a hash on the account name of the JVM running this + * Nachos instance. It is used to help prevent packets from other users + * from accidentally interfering with this network. + */ + public static final byte networkID; + + static { + hash = System.getProperty("user.name").hashCode(); + portBase = 0x4E41 + Math.abs(hash%0x4E41); + networkID = (byte) (hash/0x4E41); + } + + private Privilege privilege; + + private Runnable receiveInterrupt; + private Runnable sendInterrupt; + + private Runnable receiveInterruptHandler = null; + private Runnable sendInterruptHandler = null; + + private InetAddress localHost; + private DatagramSocket socket; + + private byte linkAddress; + private double reliability; + + private byte[] incomingBytes = null; + private Packet incomingPacket = null; + private Packet outgoingPacket = null; + + private boolean sendBusy = false; +} diff --git a/machine/OpenFile.java b/machine/OpenFile.java new file mode 100644 index 0000000..18a2942 --- /dev/null +++ b/machine/OpenFile.java @@ -0,0 +1,141 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import java.io.EOFException; + +/** + * A file that supports reading, writing, and seeking. + */ +public class OpenFile { + /** + * Allocate a new OpenFile object with the specified name on the + * specified file system. + * + * @param fileSystem the file system to which this file belongs. + * @param name the name of the file, on that file system. + */ + public OpenFile(FileSystem fileSystem, String name) { + this.fileSystem = fileSystem; + this.name = name; + } + + /** + * Allocate a new unnamed OpenFile that is not associated with any + * file system. + */ + public OpenFile() { + this(null, "unnamed"); + } + + /** + * Get the file system to which this file belongs. + * + * @return the file system to which this file belongs. + */ + public FileSystem getFileSystem() { + return fileSystem; + } + + /** + * Get the name of this open file. + * + * @return the name of this open file. + */ + public String getName() { + return name; + } + + /** + * Read this file starting at the specified position and return the number + * of bytes successfully read. If no bytes were read because of a fatal + * error, returns -1 + * + * @param pos the offset in the file at which to start reading. + * @param buf the buffer to store the bytes in. + * @param offset the offset in the buffer to start storing bytes. + * @param length the number of bytes to read. + * @return the actual number of bytes successfully read, or -1 on failure. + */ + public int read(int pos, byte[] buf, int offset, int length) { + return -1; + } + + /** + * Write this file starting at the specified position and return the number + * of bytes successfully written. If no bytes were written because of a + * fatal error, returns -1. + * + * @param pos the offset in the file at which to start writing. + * @param buf the buffer to get the bytes from. + * @param offset the offset in the buffer to start getting. + * @param length the number of bytes to write. + * @return the actual number of bytes successfully written, or -1 on + * failure. + */ + public int write(int pos, byte[] buf, int offset, int length) { + return -1; + } + + /** + * Get the length of this file. + * + * @return the length of this file, or -1 if this file has no length. + */ + public int length() { + return -1; + } + + /** + * Close this file and release any associated system resources. + */ + public void close() { + } + + /** + * Set the value of the current file pointer. + */ + public void seek(int pos) { + } + + /** + * Get the value of the current file pointer, or -1 if this file has no + * pointer. + */ + public int tell() { + return -1; + } + + /** + * Read this file starting at the current file pointer and return the + * number of bytes successfully read. Advances the file pointer by this + * amount. If no bytes could be* read because of a fatal error, returns -1. + * + * @param buf the buffer to store the bytes in. + * @param offset the offset in the buffer to start storing bytes. + * @param length the number of bytes to read. + * @return the actual number of bytes successfully read, or -1 on failure. + */ + public int read(byte[] buf, int offset, int length) { + return -1; + } + + /** + * Write this file starting at the current file pointer and return the + * number of bytes successfully written. Advances the file pointer by this + * amount. If no bytes could be written because of a fatal error, returns + * -1. + * + * @param buf the buffer to get the bytes from. + * @param offset the offset in the buffer to start getting. + * @param length the number of bytes to write. + * @return the actual number of bytes successfully written, or -1 on + * failure. + */ + public int write(byte[] buf, int offset, int length) { + return -1; + } + + private FileSystem fileSystem; + private String name; +} diff --git a/machine/OpenFileWithPosition.java b/machine/OpenFileWithPosition.java new file mode 100644 index 0000000..514c3c4 --- /dev/null +++ b/machine/OpenFileWithPosition.java @@ -0,0 +1,58 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * An OpenFile that maintains a current file position. + */ +public abstract class OpenFileWithPosition extends OpenFile { + /** + * Allocate a new OpenFileWithPosition with the specified name on + * the specified file system. + * + * @param fileSystem the file system to which this file belongs. + * @param name the name of the file, on that file system. + */ + public OpenFileWithPosition(FileSystem fileSystem, String name) { + super(fileSystem, name); + } + + /** + * Allocate a new unnamed OpenFileWithPosition that is not + * associated with any file system. + */ + public OpenFileWithPosition() { + super(); + } + + public void seek(int position) { + this.position = position; + } + + public int tell() { + return position; + } + + public int read(byte[] buf, int offset, int length) { + int amount = read(position, buf, offset, length); + if (amount == -1) + return -1; + + position += amount; + return amount; + } + + public int write(byte[] buf, int offset, int length) { + int amount = write(position, buf, offset, length); + if (amount == -1) + return -1; + + position += amount; + return amount; + } + + /** + * The current value of the file pointer. + */ + protected int position = 0; +} diff --git a/machine/Packet.java b/machine/Packet.java new file mode 100644 index 0000000..688e0cf --- /dev/null +++ b/machine/Packet.java @@ -0,0 +1,106 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * A link-layer packet. + * + * @see nachos.machine.NetworkLink + */ +public class Packet { + /** + * Allocate a new packet to be sent, using the specified parameters. + * + * @param dstLink the destination link address. + * @param srcLink the source link address. + * @param contents the contents of the packet. + */ + public Packet(int dstLink, int srcLink, byte[] contents) + throws MalformedPacketException { + // make sure the paramters are valid + if (dstLink < 0 || dstLink >= linkAddressLimit || + srcLink < 0 || srcLink >= linkAddressLimit || + contents.length > maxContentsLength) + throw new MalformedPacketException(); + + this.dstLink = dstLink; + this.srcLink = srcLink; + this.contents = contents; + + packetBytes = new byte[headerLength + contents.length]; + + packetBytes[0] = NetworkLink.networkID; + packetBytes[1] = (byte) dstLink; + packetBytes[2] = (byte) srcLink; + packetBytes[3] = (byte) contents.length; + + // if java had subarrays, i'd use them. but System.arraycopy is ok... + System.arraycopy(contents, 0, packetBytes, headerLength, + contents.length); + } + + /** + * Allocate a new packet using the specified array of bytes received from + * the network. + * + * @param packetBytes the bytes making up this packet. + */ + public Packet(byte[] packetBytes) throws MalformedPacketException { + this.packetBytes = packetBytes; + + // make sure we have a valid header + if (packetBytes.length < headerLength || + packetBytes[0] != NetworkLink.networkID || + packetBytes[1] < 0 || packetBytes[1] >= linkAddressLimit || + packetBytes[2] < 0 || packetBytes[2] >= linkAddressLimit || + packetBytes[3] < 0 || packetBytes[3] > packetBytes.length-4) + throw new MalformedPacketException(); + + dstLink = packetBytes[1]; + srcLink = packetBytes[2]; + + contents = new byte[packetBytes[3]]; + System.arraycopy(packetBytes, headerLength, contents, 0, + contents.length); + } + + /** This packet, as an array of bytes that can be sent on a network. */ + public byte[] packetBytes; + /** The address of the destination link of this packet. */ + public int dstLink; + /** The address of the source link of this packet. */ + public int srcLink; + /** The contents of this packet, excluding the link-layer header. */ + public byte[] contents; + + /** + * The number of bytes in a link-layer packet header. The header is + * formatted as follows: + * + * + * + * + * + * + * + *
offsetsizevalue
01network ID (collision detecting)
11destination link address
21source link address
31length of contents
+ */ + public static final int headerLength = 4; + /** + * The maximum length, in bytes, of a packet that can be sent or received + * on the network. + */ + public static final int maxPacketLength = 32; + /** + * The maximum number of content bytes (not including the header). Note + * that this is just maxPacketLength - headerLength. + */ + public static final int maxContentsLength = maxPacketLength - headerLength; + + /** + * The upper limit on Nachos link addresses. All link addresses fall + * between 0 and linkAddressLimit - 1. + */ + public static final int linkAddressLimit = 128; +} + diff --git a/machine/Processor.java b/machine/Processor.java new file mode 100644 index 0000000..b170e7d --- /dev/null +++ b/machine/Processor.java @@ -0,0 +1,1321 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; + +/** + * The Processor class simulates a MIPS processor that supports a + * subset of the R3000 instruction set. Specifically, the processor lacks all + * coprocessor support, and can only execute in user mode. Address translation + * information is accessed via the API. The API also allows a kernel to set an + * exception handler to be called on any user mode exception. + * + *

+ * The Processor API is re-entrant, so a single simulated processor + * can be shared by multiple user threads. + * + *

+ * An instance of a Processor also includes pages of physical memory + * accessible to user programs, the size of which is fixed by the constructor. + */ +public final class Processor { + /** + * Allocate a new MIPS processor, with the specified amount of memory. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + * @param numPhysPages the number of pages of physical memory to + * attach. + */ + public Processor(Privilege privilege, int numPhysPages) { + System.out.print(" processor"); + + this.privilege = privilege; + privilege.processor = new ProcessorPrivilege(); + + Class clsKernel = Lib.loadClass(Config.getString("Kernel.kernel")); + Class clsVMKernel = Lib.tryLoadClass("nachos.vm.VMKernel"); + + usingTLB = + (clsVMKernel != null && clsVMKernel.isAssignableFrom(clsKernel)); + + this.numPhysPages = numPhysPages; + + for (int i=0; i + * When the exception handler is called, interrupts will be enabled, and + * the CPU cause register will specify the cause of the exception (see the + * exception* constants). + * + * @param exceptionHandler the kernel exception handler. + */ + public void setExceptionHandler(Runnable exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + /** + * Get the exception handler, set by the last call to + * setExceptionHandler(). + * + * @return the exception handler. + */ + public Runnable getExceptionHandler() { + return exceptionHandler; + } + + /** + * Start executing instructions at the current PC. Never returns. + */ + public void run() { + Lib.debug(dbgProcessor, "starting program in current thread"); + + registers[regNextPC] = registers[regPC] + 4; + + Machine.autoGrader().runProcessor(privilege); + + Instruction inst = new Instruction(); + + while (true) { + try { + inst.run(); + } + catch (MipsException e) { + e.handle(); + } + + privilege.interrupt.tick(false); + } + } + + /** + * Read and return the contents of the specified CPU register. + * + * @param number the register to read. + * @return the value of the register. + */ + public int readRegister(int number) { + Lib.assertTrue(number >= 0 && number < numUserRegisters); + + return registers[number]; + } + + /** + * Write the specified value into the specified CPU register. + * + * @param number the register to write. + * @param value the value to write. + */ + public void writeRegister(int number, int value) { + Lib.assertTrue(number >= 0 && number < numUserRegisters); + + if (number != 0) + registers[number] = value; + } + + /** + * Test whether this processor uses a software-managed TLB, or single-level + * paging. + * + *

+ * If false, this processor directly supports single-level paging; + * use setPageTable(). + * + *

+ * If true, this processor has a software-managed TLB; + * use getTLBSize(), readTLBEntry(), and + * writeTLBEntry(). + * + *

+ * Using a method associated with the wrong address translation mechanism + * will result in an assertion failure. + * + * @return true if this processor has a software-managed TLB. + */ + public boolean hasTLB() { + return usingTLB; + } + + /** + * Get the current page table, set by the last call to setPageTable(). + * + * @return the current page table. + */ + public TranslationEntry[] getPageTable() { + Lib.assertTrue(!usingTLB); + + return translations; + } + + /** + * Set the page table pointer. All further address translations will use + * the specified page table. The size of the current address space will be + * determined from the length of the page table array. + * + * @param pageTable the page table to use. + */ + public void setPageTable(TranslationEntry[] pageTable) { + Lib.assertTrue(!usingTLB); + + this.translations = pageTable; + } + + /** + * Return the number of entries in this processor's TLB. + * + * @return the number of entries in this processor's TLB. + */ + public int getTLBSize() { + Lib.assertTrue(usingTLB); + + return tlbSize; + } + + /** + * Returns the specified TLB entry. + * + * @param number the index into the TLB. + * @return the contents of the specified TLB entry. + */ + public TranslationEntry readTLBEntry(int number) { + Lib.assertTrue(usingTLB); + Lib.assertTrue(number >= 0 && number < tlbSize); + + return new TranslationEntry(translations[number]); + } + + /** + * Fill the specified TLB entry. + * + *

+ * The TLB is fully associative, so the location of an entry within the TLB + * does not affect anything. + * + * @param number the index into the TLB. + * @param entry the new contents of the TLB entry. + */ + public void writeTLBEntry(int number, TranslationEntry entry) { + Lib.assertTrue(usingTLB); + Lib.assertTrue(number >= 0 && number < tlbSize); + + translations[number] = new TranslationEntry(entry); + } + + /** + * Return the number of pages of physical memory attached to this simulated + * processor. + * + * @return the number of pages of physical memory. + */ + public int getNumPhysPages() { + return numPhysPages; + } + + /** + * Return a reference to the physical memory array. The size of this array + * is pageSize * getNumPhysPages(). + * + * @return the main memory array. + */ + public byte[] getMemory() { + return mainMemory; + } + + /** + * Concatenate a page number and an offset into an address. + * + * @param page the page number. Must be between 0 and + * (232 / pageSize) - 1. + * @param offset the offset within the page. Must be between 0 + * and + * pageSize - 1. + * @return a 32-bit address consisting of the specified page and offset. + */ + public static int makeAddress(int page, int offset) { + Lib.assertTrue(page >= 0 && page < maxPages); + Lib.assertTrue(offset >= 0 && offset < pageSize); + + return (page * pageSize) | offset; + } + + /** + * Extract the page number component from a 32-bit address. + * + * @param address the 32-bit address. + * @return the page number component of the address. + */ + public static int pageFromAddress(int address) { + return (int) (((long) address & 0xFFFFFFFFL) / pageSize); + } + + /** + * Extract the offset component from an address. + * + * @param address the 32-bit address. + * @return the offset component of the address. + */ + public static int offsetFromAddress(int address) { + return (int) (((long) address & 0xFFFFFFFFL) % pageSize); + } + + private void finishLoad() { + delayedLoad(0, 0, 0); + } + + /** + * Translate a virtual address into a physical address, using either a + * page table or a TLB. Check for alignment, make sure the virtual page is + * valid, make sure a read-only page is not being written, make sure the + * resulting physical page is valid, and then return the resulting physical + * address. + * + * @param vaddr the virtual address to translate. + * @param size the size of the memory reference (must be 1, 2, or 4). + * @param writing true if the memory reference is a write. + * @return the physical address. + * @exception MipsException if a translation error occurred. + */ + private int translate(int vaddr, int size, boolean writing) + throws MipsException { + if (Lib.test(dbgProcessor)) + System.out.println("\ttranslate vaddr=0x" + Lib.toHexString(vaddr) + + (writing ? ", write" : ", read...")); + + // check alignment + if ((vaddr & (size-1)) != 0) { + Lib.debug(dbgProcessor, "\t\talignment error"); + throw new MipsException(exceptionAddressError, vaddr); + } + + // calculate virtual page number and offset from the virtual address + int vpn = pageFromAddress(vaddr); + int offset = offsetFromAddress(vaddr); + + TranslationEntry entry = null; + + // if not using a TLB, then the vpn is an index into the table + if (!usingTLB) { + if (translations == null || vpn >= translations.length || + translations[vpn] == null || + !translations[vpn].valid) { + privilege.stats.numPageFaults++; + Lib.debug(dbgProcessor, "\t\tpage fault"); + throw new MipsException(exceptionPageFault, vaddr); + } + + entry = translations[vpn]; + } + // else, look through all TLB entries for matching vpn + else { + for (int i=0; i= numPhysPages) { + Lib.debug(dbgProcessor, "\t\tbad ppn"); + throw new MipsException(exceptionBusError, vaddr); + } + + // set used and dirty bits as appropriate + entry.used = true; + if (writing) + entry.dirty = true; + + int paddr = (ppn*pageSize) + offset; + + if (Lib.test(dbgProcessor)) + System.out.println("\t\tpaddr=0x" + Lib.toHexString(paddr)); + return paddr; + } + + /** + * Read size (1, 2, or 4) bytes of virtual memory at vaddr, + * and return the result. + * + * @param vaddr the virtual address to read from. + * @param size the number of bytes to read (1, 2, or 4). + * @return the value read. + * @exception MipsException if a translation error occurred. + */ + private int readMem(int vaddr, int size) throws MipsException { + if (Lib.test(dbgProcessor)) + System.out.println("\treadMem vaddr=0x" + Lib.toHexString(vaddr) + + ", size=" + size); + + Lib.assertTrue(size==1 || size==2 || size==4); + + int value = Lib.bytesToInt(mainMemory, translate(vaddr, size, false), + size); + + if (Lib.test(dbgProcessor)) + System.out.println("\t\tvalue read=0x" + + Lib.toHexString(value, size*2)); + + return value; + } + + /** + * Write value to size (1, 2, or 4) bytes of virtual memory + * starting at vaddr. + * + * @param vaddr the virtual address to write to. + * @param size the number of bytes to write (1, 2, or 4). + * @param value the value to store. + * @exception MipsException if a translation error occurred. + */ + private void writeMem(int vaddr, int size, int value) + throws MipsException { + if (Lib.test(dbgProcessor)) + System.out.println("\twriteMem vaddr=0x" + Lib.toHexString(vaddr) + + ", size=" + size + ", value=0x" + + Lib.toHexString(value, size*2)); + + Lib.assertTrue(size==1 || size==2 || size==4); + + Lib.bytesFromInt(mainMemory, translate(vaddr, size, true), size, + value); + } + + /** + * Complete the in progress delayed load and scheduled a new one. + * + * @param nextLoadTarget the target register of the new load. + * @param nextLoadValue the value to be loaded into the new target. + * @param nextLoadMask the mask specifying which bits in the new + * target are to be overwritten. If a bit in + * nextLoadMask is 0, then the + * corresponding bit of register + * nextLoadTarget will not be written. + */ + private void delayedLoad(int nextLoadTarget, int nextLoadValue, + int nextLoadMask) { + // complete previous delayed load, if not modifying r0 + if (loadTarget != 0) { + int savedBits = registers[loadTarget] & ~loadMask; + int newBits = loadValue & loadMask; + registers[loadTarget] = savedBits | newBits; + } + + // schedule next load + loadTarget = nextLoadTarget; + loadValue = nextLoadValue; + loadMask = nextLoadMask; + } + + /** + * Advance the PC to the next instruction. + * + *

+ * Transfer the contents of the nextPC register into the PC register, and + * then add 4 to the value in the nextPC register. Same as + * advancePC(readRegister(regNextPC)+4). + * + *

+ * Use after handling a syscall exception so that the processor will move + * on to the next instruction. + */ + public void advancePC() { + advancePC(registers[regNextPC]+4); + } + + /** + * Transfer the contents of the nextPC register into the PC register, and + * then write the nextPC register. + * + * @param nextPC the new value of the nextPC register. + */ + private void advancePC(int nextPC) { + registers[regPC] = registers[regNextPC]; + registers[regNextPC] = nextPC; + } + + /** Caused by a syscall instruction. */ + public static final int exceptionSyscall = 0; + /** Caused by an access to an invalid virtual page. */ + public static final int exceptionPageFault = 1; + /** Caused by an access to a virtual page not mapped by any TLB entry. */ + public static final int exceptionTLBMiss = 2; + /** Caused by a write access to a read-only virtual page. */ + public static final int exceptionReadOnly = 3; + /** Caused by an access to an invalid physical page. */ + public static final int exceptionBusError = 4; + /** Caused by an access to a misaligned virtual address. */ + public static final int exceptionAddressError = 5; + /** Caused by an overflow by a signed operation. */ + public static final int exceptionOverflow = 6; + /** Caused by an attempt to execute an illegal instruction. */ + public static final int exceptionIllegalInstruction = 7; + + /** The names of the CPU exceptions. */ + public static final String exceptionNames[] = { + "syscall ", + "page fault ", + "TLB miss ", + "read-only ", + "bus error ", + "address error", + "overflow ", + "illegal inst " + }; + + /** Index of return value register 0. */ + public static final int regV0 = 2; + /** Index of return value register 1. */ + public static final int regV1 = 3; + /** Index of argument register 0. */ + public static final int regA0 = 4; + /** Index of argument register 1. */ + public static final int regA1 = 5; + /** Index of argument register 2. */ + public static final int regA2 = 6; + /** Index of argument register 3. */ + public static final int regA3 = 7; + /** Index of the stack pointer register. */ + public static final int regSP = 29; + /** Index of the return address register. */ + public static final int regRA = 31; + /** Index of the low register, used for multiplication and division. */ + public static final int regLo = 32; + /** Index of the high register, used for multiplication and division. */ + public static final int regHi = 33; + /** Index of the program counter register. */ + public static final int regPC = 34; + /** Index of the next program counter register. */ + public static final int regNextPC = 35; + /** Index of the exception cause register. */ + public static final int regCause = 36; + /** Index of the exception bad virtual address register. */ + public static final int regBadVAddr = 37; + + /** The total number of software-accessible CPU registers. */ + public static final int numUserRegisters = 38; + + /** Provides privilege to this processor. */ + private Privilege privilege; + + /** MIPS registers accessible to the kernel. */ + private int registers[] = new int[numUserRegisters]; + + /** The registered target of the delayed load currently in progress. */ + private int loadTarget = 0; + /** The bits to be modified by the delayed load currently in progress. */ + private int loadMask; + /** The value to be loaded by the delayed load currently in progress. */ + private int loadValue; + + /** true if using a software-managed TLB. */ + private boolean usingTLB; + /** Number of TLB entries. */ + private int tlbSize = 4; + /** + * Either an associative or direct-mapped set of translation entries, + * depending on whether there is a TLB. + */ + private TranslationEntry[] translations; + + /** Size of a page, in bytes. */ + public static final int pageSize = 0x400; + /** Number of pages in a 32-bit address space. */ + public static final int maxPages = (int) (0x100000000L / pageSize); + /** Number of physical pages in memory. */ + private int numPhysPages; + /** Main memory for user programs. */ + private byte[] mainMemory; + + /** The kernel exception handler, called on every user exception. */ + private Runnable exceptionHandler = null; + + private static final char dbgProcessor = 'p'; + private static final char dbgDisassemble = 'm'; + private static final char dbgFullDisassemble = 'M'; + + private class ProcessorPrivilege implements Privilege.ProcessorPrivilege { + public void flushPipe() { + finishLoad(); + } + } + + private class MipsException extends Exception { + public MipsException(int cause) { + Lib.assertTrue(cause >= 0 && cause < exceptionNames.length); + + this.cause = cause; + } + + public MipsException(int cause, int badVAddr) { + this(cause); + + hasBadVAddr = true; + this.badVAddr = badVAddr; + } + + public void handle() { + writeRegister(regCause, cause); + + if (hasBadVAddr) + writeRegister(regBadVAddr, badVAddr); + + if (Lib.test(dbgDisassemble) || Lib.test(dbgFullDisassemble)) + System.out.println("exception: " + exceptionNames[cause]); + + finishLoad(); + + Lib.assertTrue(exceptionHandler != null); + + // autograder might not want kernel to know about this exception + if (!Machine.autoGrader().exceptionHandler(privilege)) + return; + + exceptionHandler.run(); + } + + private boolean hasBadVAddr = false; + private int cause, badVAddr; + } + + private class Instruction { + public void run() throws MipsException { + // hopefully this looks familiar to 152 students? + fetch(); + decode(); + execute(); + writeBack(); + } + + private boolean test(int flag) { + return Lib.test(flag, flags); + } + + private void fetch() throws MipsException { + if ((Lib.test(dbgDisassemble) && !Lib.test(dbgProcessor)) || + Lib.test(dbgFullDisassemble)) + System.out.print("PC=0x" + Lib.toHexString(registers[regPC]) + + "\t"); + + value = readMem(registers[regPC], 4); + } + + private void decode() { + op = Lib.extract(value, 26, 6); + rs = Lib.extract(value, 21, 5); + rt = Lib.extract(value, 16, 5); + rd = Lib.extract(value, 11, 5); + sh = Lib.extract(value, 6, 5); + func = Lib.extract(value, 0, 6); + target = Lib.extract(value, 0, 26); + imm = Lib.extend(value, 0, 16); + + Mips info; + switch (op) { + case 0: + info = Mips.specialtable[func]; + break; + case 1: + info = Mips.regimmtable[rt]; + break; + default: + info = Mips.optable[op]; + break; + } + + operation = info.operation; + name = info.name; + format = info.format; + flags = info.flags; + + mask = 0xFFFFFFFF; + branch = true; + + // get memory access size + if (test(Mips.SIZEB)) + size = 1; + else if (test(Mips.SIZEH)) + size = 2; + else if (test(Mips.SIZEW)) + size = 4; + else + size = 0; + + // get nextPC + nextPC = registers[regNextPC]+4; + + // get dstReg + if (test(Mips.DSTRA)) + dstReg = regRA; + else if (format == Mips.IFMT) + dstReg = rt; + else if (format == Mips.RFMT) + dstReg = rd; + else + dstReg = -1; + + // get jtarget + if (format == Mips.RFMT) + jtarget = registers[rs]; + else if (format == Mips.IFMT) + jtarget = registers[regNextPC] + (imm<<2); + else if (format == Mips.JFMT) + jtarget = (registers[regNextPC]&0xF0000000) | (target<<2); + else + jtarget = -1; + + // get imm + if (test(Mips.UNSIGNED)) { + imm &= 0xFFFF; + } + + // get addr + addr = registers[rs] + imm; + + // get src1 + if (test(Mips.SRC1SH)) + src1 = sh; + else + src1 = registers[rs]; + + // get src2 + if (test(Mips.SRC2IMM)) + src2 = imm; + else + src2 = registers[rt]; + + if (test(Mips.UNSIGNED)) { + src1 &= 0xFFFFFFFFL; + src2 &= 0xFFFFFFFFL; + } + + if (Lib.test(dbgDisassemble) || Lib.test(dbgFullDisassemble)) + print(); + } + + private void print() { + if (Lib.test(dbgDisassemble) && Lib.test(dbgProcessor) && + !Lib.test(dbgFullDisassemble)) + System.out.print("PC=0x" + Lib.toHexString(registers[regPC]) + + "\t"); + + if (operation == Mips.INVALID) { + System.out.print("invalid: op=" + Lib.toHexString(op, 2) + + " rs=" + Lib.toHexString(rs, 2) + + " rt=" + Lib.toHexString(rt, 2) + + " rd=" + Lib.toHexString(rd, 2) + + " sh=" + Lib.toHexString(sh, 2) + + " func=" + Lib.toHexString(func, 2) + + "\n"); + return; + } + + int spaceIndex = name.indexOf(' '); + Lib.assertTrue(spaceIndex!=-1 && spaceIndex==name.lastIndexOf(' ')); + + String instname = name.substring(0, spaceIndex); + char[] args = name.substring(spaceIndex+1).toCharArray(); + + System.out.print(instname + "\t"); + + int minCharsPrinted = 0, maxCharsPrinted = 0; + + for (int i=0; i> (src1&0x1F); + break; + case Mips.SRL: + dst = src2 >>> (src1&0x1F); + break; + + case Mips.SLT: + dst = (src1= 0); + break; + case Mips.BGTZ: + branch = (src1 > 0); + break; + case Mips.BLEZ: + branch = (src1 <= 0); + break; + case Mips.BLTZ: + branch = (src1 < 0); + break; + + case Mips.JUMP: + break; + + case Mips.MFLO: + dst = registers[regLo]; + break; + case Mips.MFHI: + dst = registers[regHi]; + break; + case Mips.MTLO: + registers[regLo] = (int) src1; + break; + case Mips.MTHI: + registers[regHi] = (int) src1; + break; + + case Mips.SYSCALL: + throw new MipsException(exceptionSyscall); + + case Mips.LOAD: + value = readMem(addr, size); + + if (!test(Mips.UNSIGNED)) + dst = Lib.extend(value, 0, size*8); + else + dst = value; + + break; + + case Mips.LWL: + value = readMem(addr&~0x3, 4); + + // LWL shifts the input left so the addressed byte is highest + preserved = (3-(addr&0x3))*8; // number of bits to preserve + mask = -1 << preserved; // preserved bits are 0 in mask + dst = value << preserved; // shift input to correct place + addr &= ~0x3; + + break; + + case Mips.LWR: + value = readMem(addr&~0x3, 4); + + // LWR shifts the input right so the addressed byte is lowest + preserved = (addr&0x3)*8; // number of bits to preserve + mask = -1 >>> preserved; // preserved bits are 0 in mask + dst = value >>> preserved; // shift input to correct place + addr &= ~0x3; + + break; + + case Mips.STORE: + writeMem(addr, size, (int) src2); + break; + + case Mips.SWL: + value = readMem(addr&~0x3, 4); + + // SWL shifts highest order byte into the addressed position + preserved = (3-(addr&0x3))*8; + mask = -1 >>> preserved; + dst = src2 >>> preserved; + + // merge values + dst = (dst & mask) | (value & ~mask); + + writeMem(addr&~0x3, 4, (int) dst); + break; + + case Mips.SWR: + value = readMem(addr&~0x3, 4); + + // SWR shifts the lowest order byte into the addressed position + preserved = (addr&0x3)*8; + mask = -1 << preserved; + dst = src2 << preserved; + + // merge values + dst = (dst & mask) | (value & ~mask); + + writeMem(addr&~0x3, 4, (int) dst); + break; + + case Mips.UNIMPL: + System.err.println("Warning: encountered unimplemented inst"); + + case Mips.INVALID: + throw new MipsException(exceptionIllegalInstruction); + + default: + Lib.assertNotReached(); + } + } + + private void writeBack() throws MipsException { + // if instruction is signed, but carry bit !+ sign bit, throw + if (test(Mips.OVERFLOW) && Lib.test(dst,31) != Lib.test(dst,32)) + throw new MipsException(exceptionOverflow); + + if (test(Mips.DELAYEDLOAD)) + delayedLoad(dstReg, (int) dst, mask); + else + finishLoad(); + + if (test(Mips.LINK)) + dst = nextPC; + + if (test(Mips.DST) && dstReg != 0) + registers[dstReg] = (int) dst; + + if ((test(Mips.DST) || test(Mips.DELAYEDLOAD)) && dstReg != 0) { + if (Lib.test(dbgFullDisassemble)) { + System.out.print("#0x" + Lib.toHexString((int) dst)); + if (test(Mips.DELAYEDLOAD)) + System.out.print(" (delayed load)"); + } + } + + if (test(Mips.BRANCH) && branch) { + nextPC = jtarget; + } + + advancePC(nextPC); + + if ((Lib.test(dbgDisassemble) && !Lib.test(dbgProcessor)) || + Lib.test(dbgFullDisassemble)) + System.out.print("\n"); + } + + // state used to execute a single instruction + int value, op, rs, rt, rd, sh, func, target, imm; + int operation, format, flags; + String name; + + int size; + int addr, nextPC, jtarget, dstReg; + long src1, src2, dst; + int mask; + boolean branch; + } + + private static class Mips { + Mips() { + } + + Mips(int operation, String name) { + this.operation = operation; + this.name = name; + } + + Mips(int operation, String name, int format, int flags) { + this(operation, name); + this.format = format; + this.flags = flags; + } + + int operation = INVALID; + String name = "invalid "; + int format; + int flags; + + // operation types + static final int + INVALID = 0, + UNIMPL = 1, + ADD = 2, + SUB = 3, + MULT = 4, + DIV = 5, + SLL = 6, + SRA = 7, + SRL = 8, + SLT = 9, + AND = 10, + OR = 11, + NOR = 12, + XOR = 13, + LUI = 14, + MFLO = 21, + MFHI = 22, + MTLO = 23, + MTHI = 24, + JUMP = 25, + BEQ = 26, + BNE = 27, + BLEZ = 28, + BGTZ = 29, + BLTZ = 30, + BGEZ = 31, + SYSCALL = 32, + LOAD = 33, + LWL = 36, + LWR = 37, + STORE = 38, + SWL = 39, + SWR = 40, + MAX = 40; + + static final int + IFMT = 1, + JFMT = 2, + RFMT = 3; + + static final int + DST = 0x00000001, + DSTRA = 0x00000002, + OVERFLOW = 0x00000004, + SRC1SH = 0x00000008, + SRC2IMM = 0x00000010, + UNSIGNED = 0x00000020, + LINK = 0x00000040, + DELAYEDLOAD = 0x00000080, + SIZEB = 0x00000100, + SIZEH = 0x00000200, + SIZEW = 0x00000400, + BRANCH = 0x00000800; + + static final char + RS = 's', + RT = 't', + RD = 'd', + IMM = 'i', + SHIFTAMOUNT = 'h', + ADDR = 'a', // imm(rs) + TARGET = 'j', + RETURNADDRESS = 'r'; // rd, or none if rd=31; can't be last + + static final Mips[] optable = { + new Mips(), // special + new Mips(), // reg-imm + new Mips(JUMP, "j j", JFMT, BRANCH), + new Mips(JUMP, "jal j", JFMT, BRANCH|LINK|DST|DSTRA), + new Mips(BEQ, "beq stj", IFMT, BRANCH), + new Mips(BNE, "bne stj", IFMT, BRANCH), + new Mips(BLEZ, "blez sj", IFMT, BRANCH), + new Mips(BGTZ, "bgtz sj", IFMT, BRANCH), + new Mips(ADD, "addi tsi", IFMT, DST|SRC2IMM|OVERFLOW), + new Mips(ADD, "addiu tsi", IFMT, DST|SRC2IMM), + new Mips(SLT, "slti tsi", IFMT, DST|SRC2IMM), + new Mips(SLT, "sltiu tsi", IFMT, DST|SRC2IMM|UNSIGNED), + new Mips(AND, "andi tsi", IFMT, DST|SRC2IMM|UNSIGNED), + new Mips(OR, "ori tsi", IFMT, DST|SRC2IMM|UNSIGNED), + new Mips(XOR, "xori tsi", IFMT, DST|SRC2IMM|UNSIGNED), + new Mips(LUI, "lui ti", IFMT, DST|SRC2IMM|UNSIGNED), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(BEQ, "beql stj", IFMT, BRANCH), + new Mips(BNE, "bnel stj", IFMT, BRANCH), + new Mips(BLEZ, "blezl sj", IFMT, BRANCH), + new Mips(BGTZ, "bgtzl sj", IFMT, BRANCH), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(LOAD, "lb ta", IFMT, DELAYEDLOAD|SIZEB), + new Mips(LOAD, "lh ta", IFMT, DELAYEDLOAD|SIZEH), + new Mips(LWL, "lwl ta", IFMT, DELAYEDLOAD), + new Mips(LOAD, "lw ta", IFMT, DELAYEDLOAD|SIZEW), + new Mips(LOAD, "lbu ta", IFMT, DELAYEDLOAD|SIZEB|UNSIGNED), + new Mips(LOAD, "lhu ta", IFMT, DELAYEDLOAD|SIZEH|UNSIGNED), + new Mips(LWR, "lwr ta", IFMT, DELAYEDLOAD), + new Mips(), + new Mips(STORE, "sb ta", IFMT, SIZEB), + new Mips(STORE, "sh ta", IFMT, SIZEH), + new Mips(SWL, "swl ta", IFMT, 0), + new Mips(STORE, "sw ta", IFMT, SIZEW), + new Mips(), + new Mips(), + new Mips(SWR, "swr ta", IFMT, 0), + new Mips(), + new Mips(UNIMPL, "ll "), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(UNIMPL, "sc "), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + }; + + static final Mips[] specialtable = { + new Mips(SLL, "sll dth", RFMT, DST|SRC1SH), + new Mips(), + new Mips(SRL, "srl dth", RFMT, DST|SRC1SH), + new Mips(SRA, "sra dth", RFMT, DST|SRC1SH), + new Mips(SLL, "sllv dts", RFMT, DST), + new Mips(), + new Mips(SRL, "srlv dts", RFMT, DST), + new Mips(SRA, "srav dts", RFMT, DST), + new Mips(JUMP, "jr s", RFMT, BRANCH), + new Mips(JUMP, "jalr rs", RFMT, BRANCH|LINK|DST), + new Mips(), + new Mips(), + new Mips(SYSCALL, "syscall "), + new Mips(UNIMPL, "break "), + new Mips(), + new Mips(UNIMPL, "sync "), + new Mips(MFHI, "mfhi d", RFMT, DST), + new Mips(MTHI, "mthi s", RFMT, 0), + new Mips(MFLO, "mflo d", RFMT, DST), + new Mips(MTLO, "mtlo s", RFMT, 0), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(MULT, "mult st", RFMT, 0), + new Mips(MULT, "multu st", RFMT, UNSIGNED), + new Mips(DIV, "div st", RFMT, 0), + new Mips(DIV, "divu st", RFMT, UNSIGNED), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(ADD, "add dst", RFMT, DST|OVERFLOW), + new Mips(ADD, "addu dst", RFMT, DST), + new Mips(SUB, "sub dst", RFMT, DST|OVERFLOW), + new Mips(SUB, "subu dst", RFMT, DST), + new Mips(AND, "and dst", RFMT, DST), + new Mips(OR, "or dst", RFMT, DST), + new Mips(XOR, "xor dst", RFMT, DST), + new Mips(NOR, "nor dst", RFMT, DST), + new Mips(), + new Mips(), + new Mips(SLT, "slt dst", RFMT, DST), + new Mips(SLT, "sltu dst", RFMT, DST|UNSIGNED), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + }; + + static final Mips[] regimmtable = { + new Mips(BLTZ, "bltz sj", IFMT, BRANCH), + new Mips(BGEZ, "bgez sj", IFMT, BRANCH), + new Mips(BLTZ, "bltzl sj", IFMT, BRANCH), + new Mips(BGEZ, "bgezl sj", IFMT, BRANCH), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(BLTZ, "bltzal sj", IFMT, BRANCH|LINK|DST|DSTRA), + new Mips(BGEZ, "bgezal sj", IFMT, BRANCH|LINK|DST|DSTRA), + new Mips(BLTZ, "bltzlal sj", IFMT, BRANCH|LINK|DST|DSTRA), + new Mips(BGEZ, "bgezlal sj", IFMT, BRANCH|LINK|DST|DSTRA), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips(), + new Mips() + }; + } +} diff --git a/machine/RiderControls.java b/machine/RiderControls.java new file mode 100644 index 0000000..43d4817 --- /dev/null +++ b/machine/RiderControls.java @@ -0,0 +1,142 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * A set of controls that can be used by a rider controller. Each rider uses a + * distinct RiderControls object. + */ +public interface RiderControls { + /** + * Return the number of floors in the elevator bank. If n is the + * number of floors in the bank, then the floors are numbered 0 + * (the ground floor) through n - 1 (the top floor). + * + * @return the number of floors in the bank. + */ + public int getNumFloors(); + + /** + * Return the number of elevators in the elevator bank. If n is the + * number of elevators in the bank, then the elevators are numbered + * 0 through n - 1. + * + * @return the numbe rof elevators in the bank. + */ + public int getNumElevators(); + + /** + * Set the rider's interrupt handler. This handler will be called when the + * rider observes an event. + * + * @param handler the rider's interrupt handler. + */ + public void setInterruptHandler(Runnable handler); + + /** + * Return the current location of the rider. If the rider is in motion, + * the returned value will be within one of the exact location. + * + * @return the floor the rider is on. + */ + public int getFloor(); + + /** + * Return an array specifying the sequence of floors at which this rider + * has successfully exited an elevator. This array naturally does not + * count the floor the rider started on, nor does it count floors where + * the rider is in the elevator and does not exit. + * + * @return an array specifying the floors this rider has visited. + */ + public int[] getFloors(); + + /** + * Return the indicated direction of the specified elevator, set by + * ElevatorControls.setDirectionDisplay(). + * + * @param elevator the elevator to check the direction of. + * @return the displayed direction for the elevator. + * + * @see nachos.machine.ElevatorControls#setDirectionDisplay + */ + public int getDirectionDisplay(int elevator); + + /** + * Press a direction button. If up is true, invoke + * pressUpButton(); otherwise, invoke pressDownButton(). + * + * @param up true to press the up button, false to + * press the down button. + * @return the return value of pressUpButton() or of + * pressDownButton(). + */ + public boolean pressDirectionButton(boolean up); + + /** + * Press the up button. The rider must not be on the top floor and must not + * be inside an elevator. If an elevator is on the same floor as this + * rider, has the doors open, and says it is going up, does nothing and + * returns false. + * + * @return true if the button event was sent to the elevator + * controller. + */ + public boolean pressUpButton(); + + /** + * Press the down button. The rider must not be on the bottom floor and + * must not be inside an elevator. If an elevator is on the same floor as + * as this rider, has the doors open, and says it is going down, does + * nothing and returns false. + * + * @return true if the button event was sent to the elevator + * controller. + */ + public boolean pressDownButton(); + + /** + * Enter an elevator. A rider cannot enter an elevator if its doors are not + * open at the same floor, or if the elevator is full. The rider must not + * already be in an elevator. + * + * @param elevator the elevator to enter. + * @return true if the rider successfully entered the elevator. + */ + public boolean enterElevator(int elevator); + + /** + * Press a floor button. The rider must be inside an elevator. If the + * elevator already has its doors open on floor, does nothing and + * returns false. + * + * @param floor the button to press. + * @return true if the button event was sent to the elevator + * controller. + */ + public boolean pressFloorButton(int floor); + + /** + * Exit the elevator. A rider cannot exit the elevator if its doors are not + * open on the requested floor. The rider must already be in an elevator. + * + * @param floor the floor to exit on. + * @return true if the rider successfully got off the elevator. + */ + public boolean exitElevator(int floor); + + /** + * Call when the rider is finished. + */ + public void finish(); + + /** + * Return the next event in the event queue. Note that there may be + * multiple events pending when a rider interrupt occurs, so this + * method should be called repeatedly until it returns null. + * + * @return the next event, or null if no further events are + * currently pending. + */ + public RiderEvent getNextEvent(); +} diff --git a/machine/RiderEvent.java b/machine/RiderEvent.java new file mode 100644 index 0000000..bbf87f7 --- /dev/null +++ b/machine/RiderEvent.java @@ -0,0 +1,33 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * An event that affects rider software. If a rider is outside the elevators, + * it will only receive events on the same floor as the rider. If a rider is + * inside an elevator, it will only receive events pertaining to that elevator. + */ +public final class RiderEvent { + public RiderEvent(int event, int floor, int elevator, int direction) { + this.event = event; + this.floor = floor; + this.elevator = elevator; + this.direction = direction; + } + + /** The event identifier. Refer to the event* constants. */ + public final int event; + /** The floor pertaining to the event, or -1 if not applicable. */ + public final int floor; + /** The elevator pertaining to the event, or -1 if not applicable. */ + public final int elevator; + /** The direction display of the elevator (neither if not applicable). */ + public final int direction; + + /** An elevator's doors have opened. */ + public static final int eventDoorsOpened = 0; + /** An elevator's doors were open and its direction display changed. */ + public static final int eventDirectionChanged = 1; + /** An elevator's doors have closed. */ + public static final int eventDoorsClosed = 2; +} diff --git a/machine/RiderInterface.java b/machine/RiderInterface.java new file mode 100644 index 0000000..94df123 --- /dev/null +++ b/machine/RiderInterface.java @@ -0,0 +1,51 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +/** + * A single rider. Each rider accesses the elevator bank through an + * instance of RiderControls. + */ +public interface RiderInterface extends Runnable { + /** + * Initialize this rider. The rider will access the elevator bank through + * controls, and the rider will make stops at different floors as + * specified in stops. This method should return immediately after + * this rider is initialized, but not until the interrupt handler is + * set. The rider will start receiving events after this method returns, + * potentially before run() is called. + * + * @param controls the rider's interface to the elevator bank. The + * rider must not attempt to access the elevator + * bank in any other way. + * @param stops an array of stops the rider should make; see + * below. + */ + public void initialize(RiderControls controls, int[] stops); + + /** + * Cause the rider to use the provided controls to make the stops specified + * in the constructor. The rider should stop at each of the floors in + * stops, an array of floor numbers. The rider should only + * make the specified stops. + * + *

+ * For example, suppose the rider uses controls to determine that + * it is initially on floor 1, and suppose the stops array contains two + * elements: { 0, 2 }. Then the rider should get on an elevator, get off + * on floor 0, get on an elevator, and get off on floor 2, pushing buttons + * as necessary. + * + *

+ * This method should not return, but instead should call + * controls.finish() when the rider is finished. + */ + public void run(); + + /** Indicates an elevator intends to move down. */ + public static final int dirDown = -1; + /** Indicates an elevator intends not to move. */ + public static final int dirNeither = 0; + /** Indicates an elevator intends to move up. */ + public static final int dirUp = 1; +} diff --git a/machine/SerialConsole.java b/machine/SerialConsole.java new file mode 100644 index 0000000..a4be880 --- /dev/null +++ b/machine/SerialConsole.java @@ -0,0 +1,49 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; + +/** + * A serial console can be used to send and receive characters. Only one + * character may be sent at a time, and only one character may be received at a + * time. + */ + +public interface SerialConsole { + /** + * Set this console's receive and send interrupt handlers. + * + *

+ * The receive interrupt handler is called every time another byte arrives + * and can be read using readByte(). + * + *

+ * The send interrupt handler is called every time a byte sent with + * writeByte() is finished being sent. This means that another + * byte can be sent. + * + * @param receiveInterruptHandler the callback to call when a byte + * arrives. + * @param sendInterruptHandler the callback to call when another byte + * can be sent. + */ + public void setInterruptHandlers(Runnable receiveInterruptHandler, + Runnable sendInterruptHandler); + + /** + * Return the next unsigned byte received (in the range 0 through + * 255). + * + * @return the next byte read, or -1 if no byte is available. + */ + public int readByte(); + + /** + * Send another byte. If a byte is already being sent, the result is not + * defined. + * + * @param value the byte to be sent (the upper 24 bits are ignored). + */ + public void writeByte(int value); +} diff --git a/machine/StandardConsole.java b/machine/StandardConsole.java new file mode 100644 index 0000000..033ae6a --- /dev/null +++ b/machine/StandardConsole.java @@ -0,0 +1,161 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; + +import java.io.IOException; + +/** + * A text-based console that uses System.in and System.out. + */ +public class StandardConsole implements SerialConsole { + /** + * Allocate a new standard console. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + */ + public StandardConsole(Privilege privilege) { + System.out.print(" console"); + + this.privilege = privilege; + + receiveInterrupt = new Runnable() { + public void run() { receiveInterrupt(); } + }; + + sendInterrupt = new Runnable() { + public void run() { sendInterrupt(); } + }; + + scheduleReceiveInterrupt(); + } + + public final void setInterruptHandlers(Runnable receiveInterruptHandler, + Runnable sendInterruptHandler) { + this.receiveInterruptHandler = receiveInterruptHandler; + this.sendInterruptHandler = sendInterruptHandler; + } + + private void scheduleReceiveInterrupt() { + privilege.interrupt.schedule(Stats.ConsoleTime, "console read", + receiveInterrupt); + } + + /** + * Attempt to read a byte from the object backing this console. + * + * @return the byte read, or -1 of no data is available. + */ + protected int in() { + try { + if (System.in.available() <= 0) + return -1; + + return System.in.read(); + } + catch (IOException e) { + return -1; + } + } + + private int translateCharacter(int c) { + // translate win32 0x0D 0x0A sequence to single newline + if (c == 0x0A && prevCarriageReturn) { + prevCarriageReturn = false; + return -1; + } + prevCarriageReturn = (c == 0x0D); + + // invalid if non-ASCII + if (c >= 0x80) + return -1; + // backspace characters + else if (c == 0x04 || c == 0x08 || c == 0x19 || c == 0x1B || c == 0x7F) + return '\b'; + // if normal ASCII range, nothing to do + else if (c >= 0x20) + return c; + // newline characters + else if (c == 0x0A || c == 0x0D) + return '\n'; + // everything else is invalid + else + return -1; + } + + + private void receiveInterrupt() { + Lib.assertTrue(incomingKey == -1); + + incomingKey = translateCharacter(in()); + if (incomingKey == -1) { + scheduleReceiveInterrupt(); + } + else { + privilege.stats.numConsoleReads++; + + if (receiveInterruptHandler != null) + receiveInterruptHandler.run(); + } + } + + public final int readByte() { + int key = incomingKey; + + if (incomingKey != -1) { + incomingKey = -1; + scheduleReceiveInterrupt(); + } + + return key; + } + + private void scheduleSendInterrupt() { + privilege.interrupt.schedule(Stats.ConsoleTime, "console write", + sendInterrupt); + } + + /** + * Write a byte to the object backing this console. + * + * @param value the byte to write. + */ + protected void out(int value) { + System.out.write(value); + System.out.flush(); + } + + private void sendInterrupt() { + Lib.assertTrue(outgoingKey != -1); + + out(outgoingKey); + outgoingKey = -1; + + privilege.stats.numConsoleWrites++; + + if (sendInterruptHandler != null) + sendInterruptHandler.run(); + } + + public final void writeByte(int value) { + if (outgoingKey == -1) + scheduleSendInterrupt(); + + outgoingKey = value&0xFF; + } + + private Privilege privilege = null; + + private Runnable receiveInterrupt; + private Runnable sendInterrupt; + + private Runnable receiveInterruptHandler = null; + private Runnable sendInterruptHandler = null; + + private int incomingKey = -1; + private int outgoingKey = -1; + + private boolean prevCarriageReturn = false; +} diff --git a/machine/Stats.java b/machine/Stats.java new file mode 100644 index 0000000..9af927c --- /dev/null +++ b/machine/Stats.java @@ -0,0 +1,100 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.machine.*; + +/** + * An object that maintains Nachos runtime statistics. + */ +public final class Stats { + /** + * Allocate a new statistics object. + */ + public Stats() { + } + + /** + * Print out the statistics in this object. + */ + public void print() { + System.out.println("Ticks: total " + totalTicks + + ", kernel " + kernelTicks + + ", user " + userTicks); + System.out.println("Disk I/O: reads " + numDiskReads + + ", writes " + numDiskWrites); + System.out.println("Console I/O: reads " + numConsoleReads + + ", writes " + numConsoleWrites); + System.out.println("Paging: page faults " + numPageFaults + + ", TLB misses " + numTLBMisses); + System.out.println("Network I/O: received " + numPacketsReceived + + ", sent " + numPacketsSent); + } + + /** + * The total amount of simulated time that has passed since Nachos + * started. + */ + public long totalTicks = 0; + /** + * The total amount of simulated time that Nachos has spent in kernel mode. + */ + public long kernelTicks = 0; + /** + * The total amount of simulated time that Nachos has spent in user mode. + */ + public long userTicks = 0; + + /** The total number of sectors Nachos has read from the simulated disk.*/ + public int numDiskReads = 0; + /** The total number of sectors Nachos has written to the simulated disk.*/ + public int numDiskWrites = 0; + /** The total number of characters Nachos has read from the console. */ + public int numConsoleReads = 0; + /** The total number of characters Nachos has written to the console. */ + public int numConsoleWrites = 0; + /** The total number of page faults that have occurred. */ + public int numPageFaults = 0; + /** The total number of TLB misses that have occurred. */ + public int numTLBMisses = 0; + /** The total number of packets Nachos has sent to the network. */ + public int numPacketsSent = 0; + /** The total number of packets Nachos has received from the network. */ + public int numPacketsReceived = 0; + + /** + * The amount to advance simulated time after each user instructions is + * executed. + */ + public static final int UserTick = 1; + /** + * The amount to advance simulated time after each interrupt enable. + */ + public static final int KernelTick = 10; + /** + * The amount of simulated time required to rotate the disk 360 degrees. + */ + public static final int RotationTime = 500; + /** + * The amount of simulated time required for the disk to seek. + */ + public static final int SeekTime = 500; + /** + * The amount of simulated time required for the console to handle a + * character. + */ + public static final int ConsoleTime = 100; + /** + * The amount of simulated time required for the network to handle a + * packet. + */ + public static final int NetworkTime = 100; + /** + * The mean amount of simulated time between timer interrupts. + */ + public static final int TimerTicks = 500; + /** + * The amount of simulated time required for an elevator to move a floor. + */ + public static final int ElevatorTicks = 2000; +} diff --git a/machine/StubFileSystem.java b/machine/StubFileSystem.java new file mode 100644 index 0000000..4f0da7d --- /dev/null +++ b/machine/StubFileSystem.java @@ -0,0 +1,216 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; +import nachos.threads.*; + +import java.io.File; +import java.io.RandomAccessFile; +import java.io.IOException; + +/** + * This class implements a file system that redirects all requests to the host + * operating system's file system. + */ +public class StubFileSystem implements FileSystem { + /** + * Allocate a new stub file system. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + * @param directory the root directory of the stub file system. + */ + public StubFileSystem(Privilege privilege, File directory) { + this.privilege = privilege; + this.directory = directory; + } + + public OpenFile open(String name, boolean truncate) { + if (!checkName(name)) + return null; + + delay(); + + try { + return new StubOpenFile(name, truncate); + } + catch (IOException e) { + return null; + } + } + + public boolean remove(String name) { + if (!checkName(name)) + return false; + + delay(); + + FileRemover fr = new FileRemover(new File(directory, name)); + privilege.doPrivileged(fr); + return fr.successful; + } + + private class FileRemover implements Runnable { + public FileRemover(File f) { + this.f = f; + } + + public void run() { + successful = f.delete(); + } + + public boolean successful = false; + private File f; + } + + private void delay() { + long time = Machine.timer().getTime(); + int amount = 1000; + ThreadedKernel.alarm.waitUntil(amount); + Lib.assertTrue(Machine.timer().getTime() >= time+amount); + } + + private class StubOpenFile extends OpenFileWithPosition { + StubOpenFile(final String name, final boolean truncate) + throws IOException { + super(StubFileSystem.this, name); + + final File f = new File(directory, name); + + if (openCount == maxOpenFiles) + throw new IOException(); + + privilege.doPrivileged(new Runnable() { + public void run() { getRandomAccessFile(f, truncate); } + }); + + if (file == null) + throw new IOException(); + + open = true; + openCount++; + } + + private void getRandomAccessFile(File f, boolean truncate) { + try { + if (!truncate && !f.exists()) + return; + + file = new RandomAccessFile(f, "rw"); + + if (truncate) + file.setLength(0); + } + catch (IOException e) { + } + } + + public int read(int pos, byte[] buf, int offset, int length) { + if (!open) + return -1; + + try { + delay(); + + file.seek(pos); + return Math.max(0, file.read(buf, offset, length)); + } + catch (IOException e) { + return -1; + } + } + + public int write(int pos, byte[] buf, int offset, int length) { + if (!open) + return -1; + + try { + delay(); + + file.seek(pos); + file.write(buf, offset, length); + return length; + } + catch (IOException e) { + return -1; + } + } + + public int length() { + try { + return (int) file.length(); + } + catch (IOException e) { + return -1; + } + } + + public void close() { + if (open) { + open = false; + openCount--; + } + + try { + file.close(); + } + catch (IOException e) { + } + } + + private RandomAccessFile file = null; + private boolean open = false; + } + + private int openCount = 0; + private static final int maxOpenFiles = 16; + + private Privilege privilege; + private File directory; + + private static boolean checkName(String name) { + char[] chars = name.toCharArray(); + + for (int i=0; i= allowedFileNameCharacters.length) + return false; + if (!allowedFileNameCharacters[(int) chars[i]]) + return false; + } + return true; + } + + private static boolean[] allowedFileNameCharacters = new boolean[0x80]; + + private static void reject(char c) { + allowedFileNameCharacters[c] = false; + } + + private static void allow(char c) { + allowedFileNameCharacters[c] = true; + } + + private static void reject(char first, char last) { + for (char c=first; c<=last; c++) + allowedFileNameCharacters[c] = false; + } + + private static void allow(char first, char last) { + for (char c=first; c<=last; c++) + allowedFileNameCharacters[c] = true; + } + + static { + reject((char) 0x00, (char) 0x7F); + + allow('A', 'Z'); + allow('a', 'z'); + allow('0', '9'); + + allow('-'); + allow('_'); + allow('.'); + allow(','); + } +} diff --git a/machine/TCB.java b/machine/TCB.java new file mode 100644 index 0000000..6081ddb --- /dev/null +++ b/machine/TCB.java @@ -0,0 +1,414 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; +import nachos.threads.KThread; + +import java.util.Vector; +import java.security.PrivilegedAction; + +/** + * A TCB simulates the low-level details necessary to create, context-switch, + * and destroy Nachos threads. Each TCB controls an underlying JVM Thread + * object. + * + *

+ * Do not use any methods in java.lang.Thread, as they are not + * compatible with the TCB API. Most Thread methods will either crash + * Nachos or have no useful effect. + * + *

+ * Do not use the synchronized keyword anywhere in your code. + * It's against the rules, and it can easily deadlock nachos. + */ +public final class TCB { + /** + * Allocate a new TCB. + */ + public TCB() { + } + + /** + * Give the TCB class the necessary privilege to create threads. This is + * necessary, because unlike other machine classes that need privilege, we + * want the kernel to be able to create TCB objects on its own. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + */ + public static void givePrivilege(Privilege privilege) { + TCB.privilege = privilege; + privilege.tcb = new TCBPrivilege(); + } + + /** + * Causes the thread represented by this TCB to begin execution. The + * specified target is run in the thread. + */ + public void start(Runnable target) { + /* We will not use synchronization here, because we're assuming that + * either this is the first call to start(), or we're being called in + * the context of another TCB. Since we only allow one TCB to run at a + * time, no synchronization is necessary. + * + * The only way this assumption could be broken is if one of our + * non-Nachos threads used the TCB code. + */ + + /* Make sure this TCB has not already been started. If done is false, + * then destroy() has not yet set javaThread back to null, so we can + * use javaThread as a reliable indicator of whether or not start() has + * already been invoked. + */ + Lib.assertTrue(javaThread == null && !done); + + /* Make sure there aren't too many running TCBs already. This + * limitation exists in an effort to prevent wild thread usage. + */ + Lib.assertTrue(runningThreads.size() < maxThreads); + + isFirstTCB = (currentTCB == null); + + /* Probably unnecessary sanity check: if this is not the first TCB, we + * make sure that the current thread is bound to the current TCB. This + * check can only fail if non-Nachos threads invoke start(). + */ + if (!isFirstTCB) + Lib.assertTrue(currentTCB.javaThread == Thread.currentThread()); + + /* At this point all checks are complete, so we go ahead and start the + * TCB. Whether or not this is the first TCB, it gets added to + * runningThreads, and we save the target closure. + */ + runningThreads.add(this); + + this.target = target; + + if (!isFirstTCB) { + /* If this is not the first TCB, we have to make a new Java thread + * to run it. Creating Java threads is a privileged operation. + */ + tcbTarget = new Runnable() { + public void run() { threadroot(); } + }; + + privilege.doPrivileged(new Runnable() { + public void run() { javaThread = new Thread(tcbTarget); } + }); + + /* The Java thread hasn't yet started, but we need to get it + * blocking in yield(). We do this by temporarily turning off the + * current TCB, starting the new Java thread, and waiting for it + * to wake us up from threadroot(). Once the new TCB wakes us up, + * it's safe to context switch to the new TCB. + */ + currentTCB.running = false; + + this.javaThread.start(); + currentTCB.waitForInterrupt(); + } + else { + /* This is the first TCB, so we don't need to make a new Java + * thread to run it; we just steal the current Java thread. + */ + javaThread = Thread.currentThread(); + + /* All we have to do now is invoke threadroot() directly. */ + threadroot(); + } + } + + /** + * Return the TCB of the currently running thread. + */ + public static TCB currentTCB() { + return currentTCB; + } + + /** + * Context switch between the current TCB and this TCB. This TCB will + * become the new current TCB. It is acceptable for this TCB to be the + * current TCB. + */ + public void contextSwitch() { + /* Probably unnecessary sanity check: we make sure that the current + * thread is bound to the current TCB. This check can only fail if + * non-Nachos threads invoke start(). + */ + Lib.assertTrue(currentTCB.javaThread == Thread.currentThread()); + + // make sure AutoGrader.runningThread() called associateThread() + Lib.assertTrue(currentTCB.associated); + currentTCB.associated = false; + + // can't switch from a TCB to itself + if (this == currentTCB) + return; + + /* There are some synchronization concerns here. As soon as we wake up + * the next thread, we cannot assume anything about static variables, + * or about any TCB's state. Therefore, before waking up the next + * thread, we must latch the value of currentTCB, and set its running + * flag to false (so that, in case we get interrupted before we call + * yield(), the interrupt will set the running flag and yield() won't + * block). + */ + + TCB previous = currentTCB; + previous.running = false; + + this.interrupt(); + previous.yield(); + } + + /** + * Destroy this TCB. This TCB must not be in use by the current thread. + * This TCB must also have been authorized to be destroyed by the + * autograder. + */ + public void destroy() { + // make sure the current TCB is correct + Lib.assertTrue(currentTCB != null && + currentTCB.javaThread == Thread.currentThread()); + // can't destroy current thread + Lib.assertTrue(this != currentTCB); + // thread must have started but not be destroyed yet + Lib.assertTrue(javaThread != null && !done); + + // ensure AutoGrader.finishingCurrentThread() called authorizeDestroy() + Lib.assertTrue(nachosThread == toBeDestroyed); + toBeDestroyed = null; + + this.done = true; + currentTCB.running = false; + + this.interrupt(); + currentTCB.waitForInterrupt(); + + this.javaThread = null; + } + + /** + * Destroy all TCBs and exit Nachos. Same as Machine.terminate(). + */ + public static void die() { + privilege.exit(0); + } + + /** + * Test if the current JVM thread belongs to a Nachos TCB. The AWT event + * dispatcher is an example of a non-Nachos thread. + * + * @return true if the current JVM thread is a Nachos thread. + */ + public static boolean isNachosThread() { + return (currentTCB != null && + Thread.currentThread() == currentTCB.javaThread); + } + + private void threadroot() { + // this should be running the current thread + Lib.assertTrue(javaThread == Thread.currentThread()); + + if (!isFirstTCB) { + /* start() is waiting for us to wake it up, signalling that it's OK + * to context switch to us. We leave the running flag false so that + * we'll still run if a context switch happens before we go to + * sleep. All we have to do is wake up the current TCB and then + * wait to get woken up by contextSwitch() or destroy(). + */ + + currentTCB.interrupt(); + this.yield(); + } + else { + /* start() called us directly, so we just need to initialize + * a couple things. + */ + + currentTCB = this; + running = true; + } + + try { + target.run(); + + // no way out of here without going throw one of the catch blocks + Lib.assertNotReached(); + } + catch (ThreadDeath e) { + // make sure this TCB is being destroyed properly + if (!done) { + System.out.print("\nTCB terminated improperly!\n"); + privilege.exit(1); + } + + runningThreads.removeElement(this); + if (runningThreads.isEmpty()) + privilege.exit(0); + } + catch (Throwable e) { + System.out.print("\n"); + e.printStackTrace(); + + runningThreads.removeElement(this); + if (runningThreads.isEmpty()) + privilege.exit(1); + else + die(); + } + } + + /** + * Invoked by threadroot() and by contextSwitch() when it is necessary to + * wait for another TCB to context switch to this TCB. Since this TCB + * might get destroyed instead, we check the done flag after + * waking up. If it is set, the TCB that woke us up is waiting for an + * acknowledgement in destroy(). Otherwise, we just set the current TCB to + * this TCB and return. + */ + private void yield() { + waitForInterrupt(); + + if (done) { + currentTCB.interrupt(); + throw new ThreadDeath(); + } + + currentTCB = this; + } + + /** + * Waits on the monitor bound to this TCB until its running flag + * is set to true. waitForInterrupt() is used whenever a + * TCB needs to go to wait for its turn to run. This includes the ping-pong + * process of starting and destroying TCBs, as well as in context switching + * from this TCB to another. We don't rely on currentTCB, since it + * is updated by contextSwitch() before we get called. + */ + private synchronized void waitForInterrupt() { + while (!running) { + try { wait(); } + catch (InterruptedException e) { } + } + } + + /** + * Wake up this TCB by setting its running flag to true + * and signalling the monitor bound to it. Used in the ping-pong process of + * starting and destroying TCBs, as well as in context switching to this + * TCB. + */ + private synchronized void interrupt() { + running = true; + notify(); + } + + private void associateThread(KThread thread) { + // make sure AutoGrader.runningThread() gets called only once per + // context switch + Lib.assertTrue(!associated); + associated = true; + + Lib.assertTrue(thread != null); + + if (nachosThread != null) + Lib.assertTrue(thread == nachosThread); + else + nachosThread = thread; + } + + private static void authorizeDestroy(KThread thread) { + // make sure AutoGrader.finishingThread() gets called only once per + // destroy + Lib.assertTrue(toBeDestroyed == null); + toBeDestroyed = thread; + } + + /** + * The maximum number of started, non-destroyed TCB's that can be in + * existence. + */ + public static final int maxThreads = 250; + + /** + * A reference to the currently running TCB. It is initialized to + * null when the TCB class is loaded, and then the first + * invocation of start(Runnable) assigns currentTCB a + * reference to the first TCB. After that, only yield() can + * change currentTCB to the current TCB, and only after + * waitForInterrupt() returns. + * + *

+ * Note that currentTCB.javaThread will not be the current thread + * if the current thread is not bound to a TCB (this includes the threads + * created for the hardware simulation). + */ + private static TCB currentTCB = null; + + /** + * A vector containing all running TCB objects. It is initialized to + * an empty vector when the TCB class is loaded. TCB objects are + * added only in start(Runnable), which can only be invoked once + * on each TCB object. TCB objects are removed only in each of the + * catch clauses of threadroot(), one of which is always + * invoked on thread termination. The maximum number of threads in + * runningThreads is limited to maxThreads by + * start(Runnable). If threadroot() drops the number of + * TCB objects in runningThreads to zero, Nachos exits, so once + * the first TCB is created, this vector is basically never empty. + */ + private static Vector runningThreads = new Vector(); + + private static Privilege privilege; + private static KThread toBeDestroyed = null; + + /** + * true if and only if this TCB is the first TCB to start, the one + * started in Machine.main(String[]). Initialized by + * start(Runnable), on the basis of whether currentTCB + * has been initialized. + */ + private boolean isFirstTCB; + + /** + * A reference to the Java thread bound to this TCB. It is initially + * null, assigned to a Java thread in start(Runnable), + * and set to null again in destroy(). + */ + private Thread javaThread = null; + + /** + * true if and only if the Java thread bound to this TCB ought to + * be running. This is an entirely different condition from membership in + * runningThreads, which contains all TCB objects that have + * started and have not terminated. running is only true + * when the associated Java thread ought to run ASAP. When starting or + * destroying a TCB, this is temporarily true for a thread other than that + * of the current TCB. + */ + private boolean running = false; + + /** + * Set to true by destroy(), so that when + * waitForInterrupt() returns in the doomed TCB, yield() + * will know that the current TCB is doomed. + */ + private boolean done = false; + + private KThread nachosThread = null; + private boolean associated = false; + private Runnable target; + private Runnable tcbTarget; + + private static class TCBPrivilege implements Privilege.TCBPrivilege { + public void associateThread(KThread thread) { + Lib.assertTrue(currentTCB != null); + currentTCB.associateThread(thread); + } + public void authorizeDestroy(KThread thread) { + TCB.authorizeDestroy(thread); + } + } +} diff --git a/machine/Timer.java b/machine/Timer.java new file mode 100644 index 0000000..a9c7dcc --- /dev/null +++ b/machine/Timer.java @@ -0,0 +1,89 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.security.*; + +/** + * A hardware timer generates a CPU timer interrupt approximately every 500 + * clock ticks. This means that it can be used for implementing time-slicing, + * or for having a thread go to sleep for a specific period of time. + * + * The Timer class emulates a hardware timer by scheduling a timer + * interrupt to occur every time approximately 500 clock ticks pass. There is + * a small degree of randomness here, so interrupts do not occur exactly every + * 500 ticks. + */ +public final class Timer { + /** + * Allocate a new timer. + * + * @param privilege encapsulates privileged access to the Nachos + * machine. + */ + public Timer(Privilege privilege) { + System.out.print(" timer"); + + this.privilege = privilege; + + timerInterrupt = new Runnable() { + public void run() { timerInterrupt(); } + }; + + autoGraderInterrupt = new Runnable() { + public void run() { + Machine.autoGrader().timerInterrupt(Timer.this.privilege, + lastTimerInterrupt); + } + }; + + scheduleInterrupt(); + } + + /** + * Set the callback to use as a timer interrupt handler. The timer + * interrupt handler will be called approximately every 500 clock ticks. + * + * @param handler the timer interrupt handler. + */ + public void setInterruptHandler(Runnable handler) { + this.handler = handler; + } + + /** + * Get the current time. + * + * @return the number of clock ticks since Nachos started. + */ + public long getTime() { + return privilege.stats.totalTicks; + } + + private void timerInterrupt() { + scheduleInterrupt(); + scheduleAutoGraderInterrupt(); + + lastTimerInterrupt = getTime(); + + if (handler != null) + handler.run(); + } + + private void scheduleInterrupt() { + int delay = Stats.TimerTicks; + delay += Lib.random(delay/10) - (delay/20); + + privilege.interrupt.schedule(delay, "timer", timerInterrupt); + } + + private void scheduleAutoGraderInterrupt() { + privilege.interrupt.schedule(1, "timerAG", autoGraderInterrupt); + } + + private long lastTimerInterrupt; + private Runnable timerInterrupt; + private Runnable autoGraderInterrupt; + + private Privilege privilege; + private Runnable handler = null; +} diff --git a/machine/TranslationEntry.java b/machine/TranslationEntry.java new file mode 100644 index 0000000..fe68d2d --- /dev/null +++ b/machine/TranslationEntry.java @@ -0,0 +1,81 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.machine; + +import nachos.machine.*; + +/** + * A single translation between a virtual page and a physical page. + */ +public final class TranslationEntry { + /** + * Allocate a new invalid translation entry. + */ + public TranslationEntry() { + valid = false; + } + + /** + * Allocate a new translation entry with the specified initial state. + * + * @param vpn the virtual page numben. + * @param ppn the physical page number. + * @param valid the valid bit. + * @param readOnly the read-only bit. + * @param used the used bit. + * @param dirty the dirty bit. + */ + public TranslationEntry(int vpn, int ppn, boolean valid, boolean readOnly, + boolean used, boolean dirty) { + this.vpn = vpn; + this.ppn = ppn; + this.valid = valid; + this.readOnly = readOnly; + this.used = used; + this.dirty = dirty; + } + + /** + * Allocate a new translation entry, copying the contents of an existing + * one. + * + * @param entry the translation entry to copy. + */ + public TranslationEntry(TranslationEntry entry) { + vpn = entry.vpn; + ppn = entry.ppn; + valid = entry.valid; + readOnly = entry.readOnly; + used = entry.used; + dirty = entry.dirty; + } + + /** The virtual page number. */ + public int vpn; + + /** The physical page number. */ + public int ppn; + + /** + * If this flag is false, this translation entry is ignored. + */ + public boolean valid; + + /** + * If this flag is true, the user pprogram is not allowed to + * modify the contents of this virtual page. + */ + public boolean readOnly; + + /** + * This flag is set to true every time the page is read or written + * by a user program. + */ + public boolean used; + + /** + * This flag is set to true every time the page is written by a + * user program. + */ + public boolean dirty; +} diff --git a/machine/package.html b/machine/package.html new file mode 100644 index 0000000..16520b2 --- /dev/null +++ b/machine/package.html @@ -0,0 +1,3 @@ + +Provides classes that implement the Nachos simulated machine. + diff --git a/network/MailMessage.java b/network/MailMessage.java new file mode 100644 index 0000000..11db548 --- /dev/null +++ b/network/MailMessage.java @@ -0,0 +1,105 @@ +package nachos.network; + +import nachos.machine.*; + +/** + * A mail message. Includes a packet header, a mail header, and the actual + * payload. + * + * @see nachos.machine.Packet + */ +public class MailMessage { + /** + * Allocate a new mail message to be sent, using the specified parameters. + * + * @param dstLink the destination link address. + * @param dstPort the destination port. + * @param srcLink the source link address. + * @param srcPort the source port. + * @param contents the contents of the packet. + */ + public MailMessage(int dstLink, int dstPort, int srcLink, int srcPort, + byte[] contents) throws MalformedPacketException { + // make sure the paramters are valid + if (dstPort < 0 || dstPort >= portLimit || + srcPort < 0 || srcPort >= portLimit || + contents.length > maxContentsLength) + throw new MalformedPacketException(); + + this.dstPort = (byte) dstPort; + this.srcPort = (byte) srcPort; + this.contents = contents; + + byte[] packetContents = new byte[headerLength + contents.length]; + + packetContents[0] = (byte) dstPort; + packetContents[1] = (byte) srcPort; + + System.arraycopy(contents, 0, packetContents, headerLength, + contents.length); + + packet = new Packet(dstLink, srcLink, packetContents); + } + + /** + * Allocate a new mail message using the specified packet from the network. + * + * @param packet the packet containg the mail message. + */ + public MailMessage(Packet packet) throws MalformedPacketException { + this.packet = packet; + + // make sure we have a valid header + if (packet.contents.length < headerLength || + packet.contents[0] < 0 || packet.contents[0] >= portLimit || + packet.contents[1] < 0 || packet.contents[1] >= portLimit) + throw new MalformedPacketException(); + + dstPort = packet.contents[0]; + srcPort = packet.contents[1]; + + contents = new byte[packet.contents.length - headerLength]; + System.arraycopy(packet.contents, headerLength, contents, 0, + contents.length); + } + + /** + * Return a string representation of the message headers. + */ + public String toString() { + return "from (" + packet.srcLink + ":" + srcPort + + ") to (" + packet.dstLink + ":" + dstPort + + "), " + contents.length + " bytes"; + } + + /** This message, as a packet that can be sent through a network link. */ + public Packet packet; + /** The port used by this message on the destination machine. */ + public int dstPort; + /** The port used by this message on the source machine. */ + public int srcPort; + /** The contents of this message, excluding the mail message header. */ + public byte[] contents; + + /** + * The number of bytes in a mail header. The header is formatted as + * follows: + * + * + * + * + * + *
offsetsizevalue
01destination port
11source port
+ */ + public static final int headerLength = 2; + + /** Maximum payload (real data) that can be included in a single mesage. */ + public static final int maxContentsLength = + Packet.maxContentsLength - headerLength; + + /** + * The upper limit on mail ports. All ports fall between 0 and + * portLimit - 1. + */ + public static final int portLimit = 128; +} diff --git a/network/NetKernel.java b/network/NetKernel.java new file mode 100644 index 0000000..551cc78 --- /dev/null +++ b/network/NetKernel.java @@ -0,0 +1,123 @@ +package nachos.network; + +import nachos.machine.*; +import nachos.threads.*; +import nachos.userprog.*; +import nachos.vm.*; +import nachos.network.*; + +/** + * A kernel with network support. + */ +public class NetKernel extends VMKernel { + /** + * Allocate a new networking kernel. + */ + public NetKernel() { + super(); + } + + /** + * Initialize this kernel. + */ + public void initialize(String[] args) { + super.initialize(args); + + postOffice = new PostOffice(); + } + + /** + * Test the network. Create a server thread that listens for pings on port + * 1 and sends replies. Then ping one or two hosts. Note that this test + * assumes that the network is reliable (i.e. that the network's + * reliability is 1.0). + */ + public void selfTest() { + super.selfTest(); + + KThread serverThread = new KThread(new Runnable() { + public void run() { pingServer(); } + }); + + serverThread.fork(); + + System.out.println("Press any key to start the network test..."); + console.readByte(true); + + int local = Machine.networkLink().getLinkAddress(); + + // ping this machine first + ping(local); + + // if we're 0 or 1, ping the opposite + if (local <= 1) + ping(1-local); + } + + private void ping(int dstLink) { + int srcLink = Machine.networkLink().getLinkAddress(); + + System.out.println("PING " + dstLink + " from " + srcLink); + + long startTime = Machine.timer().getTime(); + + MailMessage ping; + + try { + ping = new MailMessage(dstLink, 1, + Machine.networkLink().getLinkAddress(), 0, + new byte[0]); + } + catch (MalformedPacketException e) { + Lib.assertNotReached(); + return; + } + + postOffice.send(ping); + + MailMessage ack = postOffice.receive(0); + + long endTime = Machine.timer().getTime(); + + System.out.println("time=" + (endTime-startTime) + " ticks"); + } + + private void pingServer() { + while (true) { + MailMessage ping = postOffice.receive(1); + + MailMessage ack; + + try { + ack = new MailMessage(ping.packet.srcLink, ping.srcPort, + ping.packet.dstLink, ping.dstPort, + ping.contents); + } + catch (MalformedPacketException e) { + // should never happen... + continue; + } + + postOffice.send(ack); + } + } + + /** + * Start running user programs. + */ + public void run() { + super.run(); + } + + /** + * Terminate this kernel. Never returns. + */ + public void terminate() { + super.terminate(); + } + + private PostOffice postOffice; + + // dummy variables to make javac smarter + private static NetProcess dummy1 = null; +} diff --git a/network/NetProcess.java b/network/NetProcess.java new file mode 100644 index 0000000..03c7bde --- /dev/null +++ b/network/NetProcess.java @@ -0,0 +1,46 @@ +package nachos.network; + +import nachos.machine.*; +import nachos.threads.*; +import nachos.userprog.*; +import nachos.vm.*; + +/** + * A VMProcess that supports networking syscalls. + */ +public class NetProcess extends VMProcess { + /** + * Allocate a new process. + */ + public NetProcess() { + super(); + } + + private static final int + syscallConnect = 11, + syscallAccept = 12; + + /** + * Handle a syscall exception. Called by handleException(). The + * syscall argument identifies which syscall the user executed: + * + * + * + * + * + *
syscall#syscall prototype
11int connect(int host, int port);
12int accept(int port);
+ * + * @param syscall the syscall number. + * @param a0 the first syscall argument. + * @param a1 the second syscall argument. + * @param a2 the third syscall argument. + * @param a3 the fourth syscall argument. + * @return the value to be returned to the user. + */ + public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) { + switch (syscall) { + default: + return super.handleSyscall(syscall, a0, a1, a2, a3); + } + } +} diff --git a/network/PostOffice.java b/network/PostOffice.java new file mode 100644 index 0000000..f704590 --- /dev/null +++ b/network/PostOffice.java @@ -0,0 +1,134 @@ +package nachos.network; + +import nachos.machine.*; +import nachos.threads.*; + +/** + * A collection of message queues, one for each local port. A + * PostOffice interacts directly with the network hardware. Because + * of the network hardware, we are guaranteed that messages will never be + * corrupted, but they might get lost. + * + *

+ * The post office uses a "postal worker" thread to wait for messages to arrive + * from the network and to place them in the appropriate queues. This cannot + * be done in the receive interrupt handler because each queue (implemented + * with a SynchList) is protected by a lock. + */ +public class PostOffice { + /** + * Allocate a new post office, using an array of SynchLists. + * Register the interrupt handlers with the network hardware and start the + * "postal worker" thread. + */ + public PostOffice() { + messageReceived = new Semaphore(0); + messageSent = new Semaphore(0); + sendLock = new Lock(); + + queues = new SynchList[MailMessage.portLimit]; + for (int i=0; i= 0 && port < queues.length); + + Lib.debug(dbgNet, "waiting for mail on port " + port); + + MailMessage mail = (MailMessage) queues[port].removeFirst(); + + if (Lib.test(dbgNet)) + System.out.println("got mail on port " + port + ": " + mail); + + return mail; + } + + /** + * Wait for incoming messages, and then put them in the correct mailbox. + */ + private void postalDelivery() { + while (true) { + messageReceived.P(); + + Packet p = Machine.networkLink().receive(); + + MailMessage mail; + + try { + mail = new MailMessage(p); + } + catch (MalformedPacketException e) { + continue; + } + + if (Lib.test(dbgNet)) + System.out.println("delivering mail to port " + mail.dstPort + + ": " + mail); + + // atomically add message to the mailbox and wake a waiting thread + queues[mail.dstPort].add(mail); + } + } + + /** + * Called when a packet has arrived and can be dequeued from the network + * link. + */ + private void receiveInterrupt() { + messageReceived.V(); + } + + /** + * Send a message to a mailbox on a remote machine. + */ + public void send(MailMessage mail) { + if (Lib.test(dbgNet)) + System.out.println("sending mail: " + mail); + + sendLock.acquire(); + + Machine.networkLink().send(mail.packet); + messageSent.P(); + + sendLock.release(); + } + + /** + * Called when a packet has been sent and another can be queued to the + * network link. Note that this is called even if the previous packet was + * dropped. + */ + private void sendInterrupt() { + messageSent.V(); + } + + private SynchList[] queues; + private Semaphore messageReceived; // V'd when a message can be dequeued + private Semaphore messageSent; // V'd when a message can be queued + private Lock sendLock; + + private static final char dbgNet = 'n'; +} diff --git a/network/package.html b/network/package.html new file mode 100644 index 0000000..aa34591 --- /dev/null +++ b/network/package.html @@ -0,0 +1,3 @@ + +Provides classes that allow Nachos processes to communicate over the network. + diff --git a/proj1/Makefile b/proj1/Makefile new file mode 100644 index 0000000..cac9d42 --- /dev/null +++ b/proj1/Makefile @@ -0,0 +1,3 @@ +DIRS = threads machine security ag + +include ../Makefile diff --git a/proj1/nachos.conf b/proj1/nachos.conf new file mode 100644 index 0000000..edeb179 --- /dev/null +++ b/proj1/nachos.conf @@ -0,0 +1,10 @@ +Machine.stubFileSystem = false +Machine.processor = false +Machine.console = false +Machine.disk = false +Machine.bank = false +Machine.networkLink = false +ElevatorBank.allowElevatorGUI = true +NachosSecurityManager.fullySecure = false +ThreadedKernel.scheduler = nachos.threads.RoundRobinScheduler #nachos.threads.PriorityScheduler +Kernel.kernel = nachos.threads.ThreadedKernel diff --git a/proj1/nachos/ag/AutoGrader.class b/proj1/nachos/ag/AutoGrader.class new file mode 100644 index 0000000..9ed418e Binary files /dev/null and b/proj1/nachos/ag/AutoGrader.class differ diff --git a/proj1/nachos/ag/BoatGrader.class b/proj1/nachos/ag/BoatGrader.class new file mode 100644 index 0000000..b7035fc Binary files /dev/null and b/proj1/nachos/ag/BoatGrader.class differ diff --git a/proj1/nachos/machine/ArrayFile.class b/proj1/nachos/machine/ArrayFile.class new file mode 100644 index 0000000..1751216 Binary files /dev/null and b/proj1/nachos/machine/ArrayFile.class differ diff --git a/proj1/nachos/machine/AssertionFailureError.class b/proj1/nachos/machine/AssertionFailureError.class new file mode 100644 index 0000000..62895c5 Binary files /dev/null and b/proj1/nachos/machine/AssertionFailureError.class differ diff --git a/proj1/nachos/machine/Coff.class b/proj1/nachos/machine/Coff.class new file mode 100644 index 0000000..70d2d36 Binary files /dev/null and b/proj1/nachos/machine/Coff.class differ diff --git a/proj1/nachos/machine/CoffSection.class b/proj1/nachos/machine/CoffSection.class new file mode 100644 index 0000000..3f86b22 Binary files /dev/null and b/proj1/nachos/machine/CoffSection.class differ diff --git a/proj1/nachos/machine/Config.class b/proj1/nachos/machine/Config.class new file mode 100644 index 0000000..db8ddcb Binary files /dev/null and b/proj1/nachos/machine/Config.class differ diff --git a/proj1/nachos/machine/ElevatorBank$1.class b/proj1/nachos/machine/ElevatorBank$1.class new file mode 100644 index 0000000..2d8e95e Binary files /dev/null and b/proj1/nachos/machine/ElevatorBank$1.class differ diff --git a/proj1/nachos/machine/ElevatorBank$ElevatorManager$1.class b/proj1/nachos/machine/ElevatorBank$ElevatorManager$1.class new file mode 100644 index 0000000..b212c4e Binary files /dev/null and b/proj1/nachos/machine/ElevatorBank$ElevatorManager$1.class differ diff --git a/proj1/nachos/machine/ElevatorBank$ElevatorManager.class b/proj1/nachos/machine/ElevatorBank$ElevatorManager.class new file mode 100644 index 0000000..8285db8 Binary files /dev/null and b/proj1/nachos/machine/ElevatorBank$ElevatorManager.class differ diff --git a/proj1/nachos/machine/ElevatorBank$ElevatorState.class b/proj1/nachos/machine/ElevatorBank$ElevatorState.class new file mode 100644 index 0000000..d792099 Binary files /dev/null and b/proj1/nachos/machine/ElevatorBank$ElevatorState.class differ diff --git a/proj1/nachos/machine/ElevatorBank$RiderState$1.class b/proj1/nachos/machine/ElevatorBank$RiderState$1.class new file mode 100644 index 0000000..76fe7a0 Binary files /dev/null and b/proj1/nachos/machine/ElevatorBank$RiderState$1.class differ diff --git a/proj1/nachos/machine/ElevatorBank$RiderState.class b/proj1/nachos/machine/ElevatorBank$RiderState.class new file mode 100644 index 0000000..25eff15 Binary files /dev/null and b/proj1/nachos/machine/ElevatorBank$RiderState.class differ diff --git a/proj1/nachos/machine/ElevatorBank.class b/proj1/nachos/machine/ElevatorBank.class new file mode 100644 index 0000000..0f6402f Binary files /dev/null and b/proj1/nachos/machine/ElevatorBank.class differ diff --git a/proj1/nachos/machine/ElevatorControllerInterface.class b/proj1/nachos/machine/ElevatorControllerInterface.class new file mode 100644 index 0000000..769ca1e Binary files /dev/null and b/proj1/nachos/machine/ElevatorControllerInterface.class differ diff --git a/proj1/nachos/machine/ElevatorControls.class b/proj1/nachos/machine/ElevatorControls.class new file mode 100644 index 0000000..6fcf001 Binary files /dev/null and b/proj1/nachos/machine/ElevatorControls.class differ diff --git a/proj1/nachos/machine/ElevatorEvent.class b/proj1/nachos/machine/ElevatorEvent.class new file mode 100644 index 0000000..0489597 Binary files /dev/null and b/proj1/nachos/machine/ElevatorEvent.class differ diff --git a/proj1/nachos/machine/ElevatorGui$ElevatorShaft.class b/proj1/nachos/machine/ElevatorGui$ElevatorShaft.class new file mode 100644 index 0000000..b03460e Binary files /dev/null and b/proj1/nachos/machine/ElevatorGui$ElevatorShaft.class differ diff --git a/proj1/nachos/machine/ElevatorGui$Floor.class b/proj1/nachos/machine/ElevatorGui$Floor.class new file mode 100644 index 0000000..6d89dd6 Binary files /dev/null and b/proj1/nachos/machine/ElevatorGui$Floor.class differ diff --git a/proj1/nachos/machine/ElevatorGui.class b/proj1/nachos/machine/ElevatorGui.class new file mode 100644 index 0000000..716ba71 Binary files /dev/null and b/proj1/nachos/machine/ElevatorGui.class differ diff --git a/proj1/nachos/machine/ElevatorTest$1.class b/proj1/nachos/machine/ElevatorTest$1.class new file mode 100644 index 0000000..4653f17 Binary files /dev/null and b/proj1/nachos/machine/ElevatorTest$1.class differ diff --git a/proj1/nachos/machine/ElevatorTest$ElevatorController$1.class b/proj1/nachos/machine/ElevatorTest$ElevatorController$1.class new file mode 100644 index 0000000..67e88f8 Binary files /dev/null and b/proj1/nachos/machine/ElevatorTest$ElevatorController$1.class differ diff --git a/proj1/nachos/machine/ElevatorTest$ElevatorController.class b/proj1/nachos/machine/ElevatorTest$ElevatorController.class new file mode 100644 index 0000000..6590ba7 Binary files /dev/null and b/proj1/nachos/machine/ElevatorTest$ElevatorController.class differ diff --git a/proj1/nachos/machine/ElevatorTest$Rider$1.class b/proj1/nachos/machine/ElevatorTest$Rider$1.class new file mode 100644 index 0000000..97a7f7b Binary files /dev/null and b/proj1/nachos/machine/ElevatorTest$Rider$1.class differ diff --git a/proj1/nachos/machine/ElevatorTest$Rider.class b/proj1/nachos/machine/ElevatorTest$Rider.class new file mode 100644 index 0000000..764f4f4 Binary files /dev/null and b/proj1/nachos/machine/ElevatorTest$Rider.class differ diff --git a/proj1/nachos/machine/ElevatorTest.class b/proj1/nachos/machine/ElevatorTest.class new file mode 100644 index 0000000..afd1189 Binary files /dev/null and b/proj1/nachos/machine/ElevatorTest.class differ diff --git a/proj1/nachos/machine/FileSystem.class b/proj1/nachos/machine/FileSystem.class new file mode 100644 index 0000000..49b51a9 Binary files /dev/null and b/proj1/nachos/machine/FileSystem.class differ diff --git a/proj1/nachos/machine/Interrupt$1.class b/proj1/nachos/machine/Interrupt$1.class new file mode 100644 index 0000000..3f87467 Binary files /dev/null and b/proj1/nachos/machine/Interrupt$1.class differ diff --git a/proj1/nachos/machine/Interrupt$InterruptPrivilege.class b/proj1/nachos/machine/Interrupt$InterruptPrivilege.class new file mode 100644 index 0000000..b903a45 Binary files /dev/null and b/proj1/nachos/machine/Interrupt$InterruptPrivilege.class differ diff --git a/proj1/nachos/machine/Interrupt$PendingInterrupt.class b/proj1/nachos/machine/Interrupt$PendingInterrupt.class new file mode 100644 index 0000000..b82bea4 Binary files /dev/null and b/proj1/nachos/machine/Interrupt$PendingInterrupt.class differ diff --git a/proj1/nachos/machine/Interrupt.class b/proj1/nachos/machine/Interrupt.class new file mode 100644 index 0000000..c665cbc Binary files /dev/null and b/proj1/nachos/machine/Interrupt.class differ diff --git a/proj1/nachos/machine/Kernel.class b/proj1/nachos/machine/Kernel.class new file mode 100644 index 0000000..dbde6fd Binary files /dev/null and b/proj1/nachos/machine/Kernel.class differ diff --git a/proj1/nachos/machine/Lib.class b/proj1/nachos/machine/Lib.class new file mode 100644 index 0000000..5a6cd63 Binary files /dev/null and b/proj1/nachos/machine/Lib.class differ diff --git a/proj1/nachos/machine/Machine$1.class b/proj1/nachos/machine/Machine$1.class new file mode 100644 index 0000000..2935fe4 Binary files /dev/null and b/proj1/nachos/machine/Machine$1.class differ diff --git a/proj1/nachos/machine/Machine$MachinePrivilege.class b/proj1/nachos/machine/Machine$MachinePrivilege.class new file mode 100644 index 0000000..d331f63 Binary files /dev/null and b/proj1/nachos/machine/Machine$MachinePrivilege.class differ diff --git a/proj1/nachos/machine/Machine.class b/proj1/nachos/machine/Machine.class new file mode 100644 index 0000000..3e598f9 Binary files /dev/null and b/proj1/nachos/machine/Machine.class differ diff --git a/proj1/nachos/machine/MalformedPacketException.class b/proj1/nachos/machine/MalformedPacketException.class new file mode 100644 index 0000000..7bd60bc Binary files /dev/null and b/proj1/nachos/machine/MalformedPacketException.class differ diff --git a/proj1/nachos/machine/NetworkLink$1.class b/proj1/nachos/machine/NetworkLink$1.class new file mode 100644 index 0000000..8b2c174 Binary files /dev/null and b/proj1/nachos/machine/NetworkLink$1.class differ diff --git a/proj1/nachos/machine/NetworkLink$2.class b/proj1/nachos/machine/NetworkLink$2.class new file mode 100644 index 0000000..5513fd1 Binary files /dev/null and b/proj1/nachos/machine/NetworkLink$2.class differ diff --git a/proj1/nachos/machine/NetworkLink$3.class b/proj1/nachos/machine/NetworkLink$3.class new file mode 100644 index 0000000..7fb8f92 Binary files /dev/null and b/proj1/nachos/machine/NetworkLink$3.class differ diff --git a/proj1/nachos/machine/NetworkLink$4.class b/proj1/nachos/machine/NetworkLink$4.class new file mode 100644 index 0000000..cffc30f Binary files /dev/null and b/proj1/nachos/machine/NetworkLink$4.class differ diff --git a/proj1/nachos/machine/NetworkLink.class b/proj1/nachos/machine/NetworkLink.class new file mode 100644 index 0000000..a1b7cb7 Binary files /dev/null and b/proj1/nachos/machine/NetworkLink.class differ diff --git a/proj1/nachos/machine/OpenFile.class b/proj1/nachos/machine/OpenFile.class new file mode 100644 index 0000000..6b6bd05 Binary files /dev/null and b/proj1/nachos/machine/OpenFile.class differ diff --git a/proj1/nachos/machine/OpenFileWithPosition.class b/proj1/nachos/machine/OpenFileWithPosition.class new file mode 100644 index 0000000..c17fc7b Binary files /dev/null and b/proj1/nachos/machine/OpenFileWithPosition.class differ diff --git a/proj1/nachos/machine/Packet.class b/proj1/nachos/machine/Packet.class new file mode 100644 index 0000000..0098597 Binary files /dev/null and b/proj1/nachos/machine/Packet.class differ diff --git a/proj1/nachos/machine/Processor$1.class b/proj1/nachos/machine/Processor$1.class new file mode 100644 index 0000000..7b4ae87 Binary files /dev/null and b/proj1/nachos/machine/Processor$1.class differ diff --git a/proj1/nachos/machine/Processor$Instruction.class b/proj1/nachos/machine/Processor$Instruction.class new file mode 100644 index 0000000..7ae867b Binary files /dev/null and b/proj1/nachos/machine/Processor$Instruction.class differ diff --git a/proj1/nachos/machine/Processor$Mips.class b/proj1/nachos/machine/Processor$Mips.class new file mode 100644 index 0000000..b46a70e Binary files /dev/null and b/proj1/nachos/machine/Processor$Mips.class differ diff --git a/proj1/nachos/machine/Processor$MipsException.class b/proj1/nachos/machine/Processor$MipsException.class new file mode 100644 index 0000000..a51133e Binary files /dev/null and b/proj1/nachos/machine/Processor$MipsException.class differ diff --git a/proj1/nachos/machine/Processor$ProcessorPrivilege.class b/proj1/nachos/machine/Processor$ProcessorPrivilege.class new file mode 100644 index 0000000..d1a01da Binary files /dev/null and b/proj1/nachos/machine/Processor$ProcessorPrivilege.class differ diff --git a/proj1/nachos/machine/Processor.class b/proj1/nachos/machine/Processor.class new file mode 100644 index 0000000..b4b5d54 Binary files /dev/null and b/proj1/nachos/machine/Processor.class differ diff --git a/proj1/nachos/machine/RiderControls.class b/proj1/nachos/machine/RiderControls.class new file mode 100644 index 0000000..e3e9fe6 Binary files /dev/null and b/proj1/nachos/machine/RiderControls.class differ diff --git a/proj1/nachos/machine/RiderEvent.class b/proj1/nachos/machine/RiderEvent.class new file mode 100644 index 0000000..a52c08e Binary files /dev/null and b/proj1/nachos/machine/RiderEvent.class differ diff --git a/proj1/nachos/machine/RiderInterface.class b/proj1/nachos/machine/RiderInterface.class new file mode 100644 index 0000000..a15e55b Binary files /dev/null and b/proj1/nachos/machine/RiderInterface.class differ diff --git a/proj1/nachos/machine/SerialConsole.class b/proj1/nachos/machine/SerialConsole.class new file mode 100644 index 0000000..444fe72 Binary files /dev/null and b/proj1/nachos/machine/SerialConsole.class differ diff --git a/proj1/nachos/machine/StandardConsole$1.class b/proj1/nachos/machine/StandardConsole$1.class new file mode 100644 index 0000000..9da6783 Binary files /dev/null and b/proj1/nachos/machine/StandardConsole$1.class differ diff --git a/proj1/nachos/machine/StandardConsole$2.class b/proj1/nachos/machine/StandardConsole$2.class new file mode 100644 index 0000000..20b5f20 Binary files /dev/null and b/proj1/nachos/machine/StandardConsole$2.class differ diff --git a/proj1/nachos/machine/StandardConsole.class b/proj1/nachos/machine/StandardConsole.class new file mode 100644 index 0000000..fbd21c6 Binary files /dev/null and b/proj1/nachos/machine/StandardConsole.class differ diff --git a/proj1/nachos/machine/Stats.class b/proj1/nachos/machine/Stats.class new file mode 100644 index 0000000..89425ad Binary files /dev/null and b/proj1/nachos/machine/Stats.class differ diff --git a/proj1/nachos/machine/StubFileSystem$FileRemover.class b/proj1/nachos/machine/StubFileSystem$FileRemover.class new file mode 100644 index 0000000..b8ac9de Binary files /dev/null and b/proj1/nachos/machine/StubFileSystem$FileRemover.class differ diff --git a/proj1/nachos/machine/StubFileSystem$StubOpenFile$1.class b/proj1/nachos/machine/StubFileSystem$StubOpenFile$1.class new file mode 100644 index 0000000..98347b0 Binary files /dev/null and b/proj1/nachos/machine/StubFileSystem$StubOpenFile$1.class differ diff --git a/proj1/nachos/machine/StubFileSystem$StubOpenFile.class b/proj1/nachos/machine/StubFileSystem$StubOpenFile.class new file mode 100644 index 0000000..a4e5a60 Binary files /dev/null and b/proj1/nachos/machine/StubFileSystem$StubOpenFile.class differ diff --git a/proj1/nachos/machine/StubFileSystem.class b/proj1/nachos/machine/StubFileSystem.class new file mode 100644 index 0000000..3eb3915 Binary files /dev/null and b/proj1/nachos/machine/StubFileSystem.class differ diff --git a/proj1/nachos/machine/TCB$1.class b/proj1/nachos/machine/TCB$1.class new file mode 100644 index 0000000..35d9276 Binary files /dev/null and b/proj1/nachos/machine/TCB$1.class differ diff --git a/proj1/nachos/machine/TCB$2.class b/proj1/nachos/machine/TCB$2.class new file mode 100644 index 0000000..b6524db Binary files /dev/null and b/proj1/nachos/machine/TCB$2.class differ diff --git a/proj1/nachos/machine/TCB$TCBPrivilege.class b/proj1/nachos/machine/TCB$TCBPrivilege.class new file mode 100644 index 0000000..e65ebcd Binary files /dev/null and b/proj1/nachos/machine/TCB$TCBPrivilege.class differ diff --git a/proj1/nachos/machine/TCB.class b/proj1/nachos/machine/TCB.class new file mode 100644 index 0000000..736a08e Binary files /dev/null and b/proj1/nachos/machine/TCB.class differ diff --git a/proj1/nachos/machine/Timer$1.class b/proj1/nachos/machine/Timer$1.class new file mode 100644 index 0000000..5a48409 Binary files /dev/null and b/proj1/nachos/machine/Timer$1.class differ diff --git a/proj1/nachos/machine/Timer$2.class b/proj1/nachos/machine/Timer$2.class new file mode 100644 index 0000000..147dde6 Binary files /dev/null and b/proj1/nachos/machine/Timer$2.class differ diff --git a/proj1/nachos/machine/Timer.class b/proj1/nachos/machine/Timer.class new file mode 100644 index 0000000..4115346 Binary files /dev/null and b/proj1/nachos/machine/Timer.class differ diff --git a/proj1/nachos/machine/TranslationEntry.class b/proj1/nachos/machine/TranslationEntry.class new file mode 100644 index 0000000..6d6e419 Binary files /dev/null and b/proj1/nachos/machine/TranslationEntry.class differ diff --git a/proj1/nachos/security/NachosSecurityManager$1.class b/proj1/nachos/security/NachosSecurityManager$1.class new file mode 100644 index 0000000..3568400 Binary files /dev/null and b/proj1/nachos/security/NachosSecurityManager$1.class differ diff --git a/proj1/nachos/security/NachosSecurityManager$2.class b/proj1/nachos/security/NachosSecurityManager$2.class new file mode 100644 index 0000000..c3de94b Binary files /dev/null and b/proj1/nachos/security/NachosSecurityManager$2.class differ diff --git a/proj1/nachos/security/NachosSecurityManager$PrivilegeProvider.class b/proj1/nachos/security/NachosSecurityManager$PrivilegeProvider.class new file mode 100644 index 0000000..97718d3 Binary files /dev/null and b/proj1/nachos/security/NachosSecurityManager$PrivilegeProvider.class differ diff --git a/proj1/nachos/security/NachosSecurityManager.class b/proj1/nachos/security/NachosSecurityManager.class new file mode 100644 index 0000000..a4fe3cc Binary files /dev/null and b/proj1/nachos/security/NachosSecurityManager.class differ diff --git a/proj1/nachos/security/Privilege$InterruptPrivilege.class b/proj1/nachos/security/Privilege$InterruptPrivilege.class new file mode 100644 index 0000000..8b0b161 Binary files /dev/null and b/proj1/nachos/security/Privilege$InterruptPrivilege.class differ diff --git a/proj1/nachos/security/Privilege$MachinePrivilege.class b/proj1/nachos/security/Privilege$MachinePrivilege.class new file mode 100644 index 0000000..f373345 Binary files /dev/null and b/proj1/nachos/security/Privilege$MachinePrivilege.class differ diff --git a/proj1/nachos/security/Privilege$ProcessorPrivilege.class b/proj1/nachos/security/Privilege$ProcessorPrivilege.class new file mode 100644 index 0000000..c8a1878 Binary files /dev/null and b/proj1/nachos/security/Privilege$ProcessorPrivilege.class differ diff --git a/proj1/nachos/security/Privilege$TCBPrivilege.class b/proj1/nachos/security/Privilege$TCBPrivilege.class new file mode 100644 index 0000000..c2fdda7 Binary files /dev/null and b/proj1/nachos/security/Privilege$TCBPrivilege.class differ diff --git a/proj1/nachos/security/Privilege.class b/proj1/nachos/security/Privilege.class new file mode 100644 index 0000000..7ca2654 Binary files /dev/null and b/proj1/nachos/security/Privilege.class differ diff --git a/proj1/nachos/threads/Alarm$1.class b/proj1/nachos/threads/Alarm$1.class new file mode 100644 index 0000000..7061dbd Binary files /dev/null and b/proj1/nachos/threads/Alarm$1.class differ diff --git a/proj1/nachos/threads/Alarm.class b/proj1/nachos/threads/Alarm.class new file mode 100644 index 0000000..cd63ab6 Binary files /dev/null and b/proj1/nachos/threads/Alarm.class differ diff --git a/proj1/nachos/threads/Boat$1.class b/proj1/nachos/threads/Boat$1.class new file mode 100644 index 0000000..975db12 Binary files /dev/null and b/proj1/nachos/threads/Boat$1.class differ diff --git a/proj1/nachos/threads/Boat.class b/proj1/nachos/threads/Boat.class new file mode 100644 index 0000000..5188e6a Binary files /dev/null and b/proj1/nachos/threads/Boat.class differ diff --git a/proj1/nachos/threads/Communicator.class b/proj1/nachos/threads/Communicator.class new file mode 100644 index 0000000..2981b3b Binary files /dev/null and b/proj1/nachos/threads/Communicator.class differ diff --git a/proj1/nachos/threads/Condition.class b/proj1/nachos/threads/Condition.class new file mode 100644 index 0000000..404d1e7 Binary files /dev/null and b/proj1/nachos/threads/Condition.class differ diff --git a/proj1/nachos/threads/Condition2.class b/proj1/nachos/threads/Condition2.class new file mode 100644 index 0000000..76e29da Binary files /dev/null and b/proj1/nachos/threads/Condition2.class differ diff --git a/proj1/nachos/threads/ElevatorController.class b/proj1/nachos/threads/ElevatorController.class new file mode 100644 index 0000000..ba38a45 Binary files /dev/null and b/proj1/nachos/threads/ElevatorController.class differ diff --git a/proj1/nachos/threads/KThread$1.class b/proj1/nachos/threads/KThread$1.class new file mode 100644 index 0000000..13f7e4d Binary files /dev/null and b/proj1/nachos/threads/KThread$1.class differ diff --git a/proj1/nachos/threads/KThread$2.class b/proj1/nachos/threads/KThread$2.class new file mode 100644 index 0000000..bfc9cc3 Binary files /dev/null and b/proj1/nachos/threads/KThread$2.class differ diff --git a/proj1/nachos/threads/KThread$PingTest.class b/proj1/nachos/threads/KThread$PingTest.class new file mode 100644 index 0000000..9b27f65 Binary files /dev/null and b/proj1/nachos/threads/KThread$PingTest.class differ diff --git a/proj1/nachos/threads/KThread.class b/proj1/nachos/threads/KThread.class new file mode 100644 index 0000000..6948a57 Binary files /dev/null and b/proj1/nachos/threads/KThread.class differ diff --git a/proj1/nachos/threads/Lock.class b/proj1/nachos/threads/Lock.class new file mode 100644 index 0000000..4c691eb Binary files /dev/null and b/proj1/nachos/threads/Lock.class differ diff --git a/proj1/nachos/threads/LotteryScheduler.class b/proj1/nachos/threads/LotteryScheduler.class new file mode 100644 index 0000000..678eadf Binary files /dev/null and b/proj1/nachos/threads/LotteryScheduler.class differ diff --git a/proj1/nachos/threads/PriorityScheduler$PriorityQueue.class b/proj1/nachos/threads/PriorityScheduler$PriorityQueue.class new file mode 100644 index 0000000..3d63210 Binary files /dev/null and b/proj1/nachos/threads/PriorityScheduler$PriorityQueue.class differ diff --git a/proj1/nachos/threads/PriorityScheduler$ThreadState.class b/proj1/nachos/threads/PriorityScheduler$ThreadState.class new file mode 100644 index 0000000..66f6346 Binary files /dev/null and b/proj1/nachos/threads/PriorityScheduler$ThreadState.class differ diff --git a/proj1/nachos/threads/PriorityScheduler.class b/proj1/nachos/threads/PriorityScheduler.class new file mode 100644 index 0000000..ecb310a Binary files /dev/null and b/proj1/nachos/threads/PriorityScheduler.class differ diff --git a/proj1/nachos/threads/Rider.class b/proj1/nachos/threads/Rider.class new file mode 100644 index 0000000..68aa50e Binary files /dev/null and b/proj1/nachos/threads/Rider.class differ diff --git a/proj1/nachos/threads/RoundRobinScheduler$1.class b/proj1/nachos/threads/RoundRobinScheduler$1.class new file mode 100644 index 0000000..8d2ef69 Binary files /dev/null and b/proj1/nachos/threads/RoundRobinScheduler$1.class differ diff --git a/proj1/nachos/threads/RoundRobinScheduler$FifoQueue.class b/proj1/nachos/threads/RoundRobinScheduler$FifoQueue.class new file mode 100644 index 0000000..3087e1b Binary files /dev/null and b/proj1/nachos/threads/RoundRobinScheduler$FifoQueue.class differ diff --git a/proj1/nachos/threads/RoundRobinScheduler.class b/proj1/nachos/threads/RoundRobinScheduler.class new file mode 100644 index 0000000..df77f18 Binary files /dev/null and b/proj1/nachos/threads/RoundRobinScheduler.class differ diff --git a/proj1/nachos/threads/Scheduler.class b/proj1/nachos/threads/Scheduler.class new file mode 100644 index 0000000..0b29042 Binary files /dev/null and b/proj1/nachos/threads/Scheduler.class differ diff --git a/proj1/nachos/threads/Semaphore$PingTest.class b/proj1/nachos/threads/Semaphore$PingTest.class new file mode 100644 index 0000000..07784e6 Binary files /dev/null and b/proj1/nachos/threads/Semaphore$PingTest.class differ diff --git a/proj1/nachos/threads/Semaphore.class b/proj1/nachos/threads/Semaphore.class new file mode 100644 index 0000000..6d61ee5 Binary files /dev/null and b/proj1/nachos/threads/Semaphore.class differ diff --git a/proj1/nachos/threads/SynchList$PingTest.class b/proj1/nachos/threads/SynchList$PingTest.class new file mode 100644 index 0000000..4aef68e Binary files /dev/null and b/proj1/nachos/threads/SynchList$PingTest.class differ diff --git a/proj1/nachos/threads/SynchList.class b/proj1/nachos/threads/SynchList.class new file mode 100644 index 0000000..1015769 Binary files /dev/null and b/proj1/nachos/threads/SynchList.class differ diff --git a/proj1/nachos/threads/ThreadQueue.class b/proj1/nachos/threads/ThreadQueue.class new file mode 100644 index 0000000..7c66e42 Binary files /dev/null and b/proj1/nachos/threads/ThreadQueue.class differ diff --git a/proj1/nachos/threads/ThreadedKernel.class b/proj1/nachos/threads/ThreadedKernel.class new file mode 100644 index 0000000..98e0f4e Binary files /dev/null and b/proj1/nachos/threads/ThreadedKernel.class differ diff --git a/proj2/Makefile b/proj2/Makefile new file mode 100644 index 0000000..64c9da5 --- /dev/null +++ b/proj2/Makefile @@ -0,0 +1,3 @@ +DIRS = userprog threads machine security ag + +include ../Makefile diff --git a/proj2/nachos.conf b/proj2/nachos.conf new file mode 100644 index 0000000..79a6b90 --- /dev/null +++ b/proj2/nachos.conf @@ -0,0 +1,14 @@ +Machine.stubFileSystem = true +Machine.processor = true +Machine.console = true +Machine.disk = false +Machine.bank = false +Machine.networkLink = false +Processor.usingTLB = false +Processor.numPhysPages = 64 +ElevatorBank.allowElevatorGUI = false +NachosSecurityManager.fullySecure = false +ThreadedKernel.scheduler = nachos.threads.RoundRobinScheduler #nachos.threads.LotteryScheduler +Kernel.shellProgram = halt.coff #sh.coff +Kernel.processClassName = nachos.userprog.UserProcess +Kernel.kernel = nachos.userprog.UserKernel diff --git a/proj3/Makefile b/proj3/Makefile new file mode 100644 index 0000000..9901fc5 --- /dev/null +++ b/proj3/Makefile @@ -0,0 +1,3 @@ +DIRS = vm userprog threads machine security ag + +include ../Makefile diff --git a/proj3/nachos.conf b/proj3/nachos.conf new file mode 100644 index 0000000..7622166 --- /dev/null +++ b/proj3/nachos.conf @@ -0,0 +1,14 @@ +Machine.stubFileSystem = true +Machine.processor = true +Machine.console = true +Machine.disk = false +Machine.bank = false +Machine.networkLink = false +Processor.usingTLB = true +Processor.numPhysPages = 16 +ElevatorBank.allowElevatorGUI = false +NachosSecurityManager.fullySecure = false +ThreadedKernel.scheduler = nachos.threads.RoundRobinScheduler +Kernel.shellProgram = sh.coff +Kernel.processClassName = nachos.vm.VMProcess +Kernel.kernel = nachos.vm.VMKernel diff --git a/proj4/Makefile b/proj4/Makefile new file mode 100644 index 0000000..e2890ef --- /dev/null +++ b/proj4/Makefile @@ -0,0 +1,3 @@ +DIRS = network vm userprog threads machine security ag + +include ../Makefile diff --git a/proj4/nachos.conf b/proj4/nachos.conf new file mode 100644 index 0000000..e1e3b01 --- /dev/null +++ b/proj4/nachos.conf @@ -0,0 +1,16 @@ +Machine.stubFileSystem = true +Machine.processor = true +Machine.console = true +Machine.disk = false +Machine.bank = false +Machine.networkLink = true +Processor.usingTLB = true +Processor.variableTLB = true +Processor.numPhysPages = 16 +ElevatorBank.allowElevatorGUI = false +NetworkLink.reliability = 1.0 # use 0.9 when you're ready +NachosSecurityManager.fullySecure = false +ThreadedKernel.scheduler = nachos.threads.RoundRobinScheduler +Kernel.shellProgram = sh.coff +Kernel.processClassName = nachos.network.NetProcess +Kernel.kernel = nachos.network.NetKernel diff --git a/security/NachosSecurityManager.java b/security/NachosSecurityManager.java new file mode 100644 index 0000000..8961575 --- /dev/null +++ b/security/NachosSecurityManager.java @@ -0,0 +1,318 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.security; + +import nachos.machine.*; + +import java.io.File; +import java.security.Permission; +import java.io.FilePermission; +import java.util.PropertyPermission; +import java.net.NetPermission; +import java.awt.AWTPermission; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.PrivilegedActionException; + +/** + * Protects the environment from malicious Nachos code. + */ +public class NachosSecurityManager extends SecurityManager { + /** + * Allocate a new Nachos security manager. + * + * @param testDirectory the directory usable by the stub file system. + */ + public NachosSecurityManager(File testDirectory) { + this.testDirectory = testDirectory; + + fullySecure = Config.getBoolean("NachosSecurityManager.fullySecure"); + } + + /** + * Return a privilege object for this security manager. This security + * manager must not be the active security manager. + * + * @return a privilege object for this security manager. + */ + public Privilege getPrivilege() { + Lib.assertTrue(this != System.getSecurityManager()); + + return new PrivilegeProvider(); + } + + /** + * Install this security manager. + */ + public void enable() { + Lib.assertTrue(this != System.getSecurityManager()); + + doPrivileged(new Runnable() { + public void run() { + System.setSecurityManager(NachosSecurityManager.this); + } + }); + } + + private class PrivilegeProvider extends Privilege { + public void doPrivileged(Runnable action) { + NachosSecurityManager.this.doPrivileged(action); + } + + public Object doPrivileged(PrivilegedAction action) { + return NachosSecurityManager.this.doPrivileged(action); + } + + public Object doPrivileged(PrivilegedExceptionAction action) + throws PrivilegedActionException { + return NachosSecurityManager.this.doPrivileged(action); + } + + public void exit(int exitStatus) { + invokeExitNotificationHandlers(); + NachosSecurityManager.this.exit(exitStatus); + } + } + + private void enablePrivilege() { + if (privilegeCount == 0) { + Lib.assertTrue(privileged == null); + privileged = Thread.currentThread(); + privilegeCount++; + } + else { + Lib.assertTrue(privileged == Thread.currentThread()); + privilegeCount++; + } + } + + private void rethrow(Throwable e) { + disablePrivilege(); + + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else if (e instanceof Error) + throw (Error) e; + else + Lib.assertNotReached(); + } + + private void disablePrivilege() { + Lib.assertTrue(privileged != null && privilegeCount > 0); + privilegeCount--; + if (privilegeCount == 0) + privileged = null; + } + + private void forcePrivilege() { + privileged = Thread.currentThread(); + privilegeCount = 1; + } + + private void exit(int exitStatus) { + forcePrivilege(); + System.exit(exitStatus); + } + + private boolean isPrivileged() { + // the autograder does not allow non-Nachos threads to be created, so.. + if (!TCB.isNachosThread()) + return true; + + return (privileged == Thread.currentThread()); + } + + private void doPrivileged(final Runnable action) { + doPrivileged(new PrivilegedAction() { + public Object run() { action.run(); return null; } + }); + } + + private Object doPrivileged(PrivilegedAction action) { + Object result = null; + enablePrivilege(); + try { + result = action.run(); + } + catch (Throwable e) { + rethrow(e); + } + disablePrivilege(); + return result; + } + + private Object doPrivileged(PrivilegedExceptionAction action) + throws PrivilegedActionException { + Object result = null; + enablePrivilege(); + try { + result = action.run(); + } + catch (Exception e) { + throw new PrivilegedActionException(e); + } + catch (Throwable e) { + rethrow(e); + } + disablePrivilege(); + return result; + } + + private void no() { + throw new SecurityException(); + } + + private void no(Permission perm) { + System.err.println("\n\nLacked permission: " + perm); + throw new SecurityException(); + } + + /** + * Check the specified permission. Some operations are permissible while + * not grading. These operations are regulated here. + * + * @param perm the permission to check. + */ + public void checkPermission(Permission perm) { + String name = perm.getName(); + + // some permissions are strictly forbidden + if (perm instanceof RuntimePermission) { + // no creating class loaders + if (name.equals("createClassLoader")) + no(perm); + } + + // allow the AWT mess when not grading + if (!fullySecure) { + if (perm instanceof NetPermission) { + // might be needed to load awt stuff + if (name.equals("specifyStreamHandler")) + return; + } + + if (perm instanceof RuntimePermission) { + // might need to load libawt + if (name.startsWith("loadLibrary.")) { + String lib = name.substring("loadLibrary.".length()); + if (lib.equals("awt")) { + Lib.debug(dbgSecurity, "\tdynamically linking " + lib); + return; + } + } + } + + if (perm instanceof AWTPermission) { + // permit AWT stuff + if (name.equals("accessEventQueue")) + return; + } + } + + // some are always allowed + if (perm instanceof PropertyPermission) { + // allowed to read properties + if (perm.getActions().equals("read")) + return; + } + + // some require some more checking + if (perm instanceof FilePermission) { + if (perm.getActions().equals("read")) { + // the test directory can only be read with privilege + if (isPrivileged()) + return; + + enablePrivilege(); + + // not allowed to read test directory directly w/out privilege + try { + File f = new File(name); + if (f.isFile()) { + File p = f.getParentFile(); + if (p != null) { + if (p.equals(testDirectory)) + no(perm); + } + } + } + catch (Throwable e) { + rethrow(e); + } + + disablePrivilege(); + return; + } + else if (perm.getActions().equals("write") || + perm.getActions().equals("delete")) { + // only allowed to write test diretory, and only with privilege + verifyPrivilege(); + + try { + File f = new File(name); + if (f.isFile()) { + File p = f.getParentFile(); + if (p != null && p.equals(testDirectory)) + return; + } + } + catch (Throwable e) { + no(perm); + } + } + else if (perm.getActions().equals("execute")) { + // only allowed to execute with privilege, and if there's a net + verifyPrivilege(); + + if (Machine.networkLink() == null) + no(perm); + } + else { + no(perm); + } + } + + // default to requiring privilege + verifyPrivilege(perm); + } + + /** + * Called by the java.lang.Thread constructor to determine a + * thread group for a child thread of the current thread. The caller must + * be privileged in order to successfully create the thread. + * + * @return a thread group for the new thread, or null to use the + * current thread's thread group. + */ + public ThreadGroup getThreadGroup() { + verifyPrivilege(); + return null; + } + + /** + * Verify that the caller is privileged. + */ + public void verifyPrivilege() { + if (!isPrivileged()) + no(); + } + + /** + * Verify that the caller is privileged, so as to check the specified + * permission. + * + * @param perm the permission being checked. + */ + public void verifyPrivilege(Permission perm) { + if (!isPrivileged()) + no(perm); + } + + private File testDirectory; + private boolean fullySecure; + + private Thread privileged = null; + private int privilegeCount = 0; + + private static final char dbgSecurity = 'S'; +} diff --git a/security/Privilege.java b/security/Privilege.java new file mode 100644 index 0000000..28f8a90 --- /dev/null +++ b/security/Privilege.java @@ -0,0 +1,195 @@ +// PART OF THE MACHINE SIMULATION. DO NOT CHANGE. + +package nachos.security; + +import nachos.machine.*; +import nachos.threads.KThread; + +import java.util.LinkedList; +import java.util.Iterator; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.PrivilegedActionException; + +/** + * A capability that allows privileged access to the Nachos machine. + * + *

+ * Some privileged operations are guarded by the Nachos security manager: + *

    + *
  1. creating threads + *
  2. writing/deleting files in the test directory + *
  3. exit with specific status code + *
+ * These operations can only be performed through doPrivileged(). + * + *

+ * Some privileged operations require a capability: + *

    + *
  1. scheduling interrupts + *
  2. advancing the simulated time + *
  3. accessing machine statistics + *
  4. installing a console + *
  5. flushing the simulated processor's pipeline + *
  6. approving TCB operations + *
+ * These operations can be directly performed using a Privilege + * object. + * + *

+ * The Nachos kernel should never be able to directly perform any of + * these privileged operations. If you have discovered a loophole somewhere, + * notify someone. + */ +public abstract class Privilege { + /** + * Allocate a new Privilege object. Note that this object in + * itself does not encapsulate privileged access until the machine devices + * fill it in. + */ + public Privilege() { + } + + /** + * Perform the specified action with privilege. + * + * @param action the action to perform. + */ + public abstract void doPrivileged(Runnable action); + + /** + * Perform the specified PrivilegedAction with privilege. + * + * @param action the action to perform. + * @return the return value of the action. + */ + public abstract Object doPrivileged(PrivilegedAction action); + + /** + * Perform the specified PrivilegedExceptionAction with privilege. + * + * @param action the action to perform. + * @return the return value of the action. + */ + public abstract Object doPrivileged(PrivilegedExceptionAction action) + throws PrivilegedActionException; + + /** + * Exit Nachos with the specified status. + * + * @param exitStatus the exit status of the Nachos process. + */ + public abstract void exit(int exitStatus); + + /** + * Add an exit() notification handler. The handler will be invoked + * by exit(). + * + * @param handler the notification handler. + */ + public void addExitNotificationHandler(Runnable handler) { + exitNotificationHandlers.add(handler); + } + + /** + * Invoke each exit() notification handler added by + * addExitNotificationHandler(). Called by exit(). + */ + protected void invokeExitNotificationHandlers() { + for (Iterator i=exitNotificationHandlers.iterator(); i.hasNext(); ) { + try { + ((Runnable) i.next()).run(); + } + catch (Throwable e) { + System.out.println("exit() notification handler failed"); + } + } + } + + private LinkedList exitNotificationHandlers = + new LinkedList(); + + /** Nachos runtime statistics. */ + public Stats stats = null; + + /** Provides access to some private Machine methods. */ + public MachinePrivilege machine = null; + /** Provides access to some private Interrupt methods. */ + public InterruptPrivilege interrupt = null; + /** Provides access to some private Processor methods. */ + public ProcessorPrivilege processor = null; + /** Provides access to some private TCB methods. */ + public TCBPrivilege tcb = null; + + /** + * An interface that provides access to some private Machine + * methods. + */ + public interface MachinePrivilege { + /** + * Install a hardware console. + * + * @param console the new hardware console. + */ + public void setConsole(SerialConsole console); + } + + /** + * An interface that provides access to some private Interrupt + * methods. + */ + public interface InterruptPrivilege { + /** + * Schedule an interrupt to occur at some time in the future. + * + * @param when the number of ticks until the interrupt should + * occur. + * @param type a name for the type of interrupt being + * scheduled. + * @param handler the interrupt handler to call. + */ + public void schedule(long when, String type, Runnable handler); + + /** + * Advance the simulated time. + * + * @param inKernelMode true if the current thread is running kernel + * code, false if the current thread is running + * MIPS user code. + */ + public void tick(boolean inKernelMode); + } + + /** + * An interface that provides access to some private Processor + * methods. + */ + public interface ProcessorPrivilege { + /** + * Flush the processor pipeline in preparation for switching to kernel + * mode. + */ + public void flushPipe(); + } + + /** + * An interface that provides access to some private TCB methods. + */ + public interface TCBPrivilege { + /** + * Associate the current TCB with the specified KThread. + * AutoGrader.runningThread() must call this method + * before returning. + * + * @param thread the current thread. + */ + public void associateThread(KThread thread); + /** + * Authorize the TCB associated with the specified thread to be + * destroyed. + * + * @param thread the thread whose TCB is about to be destroyed. + */ + public void authorizeDestroy(KThread thread); + } +} diff --git a/security/package.html b/security/package.html new file mode 100644 index 0000000..a38c48e --- /dev/null +++ b/security/package.html @@ -0,0 +1,4 @@ + +Provides classes that can be used to protect the host system from malicious +Nachos kernels. + diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..17b699c --- /dev/null +++ b/test/Makefile @@ -0,0 +1,59 @@ +# GNU Makefile for building user programs to run on top of Nachos +# +# Things to be aware of: +# +# The value of the ARCHDIR environment variable must be set before using +# this makefile. If you are using an instructional machine, this should +# be automatic. However, if you are not using an instructional machine, +# you need to point ARCHDIR at the cross-compiler directory, e.g. +# setenv ARCHDIR ../mips-x86.win32-xgcc + +# you need to point to the right executables +GCCDIR = $(ARCHDIR)/mips- + +ASFLAGS = -mips1 +CPPFLAGS = +CFLAGS = -O2 -B$(GCCDIR) -G 0 -Wa,-mips1 -nostdlib -ffreestanding +LDFLAGS = -s -T script -N -warn-common -warn-constructors -warn-multiple-gp + +CC = $(GCCDIR)gcc +AS = $(GCCDIR)as +LD = $(GCCDIR)ld +CPP = $(GCCDIR)cpp +AR = $(GCCDIR)ar +RANLIB = $(GCCDIR)ranlib + +STDLIB_H = stdio.h stdlib.h ag.h +STDLIB_C = stdio.c stdlib.c +STDLIB_O = start.o stdio.o stdlib.o + +LIB = assert atoi printf readline stdio strncmp strcat strcmp strcpy strlen memcpy memset +NLIB = libnachos.a + +TARGETS = halt sh matmult sort echo cat cp mv rm #chat chatserver + +.SECONDARY: $(patsubst %.c,%.o,$(wildcard *.c)) + +all: $(patsubst %,%.coff,$(TARGETS)) + +ag: grade-file.coff grade-exec.coff grade-mini.coff grade-dumb.coff + +clean: + rm -f strt.s *.o *.coff $(NLIB) + +agclean: clean + rm -f f1-* f2-* + +$(NLIB): $(patsubst %,$(NLIB)(%.o),$(LIB)) start.o + $(RANLIB) $(NLIB) + +start.o: start.s syscall.h + $(CPP) $(CPPFLAGS) start.s > strt.s + $(AS) $(ASFLAGS) -o start.o strt.s + rm strt.s + +%.o: %.c *.h + $(CC) $(CFLAGS) -c $< + +%.coff: %.o $(NLIB) + $(LD) $(LDFLAGS) -o $@ $< start.o -lnachos diff --git a/test/assert.c b/test/assert.c new file mode 100644 index 0000000..072af4b --- /dev/null +++ b/test/assert.c @@ -0,0 +1,7 @@ +#include "stdio.h" +#include "stdlib.h" + +void __assert(char* file, int line) { + printf("\nAssertion failed: line %d file %s\n", line, file); + exit(1); +} diff --git a/test/assert.o b/test/assert.o new file mode 100644 index 0000000..ca403ba Binary files /dev/null and b/test/assert.o differ diff --git a/test/atoi.c b/test/atoi.c new file mode 100644 index 0000000..9dad017 --- /dev/null +++ b/test/atoi.c @@ -0,0 +1,15 @@ +#include "stdlib.h" + +int atoi(const char *s) { + int result=0, sign=1; + + if (*s == -1) { + sign = -1; + s++; + } + + while (*s >= '0' && *s <= '9') + result = result*10 + (*(s++)-'0'); + + return result*sign; +} diff --git a/test/atoi.o b/test/atoi.o new file mode 100644 index 0000000..319f628 Binary files /dev/null and b/test/atoi.o differ diff --git a/test/cat.c b/test/cat.c new file mode 100644 index 0000000..c3e758a --- /dev/null +++ b/test/cat.c @@ -0,0 +1,31 @@ +#include "syscall.h" +#include "stdio.h" +#include "stdlib.h" + +#define BUFSIZE 1024 + +char buf[BUFSIZE]; + +int main(int argc, char** argv) +{ + int fd, amount; + + if (argc!=2) { + printf("Usage: cat \n"); + return 1; + } + + fd = open(argv[1]); + if (fd==-1) { + printf("Unable to open %s\n", argv[1]); + return 1; + } + + while ((amount = read(fd, buf, BUFSIZE))>0) { + write(1, buf, amount); + } + + close(fd); + + return 0; +} diff --git a/test/cat.coff b/test/cat.coff new file mode 100644 index 0000000..9946f7b Binary files /dev/null and b/test/cat.coff differ diff --git a/test/cat.o b/test/cat.o new file mode 100644 index 0000000..928f28d Binary files /dev/null and b/test/cat.o differ diff --git a/test/cp.c b/test/cp.c new file mode 100644 index 0000000..4bf84b7 --- /dev/null +++ b/test/cp.c @@ -0,0 +1,39 @@ +#include "syscall.h" +#include "stdio.h" +#include "stdlib.h" + +#define BUFSIZE 1024 + +char buf[BUFSIZE]; + +int main(int argc, char** argv) +{ + int src, dst, amount; + + if (argc!=3) { + printf("Usage: cp \n"); + return 1; + } + + src = open(argv[1]); + if (src==-1) { + printf("Unable to open %s\n", argv[1]); + return 1; + } + + creat(argv[2]); + dst = open(argv[2]); + if (dst==-1) { + printf("Unable to create %s\n", argv[2]); + return 1; + } + + while ((amount = read(src, buf, BUFSIZE))>0) { + write(dst, buf, amount); + } + + close(src); + close(dst); + + return 0; +} diff --git a/test/cp.coff b/test/cp.coff new file mode 100644 index 0000000..a9d55b3 Binary files /dev/null and b/test/cp.coff differ diff --git a/test/cp.o b/test/cp.o new file mode 100644 index 0000000..a987f9c Binary files /dev/null and b/test/cp.o differ diff --git a/test/echo.c b/test/echo.c new file mode 100644 index 0000000..b3716aa --- /dev/null +++ b/test/echo.c @@ -0,0 +1,14 @@ +#include "stdio.h" +#include "stdlib.h" + +int main(int argc, char** argv) +{ + int i; + + printf("%d arguments\n", argc); + + for (i=0; i \n"); + return 1; + } + + src = open(argv[1]); + if (src==-1) { + printf("Open to open %s\n", argv[1]); + return 1; + } + + creat(argv[2]); + dst = open(argv[2]); + if (dst==-1) { + printf("Unable to create %s\n", argv[2]); + return 1; + } + + while ((amount = read(src, buf, BUFSIZE))>0) { + write(dst, buf, amount); + } + + close(src); + close(dst); + unlink(argv[1]); + + return 0; +} diff --git a/test/mv.coff b/test/mv.coff new file mode 100644 index 0000000..18f4cfd Binary files /dev/null and b/test/mv.coff differ diff --git a/test/mv.o b/test/mv.o new file mode 100644 index 0000000..eeac8bd Binary files /dev/null and b/test/mv.o differ diff --git a/test/printf.c b/test/printf.c new file mode 100644 index 0000000..083386d --- /dev/null +++ b/test/printf.c @@ -0,0 +1,184 @@ +#include "stdio.h" +#include "stdlib.h" + +static char digittoascii(unsigned n, int uppercase) { + assert(n<36); + + if (n<=9) + return '0'+n; + else if (uppercase) + return 'A'+n-10; + else + return 'a'+n-10; +} + +static int charprint(char **s, char c) { + *((*s)++) = c; + + return 1; +} + +static int mcharprint(char **s, char* chars, int length) { + memcpy(*s, chars, length); + *s += length; + + return length; +} + +static int integerprint(char **s, int n, unsigned base, int min, int zpad, int upper) { + char buf[32]; + int i=32, digit, len=0; + + assert(base>=2 && base < 36); + + if (min>32) + min=32; + + if (n==0) { + for (i=1; i32-min) + buf[--i] = zpad ? '0' : ' '; + + len += mcharprint(s, &buf[i], 32-i); + return len; +} + +static int stringprint(char **s, char *string) { + return mcharprint(s, string, strlen(string)); +} + +static int _vsprintf(char *s, char *format, va_list ap) { + int min,zpad,len=0,regular=0; + char *temp; + + /* process format string */ + while (*format != 0) { + /* if switch, process */ + if (*format == '%') { + if (regular > 0) { + len += mcharprint(&s, format-regular, regular); + regular = 0; + } + format++; + /* bug: '-' here will potentially screw things up */ + assert(*format != '-'); + + min=zpad=0; + + if (*format == '0') + zpad=1; + + min = atoi(format); + + temp = format; + while (*temp >= '0' && *temp <= '9') + temp++; + + switch (*(temp++)) { + + case 'c': + len += charprint(&s, va_arg(ap, int)); + break; + + case 'd': + len += integerprint(&s, va_arg(ap, int), 10, min, zpad, 0); + break; + + case 'x': + len += integerprint(&s, va_arg(ap, int), 16, min, zpad, 0); + break; + + case 'X': + len += integerprint(&s, va_arg(ap, int), 16, min, zpad, 1); + break; + + case 's': + len += stringprint(&s, (char*) va_arg(ap, int)); + break; + + default: + len += charprint(&s, '%'); + temp = format; + } + + format = temp; + } + else { + regular++; + format++; + } + } + + if (regular > 0) { + len += mcharprint(&s, format-regular, regular); + regular = 0; + } + + *s = 0; + + return len; +} + +void vsprintf(char *s, char *format, va_list ap) { + _vsprintf(s, format, ap); +} + +static char vfprintfbuf[256]; + +void vfprintf(int fd, char *format, va_list ap) { + int len = _vsprintf(vfprintfbuf, format, ap); + assert(len < sizeof(vfprintfbuf)); + write(fd, vfprintfbuf, len); +} + +void vprintf(char *format, va_list ap) { + vfprintf(stdout, format, ap); +} + +void sprintf(char *s, char *format, ...) { + va_list ap; + va_start(ap,format); + + vsprintf(s, format, ap); + + va_end(ap); +} + +void fprintf(int fd, char *format, ...) { + va_list ap; + va_start(ap,format); + + vfprintf(fd, format, ap); + + va_end(ap); +} + +void printf(char *format, ...) { + va_list ap; + va_start(ap,format); + + vprintf(format, ap); + + va_end(ap); +} diff --git a/test/printf.o b/test/printf.o new file mode 100644 index 0000000..c33248c Binary files /dev/null and b/test/printf.o differ diff --git a/test/readline.c b/test/readline.c new file mode 100644 index 0000000..761e18a --- /dev/null +++ b/test/readline.c @@ -0,0 +1,37 @@ +#include "stdio.h" +#include "stdlib.h" + +void readline(char *s, int maxlength) { + int i = 0; + + while (1) { + char c = getch(); + /* if end of line, finish up */ + if (c == '\n') { + putchar('\n'); + s[i] = 0; + return; + } + /* else if backspace... */ + else if (c == '\b') { + /* if nothing to delete, beep */ + if (i == 0) { + beep(); + } + /* else delete it */ + else { + printf("\b \b"); + i--; + } + } + /* else if bad character or no room for more, beep */ + else if (c < 0x20 || i+1 == maxlength) { + beep(); + } + /* else add the character */ + else { + s[i++] = c; + putchar(c); + } + } +} diff --git a/test/readline.o b/test/readline.o new file mode 100644 index 0000000..f2ced4a Binary files /dev/null and b/test/readline.o differ diff --git a/test/rm.c b/test/rm.c new file mode 100644 index 0000000..cb5c213 --- /dev/null +++ b/test/rm.c @@ -0,0 +1,18 @@ +#include "syscall.h" +#include "stdio.h" +#include "stdlib.h" + +int main(int argc, char** argv) +{ + if (argc!=2) { + printf("Usage: rm \n"); + return 1; + } + + if (unlink(argv[1]) != 0) { + printf("Unable to remove %s\n", argv[1]); + return 1; + } + + return 0; +} diff --git a/test/rm.coff b/test/rm.coff new file mode 100644 index 0000000..62536f3 Binary files /dev/null and b/test/rm.coff differ diff --git a/test/rm.o b/test/rm.o new file mode 100644 index 0000000..eb186b5 Binary files /dev/null and b/test/rm.o differ diff --git a/test/script b/test/script new file mode 100644 index 0000000..5be8ccf --- /dev/null +++ b/test/script @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("ecoff-littlemips") +SEARCH_DIR(.) +ENTRY(__start) + +SECTIONS { + .text 0 : { *(.text) } + .text.startup BLOCK(0x400) : { *(.text.startup) } + .rdata BLOCK(0x400) : { *(.rdata) } + .data BLOCK(0x400) : { *(.data) } + .rodata BLOCK(0x400) : { *(.rodata) *(.rodata.str1.4)} + .sbss BLOCK(0x400) : { *(.sbss) } + .bss BLOCK(0x400) : { *(.bss) } + .scommon BLOCK(0x400) : { *(.scommon) } + /DISCARD/ : { *(.reginfo) *(.pdr) *(.comment) *(.gnu.attributes) } +} diff --git a/test/script_old b/test/script_old new file mode 100644 index 0000000..9776fe4 --- /dev/null +++ b/test/script_old @@ -0,0 +1,12 @@ +OUTPUT_FORMAT("ecoff-littlemips") +SEARCH_DIR(.) +ENTRY(__start) + +SECTIONS { + .text 0 : { *(.text) } + .rdata BLOCK(0x400) : { *(.rdata) } + .data BLOCK(0x400) : { *(.data) } + .sbss BLOCK(0x400) : { *(.sbss) } + .bss BLOCK(0x400) : { *(.bss) } + .scommon BLOCK(0x400) : { *(.scommon) } +} diff --git a/test/sh.c b/test/sh.c new file mode 100644 index 0000000..3b808c6 --- /dev/null +++ b/test/sh.c @@ -0,0 +1,203 @@ +#include "stdio.h" +#include "stdlib.h" + +#define BUFFERSIZE 64 + +#define MAXARGSIZE 16 +#define MAXARGS 16 + +/** + * tokenizeCommand + * + * Splits the specified command line into tokens, creating a token array with a maximum + * of maxTokens entries, using storage to hold the tokens. The storage array should be as + * long as the command line. + * + * Whitespace (spaces, tabs, newlines) separate tokens, unless + * enclosed in double quotes. Any character can be quoted by preceeding + * it with a backslash. Quotes must be terminated. + * + * Returns the number of tokens, or -1 on error. + */ +static int tokenizeCommand(char* command, int maxTokens, char *tokens[], char* storage) { + const int quotingCharacter = 0x00000001; + const int quotingString = 0x00000002; + const int startedArg = 0x00000004; + + int state = 0; + int numTokens = 0; + + char c; + + assert(maxTokens > 0); + + while ((c = *(command++)) != '\0') { + if (state & quotingCharacter) { + switch (c) { + case 't': + c = '\t'; + break; + case 'n': + c = '\n'; + break; + } + *(storage++) = c; + state &= ~quotingCharacter; + } + else if (state & quotingString) { + switch (c) { + case '\\': + state |= quotingCharacter; + break; + case '"': + state &= ~quotingString; + break; + default: + *(storage++) = c; + break; + } + } + else { + switch (c) { + case ' ': + case '\t': + case '\n': + if (state & startedArg) { + *(storage++) = '\0'; + state &= ~startedArg; + } + break; + default: + if (!(state & startedArg)) { + if (numTokens == maxTokens) { + return -1; + } + tokens[numTokens++] = storage; + state |= startedArg; + } + + switch (c) { + case '\\': + state |= quotingCharacter; + break; + case '"': + state |= quotingString; + break; + default: + *(storage++) = c; + break; + } + } + } + } + + if (state & quotingCharacter) { + printf("Unmatched \\.\n"); + return -1; + } + + if (state & quotingString) { + printf("Unmatched \".\n"); + return -1; + } + + if (state & startedArg) { + *(storage++) = '\0'; + } + + return numTokens; +} + +void runline(char* line) { + int pid, background, status; + + char args[BUFFERSIZE], prog[BUFFERSIZE]; + char *argv[MAXARGS]; + + int argc = tokenizeCommand(line, MAXARGS, argv, args); + if (argc <= 0) + return; + + if (argc > 0 && strcmp(argv[argc-1], "&") == 0) { + argc--; + background = 1; + } + else { + background = 0; + } + + if (argc > 0) { + if (strcmp(argv[0], "exit")==0) { + if (argc == 1) { + exit(0); + } + else if (argc == 2) { + exit(atoi(argv[1])); + } + else { + printf("exit: Expression Syntax.\n"); + return; + } + } + else if (strcmp(argv[0], "halt")==0) { + if (argc == 1) { + halt(); + printf("Not the root process!\n"); + } + else { + printf("halt: Expression Syntax.\n"); + } + return; + } + else if (strcmp(argv[0], "join")==0) { + if (argc == 2) { + pid = atoi(argv[1]); + } + else { + printf("join: Expression Syntax.\n"); + return; + } + } + else { + strcpy(prog, argv[0]); + strcat(prog, ".coff"); + + pid = exec(prog, argc, argv); + if (pid == -1) { + printf("%s: exec failed.\n", argv[0]); + return; + } + } + + if (!background) { + switch (join(pid, &status)) { + case -1: + printf("join: Invalid process ID.\n"); + break; + case 0: + printf("\n[%d] Unhandled exception\n", pid); + break; + case 1: + printf("\n[%d] Done (%d)\n", pid, status); + break; + } + } + else { + printf("\n[%d]\n", pid); + } + } +} + +int main(int argc, char *argv[]) { + char prompt[] = "nachos% "; + + char buffer[BUFFERSIZE]; + + while (1) { + printf("%s", prompt); + + readline(buffer, BUFFERSIZE); + + runline(buffer); + } +} diff --git a/test/sh.coff b/test/sh.coff new file mode 100644 index 0000000..f766485 Binary files /dev/null and b/test/sh.coff differ diff --git a/test/sh.o b/test/sh.o new file mode 100644 index 0000000..f13c805 Binary files /dev/null and b/test/sh.o differ diff --git a/test/sort.c b/test/sort.c new file mode 100644 index 0000000..8054dab --- /dev/null +++ b/test/sort.c @@ -0,0 +1,51 @@ +/* sort.c + * Test program to sort a large number of integers. + * + * Intention is to stress virtual memory system. To increase the memory + * usage of this program, simply increase SORTSHIFT. The size of the array + * is (SORTSIZE)(2^(SORTSHIFT+2)). + */ + +#include "syscall.h" + +/* size of physical memory; with code, we'll run out of space! */ +#define SORTSIZE 256 +#define SORTSHIFT 0 + +int array[SORTSIZE< A(j)) + swap(&A(i), &A(j)); + } + } + + /* and last, verify */ + for (i=0; i (b)) ? (a) : (b)) + +#define divRoundDown(n,s) ((n) / (s)) +#define divRoundUp(n,s) (((n) / (s)) + ((((n) % (s)) > 0) ? 1 : 0)) + +#define assert(_EX) ((_EX) ? (void) 0 : __assert(__FILE__, __LINE__)) +void __assert(char* file, int line); + +#define assertNotReached() assert(false) + +void *memcpy(void *s1, const void *s2, unsigned int n); +void *memset(void *s, int c, unsigned int n); + +unsigned int strlen(const char *str); +char *strcpy(char *dst, const char *src); +int strcmp(const char *a, const char *b); +int strncmp(const char *a, const char *b, int n); + +int atoi(const char *s); + +#endif // STDLIB_H diff --git a/test/strcat.c b/test/strcat.c new file mode 100644 index 0000000..70a9f90 --- /dev/null +++ b/test/strcat.c @@ -0,0 +1,16 @@ +#include "stdlib.h" + +/* concatenates s2 to the end of s1 and returns s1 */ +char *strcat(char *s1, const char *s2) { + char* result = s1; + + while (*s1 != 0) + s1++; + + do { + *(s1++) = *(s2); + } + while (*(s2++) != 0); + + return result; +} diff --git a/test/strcat.o b/test/strcat.o new file mode 100644 index 0000000..76578cf Binary files /dev/null and b/test/strcat.o differ diff --git a/test/strcmp.c b/test/strcmp.c new file mode 100644 index 0000000..5c3be03 --- /dev/null +++ b/test/strcmp.c @@ -0,0 +1,14 @@ +#include "stdlib.h" + +/* lexicographically compares a and b */ +int strcmp(const char* a, const char* b) { + do { + if (*a < *b) + return -1; + if (*a > *b) + return 1; + } + while (*(a++) != 0 && *(b++) != 0); + + return 0; +} diff --git a/test/strcmp.o b/test/strcmp.o new file mode 100644 index 0000000..fcfe125 Binary files /dev/null and b/test/strcmp.o differ diff --git a/test/strcpy.c b/test/strcpy.c new file mode 100644 index 0000000..3186514 --- /dev/null +++ b/test/strcpy.c @@ -0,0 +1,15 @@ +#include "stdlib.h" + +/* copies src to dst, returning dst */ +char *strcpy(char *dst, const char *src) { + int n=0; + char *result = dst; + + do { + *(dst++) = *src; + n++; + } + while (*(src++) != 0); + + return result; +} diff --git a/test/strcpy.o b/test/strcpy.o new file mode 100644 index 0000000..b05d561 Binary files /dev/null and b/test/strcpy.o differ diff --git a/test/strlen.c b/test/strlen.c new file mode 100644 index 0000000..d0715c5 --- /dev/null +++ b/test/strlen.c @@ -0,0 +1,11 @@ +#include "stdlib.h" + +/* returns the length of the character string str, not including the null-terminator */ +unsigned strlen(const char *str) { + int result=0; + + while (*(str++) != 0) + result++; + + return result; +} diff --git a/test/strlen.o b/test/strlen.o new file mode 100644 index 0000000..50be5d5 Binary files /dev/null and b/test/strlen.o differ diff --git a/test/strncmp.c b/test/strncmp.c new file mode 100644 index 0000000..809ea28 --- /dev/null +++ b/test/strncmp.c @@ -0,0 +1,20 @@ +#include "stdlib.h" + +/* lexicographically compares a and b up to n chars */ +int strncmp(const char* a, const char* b, int n) +{ + assert(n > 0); + + do { + if (*a < *b) + return -1; + if (*a > *b) + return 1; + n--; + a++; + b++; + } + while (n > 0); + + return 0; +} diff --git a/test/strncmp.o b/test/strncmp.o new file mode 100644 index 0000000..59c4154 Binary files /dev/null and b/test/strncmp.o differ diff --git a/test/syscall.h b/test/syscall.h new file mode 100644 index 0000000..e5ac795 --- /dev/null +++ b/test/syscall.h @@ -0,0 +1,262 @@ +/** + * The Nachos system call interface. These are Nachos kernel operations that + * can be invoked from user programs using the syscall instruction. + * + * This interface is derived from the UNIX syscalls. This information is + * largely copied from the UNIX man pages. + */ + +#ifndef SYSCALL_H +#define SYSCALL_H + +/** + * System call codes, passed in $r0 to tell the kernel which system call to do. + */ +#define syscallHalt 0 +#define syscallExit 1 +#define syscallExec 2 +#define syscallJoin 3 +#define syscallCreate 4 +#define syscallOpen 5 +#define syscallRead 6 +#define syscallWrite 7 +#define syscallClose 8 +#define syscallUnlink 9 +#define syscallMmap 10 +#define syscallConnect 11 +#define syscallAccept 12 + +/* Don't want the assembler to see C code, but start.s includes syscall.h. */ +#ifndef START_S + +/* When a process is created, two streams are already open. File descriptor 0 + * refers to keyboard input (UNIX stdin), and file descriptor 1 refers to + * display output (UNIX stdout). File descriptor 0 can be read, and file + * descriptor 1 can be written, without previous calls to open(). + */ +#define fdStandardInput 0 +#define fdStandardOutput 1 + +/* The system call interface. These are the operations the Nachos kernel needs + * to support, to be able to run user programs. + * + * Each of these is invoked by a user program by simply calling the procedure; + * an assembly language stub stores the syscall code (see above) into $r0 and + * executes a syscall instruction. The kernel exception handler is then + * invoked. + */ + +/* Halt the Nachos machine by calling Machine.halt(). Only the root process + * (the first process, executed by UserKernel.run()) should be allowed to + * execute this syscall. Any other process should ignore the syscall and return + * immediately. + */ +void halt(); + +/* PROCESS MANAGEMENT SYSCALLS: exit(), exec(), join() */ + +/** + * Terminate the current process immediately. Any open file descriptors + * belonging to the process are closed. Any children of the process no longer + * have a parent process. + * + * status is returned to the parent process as this process's exit status and + * can be collected using the join syscall. A process exiting normally should + * (but is not required to) set status to 0. + * + * exit() never returns. + */ +void exit(int status); + +/** + * Execute the program stored in the specified file, with the specified + * arguments, in a new child process. The child process has a new unique + * process ID, and starts with stdin opened as file descriptor 0, and stdout + * opened as file descriptor 1. + * + * file is a null-terminated string that specifies the name of the file + * containing the executable. Note that this string must include the ".coff" + * extension. + * + * argc specifies the number of arguments to pass to the child process. This + * number must be non-negative. + * + * argv is an array of pointers to null-terminated strings that represent the + * arguments to pass to the child process. argv[0] points to the first + * argument, and argv[argc-1] points to the last argument. + * + * exec() returns the child process's process ID, which can be passed to + * join(). On error, returns -1. + */ +int exec(char *file, int argc, char *argv[]); + +/** + * Suspend execution of the current process until the child process specified + * by the processID argument has exited. If the child has already exited by the + * time of the call, returns immediately. When the current process resumes, it + * disowns the child process, so that join() cannot be used on that process + * again. + * + * processID is the process ID of the child process, returned by exec(). + * + * status points to an integer where the exit status of the child process will + * be stored. This is the value the child passed to exit(). If the child exited + * because of an unhandled exception, the value stored is not defined. + * + * If the child exited normally, returns 1. If the child exited as a result of + * an unhandled exception, returns 0. If processID does not refer to a child + * process of the current process, returns -1. + */ +int join(int processID, int *status); + +/* FILE MANAGEMENT SYSCALLS: creat, open, read, write, close, unlink + * + * A file descriptor is a small, non-negative integer that refers to a file on + * disk or to a stream (such as console input, console output, and network + * connections). A file descriptor can be passed to read() and write() to + * read/write the corresponding file/stream. A file descriptor can also be + * passed to close() to release the file descriptor and any associated + * resources. + */ + +/** + * Attempt to open the named disk file, creating it if it does not exist, + * and return a file descriptor that can be used to access the file. + * + * Note that creat() can only be used to create files on disk; creat() will + * never return a file descriptor referring to a stream. + * + * Returns the new file descriptor, or -1 if an error occurred. + */ +int creat(char *name); + +/** + * Attempt to open the named file and return a file descriptor. + * + * Note that open() can only be used to open files on disk; open() will never + * return a file descriptor referring to a stream. + * + * Returns the new file descriptor, or -1 if an error occurred. + */ +int open(char *name); + +/** + * Attempt to read up to count bytes into buffer from the file or stream + * referred to by fileDescriptor. + * + * On success, the number of bytes read is returned. If the file descriptor + * refers to a file on disk, the file position is advanced by this number. + * + * It is not necessarily an error if this number is smaller than the number of + * bytes requested. If the file descriptor refers to a file on disk, this + * indicates that the end of the file has been reached. If the file descriptor + * refers to a stream, this indicates that the fewer bytes are actually + * available right now than were requested, but more bytes may become available + * in the future. Note that read() never waits for a stream to have more data; + * it always returns as much as possible immediately. + * + * On error, -1 is returned, and the new file position is undefined. This can + * happen if fileDescriptor is invalid, if part of the buffer is read-only or + * invalid, or if a network stream has been terminated by the remote host and + * no more data is available. + */ +int read(int fileDescriptor, void *buffer, int count); + +/** + * Attempt to write up to count bytes from buffer to the file or stream + * referred to by fileDescriptor. write() can return before the bytes are + * actually flushed to the file or stream. A write to a stream can block, + * however, if kernel queues are temporarily full. + * + * On success, the number of bytes written is returned (zero indicates nothing + * was written), and the file position is advanced by this number. It IS an + * error if this number is smaller than the number of bytes requested. For + * disk files, this indicates that the disk is full. For streams, this + * indicates the stream was terminated by the remote host before all the data + * was transferred. + * + * On error, -1 is returned, and the new file position is undefined. This can + * happen if fileDescriptor is invalid, if part of the buffer is invalid, or + * if a network stream has already been terminated by the remote host. + */ +int write(int fileDescriptor, void *buffer, int count); + +/** + * Close a file descriptor, so that it no longer refers to any file or stream + * and may be reused. + * + * If the file descriptor refers to a file, all data written to it by write() + * will be flushed to disk before close() returns. + * If the file descriptor refers to a stream, all data written to it by write() + * will eventually be flushed (unless the stream is terminated remotely), but + * not necessarily before close() returns. + * + * The resources associated with the file descriptor are released. If the + * descriptor is the last reference to a disk file which has been removed using + * unlink, the file is deleted (this detail is handled by the file system + * implementation). + * + * Returns 0 on success, or -1 if an error occurred. + */ +int close(int fileDescriptor); + +/** + * Delete a file from the file system. If no processes have the file open, the + * file is deleted immediately and the space it was using is made available for + * reuse. + * + * If any processes still have the file open, the file will remain in existence + * until the last file descriptor referring to it is closed. However, creat() + * and open() will not be able to return new file descriptors for the file + * until it is deleted. + * + * Returns 0 on success, or -1 if an error occurred. + */ +int unlink(char *name); + +/** + * Map the file referenced by fileDescriptor into memory at address. The file + * may be as large as 0x7FFFFFFF bytes. + * + * To maintain consistency, further calls to read() and write() on this file + * descriptor will fail (returning -1) until the file descriptor is closed. + * + * When the file descriptor is closed, all remaining dirty pages of the map + * will be flushed to disk and the map will be removed. + * + * Returns the length of the file on success, or -1 if an error occurred. + */ +int mmap(int fileDescriptor, char *address); + +/** + * Attempt to initiate a new connection to the specified port on the specified + * remote host, and return a new file descriptor referring to the connection. + * connect() does not give up if the remote host does not respond immediately. + * + * Returns the new file descriptor, or -1 if an error occurred. + */ +int connect(int host, int port); + +/** + * Attempt to accept a single connection on the specified local port and return + * a file descriptor referring to the connection. + * + * If any connection requests are pending on the port, one request is dequeued + * and an acknowledgement is sent to the remote host (so that its connect() + * call can return). Since the remote host will never cancel a connection + * request, there is no need for accept() to wait for the remote host to + * confirm the connection (i.e. a 2-way handshake is sufficient; TCP's 3-way + * handshake is unnecessary). + * + * If no connection requests are pending, returns -1 immediately. + * + * In either case, accept() returns without waiting for a remote host. + * + * Returns a new file descriptor referring to the connection, or -1 if an error + * occurred. + */ +int accept(int port); + +#endif /* START_S */ + +#endif /* SYSCALL_H */ diff --git a/test/va-mips.h b/test/va-mips.h new file mode 100644 index 0000000..9a4b6ef --- /dev/null +++ b/test/va-mips.h @@ -0,0 +1,276 @@ +/* ---------------------------------------- */ +/* VARARGS for MIPS/GNU CC */ +/* */ +/* */ +/* */ +/* */ +/* ---------------------------------------- */ + + +/* These macros implement varargs for GNU C--either traditional or ANSI. */ + +/* Define __gnuc_va_list. */ + +#ifndef __GNUC_VA_LIST +#define __GNUC_VA_LIST +#if defined (__mips_eabi) && ! defined (__mips_soft_float) && ! defined (__mips_single_float) + +typedef struct { + /* Pointer to FP regs. */ + char *__fp_regs; + /* Number of FP regs remaining. */ + int __fp_left; + /* Pointer to GP regs followed by stack parameters. */ + char *__gp_regs; +} __gnuc_va_list; + +#else /* ! (defined (__mips_eabi) && ! defined (__mips_soft_float) && ! defined (__mips_single_float)) */ + +typedef char * __gnuc_va_list; + +#endif /* ! (defined (__mips_eabi) && ! defined (__mips_soft_float) && ! defined (__mips_single_float)) */ +#endif /* not __GNUC_VA_LIST */ + +/* If this is for internal libc use, don't define anything but + __gnuc_va_list. */ +#if defined (_STDARG_H) || defined (_VARARGS_H) + +#ifndef _VA_MIPS_H_ENUM +#define _VA_MIPS_H_ENUM +enum { + __no_type_class = -1, + __void_type_class, + __integer_type_class, + __char_type_class, + __enumeral_type_class, + __boolean_type_class, + __pointer_type_class, + __reference_type_class, + __offset_type_class, + __real_type_class, + __complex_type_class, + __function_type_class, + __method_type_class, + __record_type_class, + __union_type_class, + __array_type_class, + __string_type_class, + __set_type_class, + __file_type_class, + __lang_type_class +}; +#endif + +/* In GCC version 2, we want an ellipsis at the end of the declaration + of the argument list. GCC version 1 can't parse it. */ + +#if __GNUC__ > 1 +#define __va_ellipsis ... +#else +#define __va_ellipsis +#endif + +#ifdef __mips64 +#define __va_rounded_size(__TYPE) \ + (((sizeof (__TYPE) + 8 - 1) / 8) * 8) +#else +#define __va_rounded_size(__TYPE) \ + (((sizeof (__TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int)) +#endif + +#ifdef __mips64 +#define __va_reg_size 8 +#else +#define __va_reg_size 4 +#endif + +/* Get definitions for _MIPS_SIM_ABI64 etc. */ +#ifdef _MIPS_SIM +//#include +#endif + +#ifdef _STDARG_H +#if defined (__mips_eabi) +#if ! defined (__mips_soft_float) && ! defined (__mips_single_float) +#ifdef __mips64 +#define va_start(__AP, __LASTARG) \ + (__AP.__gp_regs = ((char *) __builtin_next_arg (__LASTARG) \ + - (__builtin_args_info (2) < 8 \ + ? (8 - __builtin_args_info (2)) * __va_reg_size \ + : 0)), \ + __AP.__fp_left = 8 - __builtin_args_info (3), \ + __AP.__fp_regs = __AP.__gp_regs - __AP.__fp_left * __va_reg_size) +#else /* ! defined (__mips64) */ +#define va_start(__AP, __LASTARG) \ + (__AP.__gp_regs = ((char *) __builtin_next_arg (__LASTARG) \ + - (__builtin_args_info (2) < 8 \ + ? (8 - __builtin_args_info (2)) * __va_reg_size \ + : 0)), \ + __AP.__fp_left = (8 - __builtin_args_info (3)) / 2, \ + __AP.__fp_regs = __AP.__gp_regs - __AP.__fp_left * 8, \ + __AP.__fp_regs = (char *) ((int) __AP.__fp_regs & -8)) +#endif /* ! defined (__mips64) */ +#else /* ! (! defined (__mips_soft_float) && ! defined (__mips_single_float) ) */ +#define va_start(__AP, __LASTARG) \ + (__AP = ((__gnuc_va_list) __builtin_next_arg (__LASTARG) \ + - (__builtin_args_info (2) >= 8 ? 0 \ + : (8 - __builtin_args_info (2)) * __va_reg_size))) +#endif /* ! (! defined (__mips_soft_float) && ! defined (__mips_single_float) ) */ +#else /* ! defined (__mips_eabi) */ +#define va_start(__AP, __LASTARG) \ + (__AP = (__gnuc_va_list) __builtin_next_arg (__LASTARG)) +#endif /* ! (defined (__mips_eabi) && ! defined (__mips_soft_float) && ! defined (__mips_single_float)) */ +#else /* ! _STDARG_H */ +#define va_alist __builtin_va_alist +#ifdef __mips64 +/* This assumes that `long long int' is always a 64 bit type. */ +#define va_dcl long long int __builtin_va_alist; __va_ellipsis +#else +#define va_dcl int __builtin_va_alist; __va_ellipsis +#endif +#if defined (__mips_eabi) +#if ! defined (__mips_soft_float) && ! defined (__mips_single_float) +#ifdef __mips64 +#define va_start(__AP) \ + (__AP.__gp_regs = ((char *) __builtin_next_arg () \ + - (__builtin_args_info (2) < 8 \ + ? (8 - __builtin_args_info (2)) * __va_reg_size \ + : __va_reg_size)), \ + __AP.__fp_left = 8 - __builtin_args_info (3), \ + __AP.__fp_regs = __AP.__gp_regs - __AP.__fp_left * __va_reg_size) +#else /* ! defined (__mips64) */ +#define va_start(__AP) \ + (__AP.__gp_regs = ((char *) __builtin_next_arg () \ + - (__builtin_args_info (2) < 8 \ + ? (8 - __builtin_args_info (2)) * __va_reg_size \ + : __va_reg_size)), \ + __AP.__fp_left = (8 - __builtin_args_info (3)) / 2, \ + __AP.__fp_regs = __AP.__gp_regs - __AP.__fp_left * 8, \ + __AP.__fp_regs = (char *) ((int) __AP.__fp_regs & -8)) +#endif /* ! defined (__mips64) */ +#else /* ! (! defined (__mips_soft_float) && ! defined (__mips_single_float)) */ +#define va_start(__AP) \ + (__AP = ((__gnuc_va_list) __builtin_next_arg () \ + - (__builtin_args_info (2) >= 8 ? __va_reg_size \ + : (8 - __builtin_args_info (2)) * __va_reg_size))) +#endif /* ! (! defined (__mips_soft_float) && ! defined (__mips_single_float)) */ +/* Need alternate code for _MIPS_SIM_ABI64. */ +#elif defined(_MIPS_SIM) && (_MIPS_SIM == _MIPS_SIM_ABI64 || _MIPS_SIM == _MIPS_SIM_NABI32) +#define va_start(__AP) \ + (__AP = (__gnuc_va_list) __builtin_next_arg () \ + + (__builtin_args_info (2) >= 8 ? -8 : 0)) +#else +#define va_start(__AP) __AP = (char *) &__builtin_va_alist +#endif +#endif /* ! _STDARG_H */ + +#ifndef va_end +void va_end (__gnuc_va_list); /* Defined in libgcc.a */ +#endif +#define va_end(__AP) ((void)0) + +#if defined (__mips_eabi) + +#if ! defined (__mips_soft_float) && ! defined (__mips_single_float) +#ifdef __mips64 +#define __va_next_addr(__AP, __type) \ + ((__builtin_classify_type (*(__type *) 0) == __real_type_class \ + && __AP.__fp_left > 0) \ + ? (--__AP.__fp_left, (__AP.__fp_regs += 8) - 8) \ + : (__AP.__gp_regs += __va_reg_size) - __va_reg_size) +#else +#define __va_next_addr(__AP, __type) \ + ((__builtin_classify_type (*(__type *) 0) == __real_type_class \ + && __AP.__fp_left > 0) \ + ? (--__AP.__fp_left, (__AP.__fp_regs += 8) - 8) \ + : (((__builtin_classify_type (* (__type *) 0) < __record_type_class \ + && __alignof__ (__type) > 4) \ + ? __AP.__gp_regs = (char *) (((int) __AP.__gp_regs + 8 - 1) & -8) \ + : (char *) 0), \ + (__builtin_classify_type (* (__type *) 0) >= __record_type_class \ + ? (__AP.__gp_regs += __va_reg_size) - __va_reg_size \ + : ((__AP.__gp_regs += __va_rounded_size (__type)) \ + - __va_rounded_size (__type))))) +#endif +#else /* ! (! defined (__mips_soft_float) && ! defined (__mips_single_float)) */ +#ifdef __mips64 +#define __va_next_addr(__AP, __type) \ + ((__AP += __va_reg_size) - __va_reg_size) +#else +#define __va_next_addr(__AP, __type) \ + (((__builtin_classify_type (* (__type *) 0) < __record_type_class \ + && __alignof__ (__type) > 4) \ + ? __AP = (char *) (((int) __AP + 8 - 1) & -8) \ + : (char *) 0), \ + (__builtin_classify_type (* (__type *) 0) >= __record_type_class \ + ? (__AP += __va_reg_size) - __va_reg_size \ + : ((__AP += __va_rounded_size (__type)) \ + - __va_rounded_size (__type)))) +#endif +#endif /* ! (! defined (__mips_soft_float) && ! defined (__mips_single_float)) */ + +#ifdef __MIPSEB__ +#define va_arg(__AP, __type) \ + ((__va_rounded_size (__type) <= __va_reg_size) \ + ? *(__type *) (void *) (__va_next_addr (__AP, __type) \ + + __va_reg_size \ + - sizeof (__type)) \ + : (__builtin_classify_type (*(__type *) 0) >= __record_type_class \ + ? **(__type **) (void *) (__va_next_addr (__AP, __type) \ + + __va_reg_size \ + - sizeof (char *)) \ + : *(__type *) (void *) __va_next_addr (__AP, __type))) +#else +#define va_arg(__AP, __type) \ + ((__va_rounded_size (__type) <= __va_reg_size) \ + ? *(__type *) (void *) __va_next_addr (__AP, __type) \ + : (__builtin_classify_type (* (__type *) 0) >= __record_type_class \ + ? **(__type **) (void *) __va_next_addr (__AP, __type) \ + : *(__type *) (void *) __va_next_addr (__AP, __type))) +#endif + +#else /* ! defined (__mips_eabi) */ + +/* We cast to void * and then to TYPE * because this avoids + a warning about increasing the alignment requirement. */ +/* The __mips64 cases are reversed from the 32 bit cases, because the standard + 32 bit calling convention left-aligns all parameters smaller than a word, + whereas the __mips64 calling convention does not (and hence they are + right aligned). */ +#ifdef __mips64 +#ifdef __MIPSEB__ +#define va_arg(__AP, __type) \ + ((__type *) (void *) (__AP = (char *) ((((__PTRDIFF_TYPE__)__AP + 8 - 1) & -8) \ + + __va_rounded_size (__type))))[-1] +#else +#define va_arg(__AP, __type) \ + ((__AP = (char *) ((((__PTRDIFF_TYPE__)__AP + 8 - 1) & -8) \ + + __va_rounded_size (__type))), \ + *(__type *) (void *) (__AP - __va_rounded_size (__type))) +#endif + +#else /* not __mips64 */ + +#ifdef __MIPSEB__ +/* For big-endian machines. */ +#define va_arg(__AP, __type) \ + ((__AP = (char *) ((__alignof__ (__type) > 4 \ + ? ((int)__AP + 8 - 1) & -8 \ + : ((int)__AP + 4 - 1) & -4) \ + + __va_rounded_size (__type))), \ + *(__type *) (void *) (__AP - __va_rounded_size (__type))) +#else +/* For little-endian machines. */ +#define va_arg(__AP, __type) \ + ((__type *) (void *) (__AP = (char *) ((__alignof__(__type) > 4 \ + ? ((int)__AP + 8 - 1) & -8 \ + : ((int)__AP + 4 - 1) & -4) \ + + __va_rounded_size(__type))))[-1] +#endif +#endif +#endif /* ! defined (__mips_eabi) */ + +/* Copy __gnuc_va_list into another variable of this type. */ +#define __va_copy(dest, src) (dest) = (src) + +#endif /* defined (_STDARG_H) || defined (_VARARGS_H) */ diff --git a/threads/Alarm.java b/threads/Alarm.java new file mode 100644 index 0000000..07b11b0 --- /dev/null +++ b/threads/Alarm.java @@ -0,0 +1,53 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * Uses the hardware timer to provide preemption, and to allow threads to sleep + * until a certain time. + */ +public class Alarm { + /** + * Allocate a new Alarm. Set the machine's timer interrupt handler to this + * alarm's callback. + * + *

Note: Nachos will not function correctly with more than one + * alarm. + */ + public Alarm() { + Machine.timer().setInterruptHandler(new Runnable() { + public void run() { timerInterrupt(); } + }); + } + + /** + * The timer interrupt handler. This is called by the machine's timer + * periodically (approximately every 500 clock ticks). Causes the current + * thread to yield, forcing a context switch if there is another thread + * that should be run. + */ + public void timerInterrupt() { + KThread.currentThread().yield(); + } + + /** + * Put the current thread to sleep for at least x ticks, + * waking it up in the timer interrupt handler. The thread must be + * woken up (placed in the scheduler ready set) during the first timer + * interrupt where + * + *

+ * (current time) >= (WaitUntil called time)+(x) + *
+ * + * @param x the minimum number of clock ticks to wait. + * + * @see nachos.machine.Timer#getTime() + */ + public void waitUntil(long x) { + // for now, cheat just to get something working (busy waiting is bad) + long wakeTime = Machine.timer().getTime() + x; + while (wakeTime > Machine.timer().getTime()) + KThread.yield(); + } +} diff --git a/threads/Boat.java b/threads/Boat.java new file mode 100644 index 0000000..cdf7d69 --- /dev/null +++ b/threads/Boat.java @@ -0,0 +1,76 @@ +package nachos.threads; +import nachos.ag.BoatGrader; + +public class Boat +{ + static BoatGrader bg; + + public static void selfTest() + { + BoatGrader b = new BoatGrader(); + + System.out.println("\n ***Testing Boats with only 2 children***"); + begin(0, 2, b); + +// System.out.println("\n ***Testing Boats with 2 children, 1 adult***"); +// begin(1, 2, b); + +// System.out.println("\n ***Testing Boats with 3 children, 3 adults***"); +// begin(3, 3, b); + } + + public static void begin( int adults, int children, BoatGrader b ) + { + // Store the externally generated autograder in a class + // variable to be accessible by children. + bg = b; + + // Instantiate global variables here + + // Create threads here. See section 3.4 of the Nachos for Java + // Walkthrough linked from the projects page. + + Runnable r = new Runnable() { + public void run() { + SampleItinerary(); + } + }; + KThread t = new KThread(r); + t.setName("Sample Boat Thread"); + t.fork(); + + } + + static void AdultItinerary() + { + bg.initializeAdult(); //Required for autograder interface. Must be the first thing called. + //DO NOT PUT ANYTHING ABOVE THIS LINE. + + /* This is where you should put your solutions. Make calls + to the BoatGrader to show that it is synchronized. For + example: + bg.AdultRowToMolokai(); + indicates that an adult has rowed the boat across to Molokai + */ + } + + static void ChildItinerary() + { + bg.initializeChild(); //Required for autograder interface. Must be the first thing called. + //DO NOT PUT ANYTHING ABOVE THIS LINE. + } + + static void SampleItinerary() + { + // Please note that this isn't a valid solution (you can't fit + // all of them on the boat). Please also note that you may not + // have a single thread calculate a solution and then just play + // it back at the autograder -- you will be caught. + System.out.println("\n ***Everyone piles on the boat and goes to Molokai***"); + bg.AdultRowToMolokai(); + bg.ChildRideToMolokai(); + bg.AdultRideToMolokai(); + bg.ChildRideToMolokai(); + } + +} diff --git a/threads/Communicator.java b/threads/Communicator.java new file mode 100644 index 0000000..47c0a6b --- /dev/null +++ b/threads/Communicator.java @@ -0,0 +1,41 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * A communicator allows threads to synchronously exchange 32-bit + * messages. Multiple threads can be waiting to speak, + * and multiple threads can be waiting to listen. But there should never + * be a time when both a speaker and a listener are waiting, because the two + * threads can be paired off at this point. + */ +public class Communicator { + /** + * Allocate a new communicator. + */ + public Communicator() { + } + + /** + * Wait for a thread to listen through this communicator, and then transfer + * word to the listener. + * + *

+ * Does not return until this thread is paired up with a listening thread. + * Exactly one listener should receive word. + * + * @param word the integer to transfer. + */ + public void speak(int word) { + } + + /** + * Wait for a thread to speak through this communicator, and then return + * the word that thread passed to speak(). + * + * @return the integer transferred. + */ + public int listen() { + return 0; + } +} diff --git a/threads/Condition.java b/threads/Condition.java new file mode 100644 index 0000000..40ccfc4 --- /dev/null +++ b/threads/Condition.java @@ -0,0 +1,114 @@ +package nachos.threads; + +import nachos.machine.*; + +import java.util.LinkedList; + +/** + * An implementation of condition variables built upon semaphores. + * + *

+ * A condition variable is a synchronization primitive that does not have + * a value (unlike a semaphore or a lock), but threads may still be queued. + * + *

    + * + *
  • sleep(): atomically release the lock and relinkquish the CPU + * until woken; then reacquire the lock. + * + *
  • wake(): wake up a single thread sleeping in this condition + * variable, if possible. + * + *
  • wakeAll(): wake up all threads sleeping inn this condition + * variable. + * + *
+ * + *

+ * Every condition variable is associated with some lock. Multiple condition + * variables may be associated with the same lock. All three condition variable + * operations can only be used while holding the associated lock. + * + *

+ * In Nachos, condition variables are summed to obey Mesa-style + * semantics. When a wake() or wakeAll() wakes up another + * thread, the woken thread is simply put on the ready list, and it is the + * responsibility of the woken thread to reacquire the lock (this reacquire is + * taken core of in sleep()). + * + *

+ * By contrast, some implementations of condition variables obey + * Hoare-style semantics, where the thread that calls wake() + * gives up the lock and the CPU to the woken thread, which runs immediately + * and gives the lock and CPU back to the waker when the woken thread exits the + * critical section. + * + *

+ * The consequence of using Mesa-style semantics is that some other thread + * can acquire the lock and change data structures, before the woken thread + * gets a chance to run. The advance to Mesa-style semantics is that it is a + * lot easier to implement. + */ +public class Condition { + /** + * Allocate a new condition variable. + * + * @param conditionLock the lock associated with this condition + * variable. The current thread must hold this + * lock whenever it uses sleep(), + * wake(), or wakeAll(). + */ + public Condition(Lock conditionLock) { + this.conditionLock = conditionLock; + + waitQueue = new LinkedList(); + } + + /** + * Atomically release the associated lock and go to sleep on this condition + * variable until another thread wakes it using wake(). The + * current thread must hold the associated lock. The thread will + * automatically reacquire the lock before sleep() returns. + * + *

+ * This implementation uses semaphores to implement this, by allocating a + * semaphore for each waiting thread. The waker will V() this + * semaphore, so thre is no chance the sleeper will miss the wake-up, even + * though the lock is released before caling P(). + */ + public void sleep() { + Lib.assertTrue(conditionLock.isHeldByCurrentThread()); + + Semaphore waiter = new Semaphore(0); + waitQueue.add(waiter); + + conditionLock.release(); + waiter.P(); + conditionLock.acquire(); + } + + /** + * Wake up at most one thread sleeping on this condition variable. The + * current thread must hold the associated lock. + */ + public void wake() { + Lib.assertTrue(conditionLock.isHeldByCurrentThread()); + + if (!waitQueue.isEmpty()) + ((Semaphore) waitQueue.removeFirst()).V(); + } + + /** + * Wake up all threads sleeping on this condition variable. The current + * thread must hold the associated lock. + */ + public void wakeAll() { + Lib.assertTrue(conditionLock.isHeldByCurrentThread()); + + while (!waitQueue.isEmpty()) + wake(); + } + + private Lock conditionLock; + private LinkedList waitQueue; +} diff --git a/threads/Condition2.java b/threads/Condition2.java new file mode 100644 index 0000000..a17d98b --- /dev/null +++ b/threads/Condition2.java @@ -0,0 +1,58 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * An implementation of condition variables that disables interrupt()s for + * synchronization. + * + *

+ * You must implement this. + * + * @see nachos.threads.Condition + */ +public class Condition2 { + /** + * Allocate a new condition variable. + * + * @param conditionLock the lock associated with this condition + * variable. The current thread must hold this + * lock whenever it uses sleep(), + * wake(), or wakeAll(). + */ + public Condition2(Lock conditionLock) { + this.conditionLock = conditionLock; + } + + /** + * Atomically release the associated lock and go to sleep on this condition + * variable until another thread wakes it using wake(). The + * current thread must hold the associated lock. The thread will + * automatically reacquire the lock before sleep() returns. + */ + public void sleep() { + Lib.assertTrue(conditionLock.isHeldByCurrentThread()); + + conditionLock.release(); + + conditionLock.acquire(); + } + + /** + * Wake up at most one thread sleeping on this condition variable. The + * current thread must hold the associated lock. + */ + public void wake() { + Lib.assertTrue(conditionLock.isHeldByCurrentThread()); + } + + /** + * Wake up all threads sleeping on this condition variable. The current + * thread must hold the associated lock. + */ + public void wakeAll() { + Lib.assertTrue(conditionLock.isHeldByCurrentThread()); + } + + private Lock conditionLock; +} diff --git a/threads/ElevatorController.java b/threads/ElevatorController.java new file mode 100644 index 0000000..6758d11 --- /dev/null +++ b/threads/ElevatorController.java @@ -0,0 +1,38 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * A controller for all the elevators in an elevator bank. The controller + * accesses the elevator bank through an instance of ElevatorControls. + */ +public class ElevatorController implements ElevatorControllerInterface { + /** + * Allocate a new elevator controller. + */ + public ElevatorController() { + } + + /** + * Initialize this elevator controller. The controller will access the + * elevator bank through controls. This constructor should return + * immediately after this controller is initialized, but not until the + * interupt handler is set. The controller will start receiving events + * after this method returns, but potentially before run() is + * called. + * + * @param controls the controller's interface to the elevator + * bank. The controler must not attempt to access + * the elevator bank in any other way. + */ + public void initialize(ElevatorControls controls) { + } + + /** + * Cause the controller to use the provided controls to receive and process + * requests from riders. This method should not return, but instead should + * call controls.finish() when the controller is finished. + */ + public void run() { + } +} diff --git a/threads/KThread.java b/threads/KThread.java new file mode 100644 index 0000000..7bb06c8 --- /dev/null +++ b/threads/KThread.java @@ -0,0 +1,447 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * A KThread is a thread that can be used to execute Nachos kernel code. Nachos + * allows multiple threads to run concurrently. + * + * To create a new thread of execution, first declare a class that implements + * the Runnable interface. That class then implements the run + * method. An instance of the class can then be allocated, passed as an + * argument when creating KThread, and forked. For example, a thread + * that computes pi could be written as follows: + * + *

+ * class PiRun implements Runnable {
+ *     public void run() {
+ *         // compute pi
+ *         ...
+ *     }
+ * }
+ * 
+ *

The following code would then create a thread and start it running: + * + *

+ * PiRun p = new PiRun();
+ * new KThread(p).fork();
+ * 
+ */ +public class KThread { + /** + * Get the current thread. + * + * @return the current thread. + */ + public static KThread currentThread() { + Lib.assertTrue(currentThread != null); + return currentThread; + } + + /** + * Allocate a new KThread. If this is the first KThread, + * create an idle thread as well. + */ + public KThread() { + if (currentThread != null) { + tcb = new TCB(); + } + else { + readyQueue = ThreadedKernel.scheduler.newThreadQueue(false); + readyQueue.acquire(this); + + currentThread = this; + tcb = TCB.currentTCB(); + name = "main"; + restoreState(); + + createIdleThread(); + } + } + + /** + * Allocate a new KThread. + * + * @param target the object whose run method is called. + */ + public KThread(Runnable target) { + this(); + this.target = target; + } + + /** + * Set the target of this thread. + * + * @param target the object whose run method is called. + * @return this thread. + */ + public KThread setTarget(Runnable target) { + Lib.assertTrue(status == statusNew); + + this.target = target; + return this; + } + + /** + * Set the name of this thread. This name is used for debugging purposes + * only. + * + * @param name the name to give to this thread. + * @return this thread. + */ + public KThread setName(String name) { + this.name = name; + return this; + } + + /** + * Get the name of this thread. This name is used for debugging purposes + * only. + * + * @return the name given to this thread. + */ + public String getName() { + return name; + } + + /** + * Get the full name of this thread. This includes its name along with its + * numerical ID. This name is used for debugging purposes only. + * + * @return the full name given to this thread. + */ + public String toString() { + return (name + " (#" + id + ")"); + } + + /** + * Deterministically and consistently compare this thread to another + * thread. + */ + public int compareTo(Object o) { + KThread thread = (KThread) o; + + if (id < thread.id) + return -1; + else if (id > thread.id) + return 1; + else + return 0; + } + + /** + * Causes this thread to begin execution. The result is that two threads + * are running concurrently: the current thread (which returns from the + * call to the fork method) and the other thread (which executes + * its target's run method). + */ + public void fork() { + Lib.assertTrue(status == statusNew); + Lib.assertTrue(target != null); + + Lib.debug(dbgThread, + "Forking thread: " + toString() + " Runnable: " + target); + + boolean intStatus = Machine.interrupt().disable(); + + tcb.start(new Runnable() { + public void run() { + runThread(); + } + }); + + ready(); + + Machine.interrupt().restore(intStatus); + } + + private void runThread() { + begin(); + target.run(); + finish(); + } + + private void begin() { + Lib.debug(dbgThread, "Beginning thread: " + toString()); + + Lib.assertTrue(this == currentThread); + + restoreState(); + + Machine.interrupt().enable(); + } + + /** + * Finish the current thread and schedule it to be destroyed when it is + * safe to do so. This method is automatically called when a thread's + * run method returns, but it may also be called directly. + * + * The current thread cannot be immediately destroyed because its stack and + * other execution state are still in use. Instead, this thread will be + * destroyed automatically by the next thread to run, when it is safe to + * delete this thread. + */ + public static void finish() { + Lib.debug(dbgThread, "Finishing thread: " + currentThread.toString()); + + Machine.interrupt().disable(); + + Machine.autoGrader().finishingCurrentThread(); + + Lib.assertTrue(toBeDestroyed == null); + toBeDestroyed = currentThread; + + + currentThread.status = statusFinished; + + sleep(); + } + + /** + * Relinquish the CPU if any other thread is ready to run. If so, put the + * current thread on the ready queue, so that it will eventually be + * rescheuled. + * + *

+ * Returns immediately if no other thread is ready to run. Otherwise + * returns when the current thread is chosen to run again by + * readyQueue.nextThread(). + * + *

+ * Interrupts are disabled, so that the current thread can atomically add + * itself to the ready queue and switch to the next thread. On return, + * restores interrupts to the previous state, in case yield() was + * called with interrupts disabled. + */ + public static void yield() { + Lib.debug(dbgThread, "Yielding thread: " + currentThread.toString()); + + Lib.assertTrue(currentThread.status == statusRunning); + + boolean intStatus = Machine.interrupt().disable(); + + currentThread.ready(); + + runNextThread(); + + Machine.interrupt().restore(intStatus); + } + + /** + * Relinquish the CPU, because the current thread has either finished or it + * is blocked. This thread must be the current thread. + * + *

+ * If the current thread is blocked (on a synchronization primitive, i.e. + * a Semaphore, Lock, or Condition), eventually + * some thread will wake this thread up, putting it back on the ready queue + * so that it can be rescheduled. Otherwise, finish() should have + * scheduled this thread to be destroyed by the next thread to run. + */ + public static void sleep() { + Lib.debug(dbgThread, "Sleeping thread: " + currentThread.toString()); + + Lib.assertTrue(Machine.interrupt().disabled()); + + if (currentThread.status != statusFinished) + currentThread.status = statusBlocked; + + runNextThread(); + } + + /** + * Moves this thread to the ready state and adds this to the scheduler's + * ready queue. + */ + public void ready() { + Lib.debug(dbgThread, "Ready thread: " + toString()); + + Lib.assertTrue(Machine.interrupt().disabled()); + Lib.assertTrue(status != statusReady); + + status = statusReady; + if (this != idleThread) + readyQueue.waitForAccess(this); + + Machine.autoGrader().readyThread(this); + } + + /** + * Waits for this thread to finish. If this thread is already finished, + * return immediately. This method must only be called once; the second + * call is not guaranteed to return. This thread must not be the current + * thread. + */ + public void join() { + Lib.debug(dbgThread, "Joining to thread: " + toString()); + + Lib.assertTrue(this != currentThread); + + } + + /** + * Create the idle thread. Whenever there are no threads ready to be run, + * and runNextThread() is called, it will run the idle thread. The + * idle thread must never block, and it will only be allowed to run when + * all other threads are blocked. + * + *

+ * Note that ready() never adds the idle thread to the ready set. + */ + private static void createIdleThread() { + Lib.assertTrue(idleThread == null); + + idleThread = new KThread(new Runnable() { + public void run() { while (true) yield(); } + }); + idleThread.setName("idle"); + + Machine.autoGrader().setIdleThread(idleThread); + + idleThread.fork(); + } + + /** + * Determine the next thread to run, then dispatch the CPU to the thread + * using run(). + */ + private static void runNextThread() { + KThread nextThread = readyQueue.nextThread(); + if (nextThread == null) + nextThread = idleThread; + + nextThread.run(); + } + + /** + * Dispatch the CPU to this thread. Save the state of the current thread, + * switch to the new thread by calling TCB.contextSwitch(), and + * load the state of the new thread. The new thread becomes the current + * thread. + * + *

+ * If the new thread and the old thread are the same, this method must + * still call saveState(), contextSwitch(), and + * restoreState(). + * + *

+ * The state of the previously running thread must already have been + * changed from running to blocked or ready (depending on whether the + * thread is sleeping or yielding). + * + * @param finishing true if the current thread is + * finished, and should be destroyed by the new + * thread. + */ + private void run() { + Lib.assertTrue(Machine.interrupt().disabled()); + + Machine.yield(); + + currentThread.saveState(); + + Lib.debug(dbgThread, "Switching from: " + currentThread.toString() + + " to: " + toString()); + + currentThread = this; + + tcb.contextSwitch(); + + currentThread.restoreState(); + } + + /** + * Prepare this thread to be run. Set status to + * statusRunning and check toBeDestroyed. + */ + protected void restoreState() { + Lib.debug(dbgThread, "Running thread: " + currentThread.toString()); + + Lib.assertTrue(Machine.interrupt().disabled()); + Lib.assertTrue(this == currentThread); + Lib.assertTrue(tcb == TCB.currentTCB()); + + Machine.autoGrader().runningThread(this); + + status = statusRunning; + + if (toBeDestroyed != null) { + toBeDestroyed.tcb.destroy(); + toBeDestroyed.tcb = null; + toBeDestroyed = null; + } + } + + /** + * Prepare this thread to give up the processor. Kernel threads do not + * need to do anything here. + */ + protected void saveState() { + Lib.assertTrue(Machine.interrupt().disabled()); + Lib.assertTrue(this == currentThread); + } + + private static class PingTest implements Runnable { + PingTest(int which) { + this.which = which; + } + + public void run() { + for (int i=0; i<5; i++) { + System.out.println("*** thread " + which + " looped " + + i + " times"); + currentThread.yield(); + } + } + + private int which; + } + + /** + * Tests whether this module is working. + */ + public static void selfTest() { + Lib.debug(dbgThread, "Enter KThread.selfTest"); + + new KThread(new PingTest(1)).setName("forked thread").fork(); + new PingTest(0).run(); + } + + private static final char dbgThread = 't'; + + /** + * Additional state used by schedulers. + * + * @see nachos.threads.PriorityScheduler.ThreadState + */ + public Object schedulingState = null; + + private static final int statusNew = 0; + private static final int statusReady = 1; + private static final int statusRunning = 2; + private static final int statusBlocked = 3; + private static final int statusFinished = 4; + + /** + * The status of this thread. A thread can either be new (not yet forked), + * ready (on the ready queue but not running), running, or blocked (not + * on the ready queue and not running). + */ + private int status = statusNew; + private String name = "(unnamed thread)"; + private Runnable target; + private TCB tcb; + + /** + * Unique identifer for this thread. Used to deterministically compare + * threads. + */ + private int id = numCreated++; + /** Number of times the KThread constructor was called. */ + private static int numCreated = 0; + + private static ThreadQueue readyQueue = null; + private static KThread currentThread = null; + private static KThread toBeDestroyed = null; + private static KThread idleThread = null; +} diff --git a/threads/Lock.java b/threads/Lock.java new file mode 100644 index 0000000..8a4a51b --- /dev/null +++ b/threads/Lock.java @@ -0,0 +1,79 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * A Lock is a synchronization primitive that has two states, + * busy and free. There are only two operations allowed on a + * lock: + * + *

    + *
  • acquire(): atomically wait until the lock is free and + * then set it to busy. + *
  • release(): set the lock to be free, waking up one + * waiting thread if possible. + *
+ * + *

+ * Also, only the thread that acquired a lock may release it. As with + * semaphores, the API does not allow you to read the lock state (because the + * value could change immediately after you read it). + */ +public class Lock { + /** + * Allocate a new lock. The lock will initially be free. + */ + public Lock() { + } + + /** + * Atomically acquire this lock. The current thread must not already hold + * this lock. + */ + public void acquire() { + Lib.assertTrue(!isHeldByCurrentThread()); + + boolean intStatus = Machine.interrupt().disable(); + KThread thread = KThread.currentThread(); + + if (lockHolder != null) { + waitQueue.waitForAccess(thread); + KThread.sleep(); + } + else { + waitQueue.acquire(thread); + lockHolder = thread; + } + + Lib.assertTrue(lockHolder == thread); + + Machine.interrupt().restore(intStatus); + } + + /** + * Atomically release this lock, allowing other threads to acquire it. + */ + public void release() { + Lib.assertTrue(isHeldByCurrentThread()); + + boolean intStatus = Machine.interrupt().disable(); + + if ((lockHolder = waitQueue.nextThread()) != null) + lockHolder.ready(); + + Machine.interrupt().restore(intStatus); + } + + /** + * Test if the current thread holds this lock. + * + * @return true if the current thread holds this lock. + */ + public boolean isHeldByCurrentThread() { + return (lockHolder == KThread.currentThread()); + } + + private KThread lockHolder = null; + private ThreadQueue waitQueue = + ThreadedKernel.scheduler.newThreadQueue(true); +} diff --git a/threads/LotteryScheduler.java b/threads/LotteryScheduler.java new file mode 100644 index 0000000..a9a06da --- /dev/null +++ b/threads/LotteryScheduler.java @@ -0,0 +1,48 @@ +package nachos.threads; + +import nachos.machine.*; + +import java.util.TreeSet; +import java.util.HashSet; +import java.util.Iterator; + +/** + * A scheduler that chooses threads using a lottery. + * + *

+ * A lottery scheduler associates a number of tickets with each thread. When a + * thread needs to be dequeued, a random lottery is held, among all the tickets + * of all the threads waiting to be dequeued. The thread that holds the winning + * ticket is chosen. + * + *

+ * Note that a lottery scheduler must be able to handle a lot of tickets + * (sometimes billions), so it is not acceptable to maintain state for every + * ticket. + * + *

+ * A lottery scheduler must partially solve the priority inversion problem; in + * particular, tickets must be transferred through locks, and through joins. + * Unlike a priority scheduler, these tickets add (as opposed to just taking + * the maximum). + */ +public class LotteryScheduler extends PriorityScheduler { + /** + * Allocate a new lottery scheduler. + */ + public LotteryScheduler() { + } + + /** + * Allocate a new lottery thread queue. + * + * @param transferPriority true if this queue should + * transfer tickets from waiting threads + * to the owning thread. + * @return a new lottery thread queue. + */ + public ThreadQueue newThreadQueue(boolean transferPriority) { + // implement me + return null; + } +} diff --git a/threads/PriorityScheduler.java b/threads/PriorityScheduler.java new file mode 100644 index 0000000..aa6dc51 --- /dev/null +++ b/threads/PriorityScheduler.java @@ -0,0 +1,261 @@ +package nachos.threads; + +import nachos.machine.*; + +import java.util.TreeSet; +import java.util.HashSet; +import java.util.Iterator; + +/** + * A scheduler that chooses threads based on their priorities. + * + *

+ * A priority scheduler associates a priority with each thread. The next thread + * to be dequeued is always a thread with priority no less than any other + * waiting thread's priority. Like a round-robin scheduler, the thread that is + * dequeued is, among all the threads of the same (highest) priority, the + * thread that has been waiting longest. + * + *

+ * Essentially, a priority scheduler gives access in a round-robin fassion to + * all the highest-priority threads, and ignores all other threads. This has + * the potential to + * starve a thread if there's always a thread waiting with higher priority. + * + *

+ * A priority scheduler must partially solve the priority inversion problem; in + * particular, priority must be donated through locks, and through joins. + */ +public class PriorityScheduler extends Scheduler { + /** + * Allocate a new priority scheduler. + */ + public PriorityScheduler() { + } + + /** + * Allocate a new priority thread queue. + * + * @param transferPriority true if this queue should + * transfer priority from waiting threads + * to the owning thread. + * @return a new priority thread queue. + */ + public ThreadQueue newThreadQueue(boolean transferPriority) { + return new PriorityQueue(transferPriority); + } + + public int getPriority(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + + return getThreadState(thread).getPriority(); + } + + public int getEffectivePriority(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + + return getThreadState(thread).getEffectivePriority(); + } + + public void setPriority(KThread thread, int priority) { + Lib.assertTrue(Machine.interrupt().disabled()); + + Lib.assertTrue(priority >= priorityMinimum && + priority <= priorityMaximum); + + getThreadState(thread).setPriority(priority); + } + + public boolean increasePriority() { + boolean intStatus = Machine.interrupt().disable(); + + KThread thread = KThread.currentThread(); + + int priority = getPriority(thread); + if (priority == priorityMaximum) + return false; + + setPriority(thread, priority+1); + + Machine.interrupt().restore(intStatus); + return true; + } + + public boolean decreasePriority() { + boolean intStatus = Machine.interrupt().disable(); + + KThread thread = KThread.currentThread(); + + int priority = getPriority(thread); + if (priority == priorityMinimum) + return false; + + setPriority(thread, priority-1); + + Machine.interrupt().restore(intStatus); + return true; + } + + /** + * The default priority for a new thread. Do not change this value. + */ + public static final int priorityDefault = 1; + /** + * The minimum priority that a thread can have. Do not change this value. + */ + public static final int priorityMinimum = 0; + /** + * The maximum priority that a thread can have. Do not change this value. + */ + public static final int priorityMaximum = 7; + + /** + * Return the scheduling state of the specified thread. + * + * @param thread the thread whose scheduling state to return. + * @return the scheduling state of the specified thread. + */ + protected ThreadState getThreadState(KThread thread) { + if (thread.schedulingState == null) + thread.schedulingState = new ThreadState(thread); + + return (ThreadState) thread.schedulingState; + } + + /** + * A ThreadQueue that sorts threads by priority. + */ + protected class PriorityQueue extends ThreadQueue { + PriorityQueue(boolean transferPriority) { + this.transferPriority = transferPriority; + } + + public void waitForAccess(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + getThreadState(thread).waitForAccess(this); + } + + public void acquire(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + getThreadState(thread).acquire(this); + } + + public KThread nextThread() { + Lib.assertTrue(Machine.interrupt().disabled()); + // implement me + return null; + } + + /** + * Return the next thread that nextThread() would return, + * without modifying the state of this queue. + * + * @return the next thread that nextThread() would + * return. + */ + protected ThreadState pickNextThread() { + // implement me + return null; + } + + public void print() { + Lib.assertTrue(Machine.interrupt().disabled()); + // implement me (if you want) + } + + /** + * true if this queue should transfer priority from waiting + * threads to the owning thread. + */ + public boolean transferPriority; + } + + /** + * The scheduling state of a thread. This should include the thread's + * priority, its effective priority, any objects it owns, and the queue + * it's waiting for, if any. + * + * @see nachos.threads.KThread#schedulingState + */ + protected class ThreadState { + /** + * Allocate a new ThreadState object and associate it with the + * specified thread. + * + * @param thread the thread this state belongs to. + */ + public ThreadState(KThread thread) { + this.thread = thread; + + setPriority(priorityDefault); + } + + /** + * Return the priority of the associated thread. + * + * @return the priority of the associated thread. + */ + public int getPriority() { + return priority; + } + + /** + * Return the effective priority of the associated thread. + * + * @return the effective priority of the associated thread. + */ + public int getEffectivePriority() { + // implement me + return priority; + } + + /** + * Set the priority of the associated thread to the specified value. + * + * @param priority the new priority. + */ + public void setPriority(int priority) { + if (this.priority == priority) + return; + + this.priority = priority; + + // implement me + } + + /** + * Called when waitForAccess(thread) (where thread is + * the associated thread) is invoked on the specified priority queue. + * The associated thread is therefore waiting for access to the + * resource guarded by waitQueue. This method is only called + * if the associated thread cannot immediately obtain access. + * + * @param waitQueue the queue that the associated thread is + * now waiting on. + * + * @see nachos.threads.ThreadQueue#waitForAccess + */ + public void waitForAccess(PriorityQueue waitQueue) { + // implement me + } + + /** + * Called when the associated thread has acquired access to whatever is + * guarded by waitQueue. This can occur either as a result of + * acquire(thread) being invoked on waitQueue (where + * thread is the associated thread), or as a result of + * nextThread() being invoked on waitQueue. + * + * @see nachos.threads.ThreadQueue#acquire + * @see nachos.threads.ThreadQueue#nextThread + */ + public void acquire(PriorityQueue waitQueue) { + // implement me + } + + /** The thread with which this object is associated. */ + protected KThread thread; + /** The priority of the associated thread. */ + protected int priority; + } +} diff --git a/threads/Rider.java b/threads/Rider.java new file mode 100644 index 0000000..feb6b52 --- /dev/null +++ b/threads/Rider.java @@ -0,0 +1,52 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * A single rider. Each rider accesses the elevator bank through an + * instance of RiderControls. + */ +public class Rider implements RiderInterface { + /** + * Allocate a new rider. + */ + public Rider() { + } + + /** + * Initialize this rider. The rider will access the elevator bank through + * controls, and the rider will make stops at different floors as + * specified in stops. This method should return immediately after + * this rider is initialized, but not until the interrupt handler is + * set. The rider will start receiving events after this method returns, + * potentially before run() is called. + * + * @param controls the rider's interface to the elevator bank. The + * rider must not attempt to access the elevator + * bank in any other way. + * @param stops an array of stops the rider should make; see + * below. + */ + public void initialize(RiderControls controls, int[] stops) { + } + + /** + * Cause the rider to use the provided controls to make the stops specified + * in the constructor. The rider should stop at each of the floors in + * stops, an array of floor numbers. The rider should only + * make the specified stops. + * + *

+ * For example, suppose the rider uses controls to determine that + * it is initially on floor 1, and suppose the stops array contains two + * elements: { 0, 2 }. Then the rider should get on an elevator, get off + * on floor 0, get on an elevator, and get off on floor 2, pushing buttons + * as necessary. + * + *

+ * This method should not return, but instead should call + * controls.finish() when the rider is finished. + */ + public void run() { + } +} diff --git a/threads/RoundRobinScheduler.java b/threads/RoundRobinScheduler.java new file mode 100644 index 0000000..ade31a6 --- /dev/null +++ b/threads/RoundRobinScheduler.java @@ -0,0 +1,84 @@ +package nachos.threads; + +import nachos.machine.*; + +import java.util.LinkedList; +import java.util.Iterator; + +/** + * A round-robin scheduler tracks waiting threads in FIFO queues, implemented + * with linked lists. When a thread begins waiting for access, it is appended + * to the end of a list. The next thread to receive access is always the first + * thread in the list. This causes access to be given on a first-come + * first-serve basis. + */ +public class RoundRobinScheduler extends Scheduler { + /** + * Allocate a new round-robin scheduler. + */ + public RoundRobinScheduler() { + } + + /** + * Allocate a new FIFO thread queue. + * + * @param transferPriority ignored. Round robin schedulers have + * no priority. + * @return a new FIFO thread queue. + */ + public ThreadQueue newThreadQueue(boolean transferPriority) { + return new FifoQueue(); + } + + private class FifoQueue extends ThreadQueue { + /** + * Add a thread to the end of the wait queue. + * + * @param thread the thread to append to the queue. + */ + public void waitForAccess(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + + waitQueue.add(thread); + } + + /** + * Remove a thread from the beginning of the queue. + * + * @return the first thread on the queue, or null if the + * queue is + * empty. + */ + public KThread nextThread() { + Lib.assertTrue(Machine.interrupt().disabled()); + + if (waitQueue.isEmpty()) + return null; + + return (KThread) waitQueue.removeFirst(); + } + + /** + * The specified thread has received exclusive access, without using + * waitForAccess() or nextThread(). Assert that no + * threads are waiting for access. + */ + public void acquire(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + + Lib.assertTrue(waitQueue.isEmpty()); + } + + /** + * Print out the contents of the queue. + */ + public void print() { + Lib.assertTrue(Machine.interrupt().disabled()); + + for (Iterator i=waitQueue.iterator(); i.hasNext(); ) + System.out.print((KThread) i.next() + " "); + } + + private LinkedList waitQueue = new LinkedList(); + } +} diff --git a/threads/Scheduler.java b/threads/Scheduler.java new file mode 100644 index 0000000..6e86f99 --- /dev/null +++ b/threads/Scheduler.java @@ -0,0 +1,157 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * Coordinates a group of thread queues of the same kind. + * + * @see nachos.threads.ThreadQueue + */ +public abstract class Scheduler { + /** + * Allocate a new scheduler. + */ + public Scheduler() { + } + + /** + * Allocate a new thread queue. If transferPriority is + * true, then threads waiting on the new queue will transfer their + * "priority" to the thread that has access to whatever is being guarded by + * the queue. This is the mechanism used to partially solve priority + * inversion. + * + *

+ * If there is no definite thread that can be said to have "access" (as in + * the case of semaphores and condition variables), this parameter should + * be false, indicating that no priority should be transferred. + * + *

+ * The processor is a special case. There is clearly no purpose to donating + * priority to a thread that already has the processor. When the processor + * wait queue is created, this parameter should be false. + * + *

+ * Otherwise, it is beneficial to donate priority. For example, a lock has + * a definite owner (the thread that holds the lock), and a lock is always + * released by the same thread that acquired it, so it is possible to help + * a high priority thread waiting for a lock by donating its priority to + * the thread holding the lock. Therefore, a queue for a lock should be + * created with this parameter set to true. + * + *

+ * Similarly, when a thread is asleep in join() waiting for the + * target thread to finish, the sleeping thread should donate its priority + * to the target thread. Therefore, a join queue should be created with + * this parameter set to true. + * + * @param transferPriority true if the thread that has + * access should receive priority from the + * threads that are waiting on this queue. + * @return a new thread queue. + */ + public abstract ThreadQueue newThreadQueue(boolean transferPriority); + + /** + * Get the priority of the specified thread. Must be called with + * interrupts disabled. + * + * @param thread the thread to get the priority of. + * @return the thread's priority. + */ + public int getPriority(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + return 0; + } + + /** + * Get the priority of the current thread. Equivalent to + * getPriority(KThread.currentThread()). + * + * @return the current thread's priority. + */ + public int getPriority() { + return getPriority(KThread.currentThread()); + } + + /** + * Get the effective priority of the specified thread. Must be called with + * interrupts disabled. + * + *

+ * The effective priority of a thread is the priority of a thread after + * taking into account priority donations. + * + *

+ * For a priority scheduler, this is the maximum of the thread's priority + * and the priorities of all other threads waiting for the thread through a + * lock or a join. + * + *

+ * For a lottery scheduler, this is the sum of the thread's tickets and the + * tickets of all other threads waiting for the thread through a lock or a + * join. + * + * @param thread the thread to get the effective priority of. + * @return the thread's effective priority. + */ + public int getEffectivePriority(KThread thread) { + Lib.assertTrue(Machine.interrupt().disabled()); + return 0; + } + + /** + * Get the effective priority of the current thread. Equivalent to + * getEffectivePriority(KThread.currentThread()). + * + * @return the current thread's priority. + */ + public int getEffectivePriority() { + return getEffectivePriority(KThread.currentThread()); + } + + /** + * Set the priority of the specified thread. Must be called with interrupts + * disabled. + * + * @param thread the thread to set the priority of. + * @param priority the new priority. + */ + public void setPriority(KThread thread, int priority) { + Lib.assertTrue(Machine.interrupt().disabled()); + } + + /** + * Set the priority of the current thread. Equivalent to + * setPriority(KThread.currentThread(), priority). + * + * @param priority the new priority. + */ + public void setPriority(int priority) { + setPriority(KThread.currentThread(), priority); + } + + /** + * If possible, raise the priority of the current thread in some + * scheduler-dependent way. + * + * @return true if the scheduler was able to increase the current + * thread's + * priority. + */ + public boolean increasePriority() { + return false; + } + + /** + * If possible, lower the priority of the current thread user in some + * scheduler-dependent way, preferably by the same amount as would a call + * to increasePriority(). + * + * @return true if the scheduler was able to decrease the current + * thread's priority. + */ + public boolean decreasePriority() { + return false; + } +} diff --git a/threads/Semaphore.java b/threads/Semaphore.java new file mode 100644 index 0000000..e999921 --- /dev/null +++ b/threads/Semaphore.java @@ -0,0 +1,104 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * A Semaphore is a synchronization primitive with an unsigned value. + * A semaphore has only two operations: + * + *

    + *
  • P(): waits until the semaphore's value is greater than zero, + * then decrements it. + *
  • V(): increments the semaphore's value, and wakes up one thread + * waiting in P() if possible. + *
+ * + *

+ * Note that this API does not allow a thread to read the value of the + * semaphore directly. Even if you did read the value, the only thing you would + * know is what the value used to be. You don't know what the value is now, + * because by the time you get the value, a context switch might have occurred, + * and some other thread might have called P() or V(), so the + * true value might now be different. + */ +public class Semaphore { + /** + * Allocate a new semaphore. + * + * @param initialValue the initial value of this semaphore. + */ + public Semaphore(int initialValue) { + value = initialValue; + } + + /** + * Atomically wait for this semaphore to become non-zero and decrement it. + */ + public void P() { + boolean intStatus = Machine.interrupt().disable(); + + if (value == 0) { + waitQueue.waitForAccess(KThread.currentThread()); + KThread.sleep(); + } + else { + value--; + } + + Machine.interrupt().restore(intStatus); + } + + /** + * Atomically increment this semaphore and wake up at most one other thread + * sleeping on this semaphore. + */ + public void V() { + boolean intStatus = Machine.interrupt().disable(); + + KThread thread = waitQueue.nextThread(); + if (thread != null) { + thread.ready(); + } + else { + value++; + } + + Machine.interrupt().restore(intStatus); + } + + private static class PingTest implements Runnable { + PingTest(Semaphore ping, Semaphore pong) { + this.ping = ping; + this.pong = pong; + } + + public void run() { + for (int i=0; i<10; i++) { + ping.P(); + pong.V(); + } + } + + private Semaphore ping; + private Semaphore pong; + } + + /** + * Test if this module is working. + */ + public static void selfTest() { + Semaphore ping = new Semaphore(0); + Semaphore pong = new Semaphore(0); + + new KThread(new PingTest(ping, pong)).setName("ping").fork(); + + for (int i=0; i<10; i++) { + ping.V(); + pong.P(); + } + } + + private int value; + private ThreadQueue waitQueue = + ThreadedKernel.scheduler.newThreadQueue(false); +} diff --git a/threads/SynchList.java b/threads/SynchList.java new file mode 100644 index 0000000..824b32e --- /dev/null +++ b/threads/SynchList.java @@ -0,0 +1,88 @@ +package nachos.threads; + +import java.util.LinkedList; +import nachos.machine.*; +import nachos.threads.*; + +/** + * A synchronized queue. + */ +public class SynchList { + /** + * Allocate a new synchronized queue. + */ + public SynchList() { + list = new LinkedList(); + lock = new Lock(); + listEmpty = new Condition(lock); + } + + /** + * Add the specified object to the end of the queue. If another thread is + * waiting in removeFirst(), it is woken up. + * + * @param o the object to add. Must not be null. + */ + public void add(Object o) { + Lib.assertTrue(o != null); + + lock.acquire(); + list.add(o); + listEmpty.wake(); + lock.release(); + } + + /** + * Remove an object from the front of the queue, blocking until the queue + * is non-empty if necessary. + * + * @return the element removed from the front of the queue. + */ + public Object removeFirst() { + Object o; + + lock.acquire(); + while (list.isEmpty()) + listEmpty.sleep(); + o = list.removeFirst(); + lock.release(); + + return o; + } + + private static class PingTest implements Runnable { + PingTest(SynchList ping, SynchList pong) { + this.ping = ping; + this.pong = pong; + } + + public void run() { + for (int i=0; i<10; i++) + pong.add(ping.removeFirst()); + } + + private SynchList ping; + private SynchList pong; + } + + /** + * Test that this module is working. + */ + public static void selfTest() { + SynchList ping = new SynchList(); + SynchList pong = new SynchList(); + + new KThread(new PingTest(ping, pong)).setName("ping").fork(); + + for (int i=0; i<10; i++) { + Integer o = new Integer(i); + ping.add(o); + Lib.assertTrue(pong.removeFirst() == o); + } + } + + private LinkedList list; + private Lock lock; + private Condition listEmpty; +} + diff --git a/threads/ThreadQueue.java b/threads/ThreadQueue.java new file mode 100644 index 0000000..a942245 --- /dev/null +++ b/threads/ThreadQueue.java @@ -0,0 +1,98 @@ +package nachos.threads; + +/** + * Schedules access to some sort of resource with limited access constraints. A + * thread queue can be used to share this limited access among multiple + * threads. + * + *

+ * Examples of limited access in Nachos include: + * + *

    + *
  1. the right for a thread to use the processor. Only one thread may run on + * the processor at a time. + * + *
  2. the right for a thread to acquire a specific lock. A lock may be held by + * only one thread at a time. + * + *
  3. the right for a thread to return from Semaphore.P() when the + * semaphore is 0. When another thread calls Semaphore.V(), only one + * thread waiting in Semaphore.P() can be awakened. + * + *
  4. the right for a thread to be woken while sleeping on a condition + * variable. When another thread calls Condition.wake(), only one + * thread sleeping on the condition variable can be awakened. + * + *
  5. the right for a thread to return from KThread.join(). Threads + * are not allowed to return from join() until the target thread has + * finished. + *
+ * + * All these cases involve limited access because, for each of them, it is not + * necessarily possible (or correct) for all the threads to have simultaneous + * access. Some of these cases involve concrete resources (e.g. the processor, + * or a lock); others are more abstract (e.g. waiting on semaphores, condition + * variables, or join). + * + *

+ * All thread queue methods must be invoked with interrupts disabled. + */ +public abstract class ThreadQueue { + /** + * Notify this thread queue that the specified thread is waiting for + * access. This method should only be called if the thread cannot + * immediately obtain access (e.g. if the thread wants to acquire a lock + * but another thread already holds the lock). + * + *

+ * A thread must not simultaneously wait for access to multiple resources. + * For example, a thread waiting for a lock must not also be waiting to run + * on the processor; if a thread is waiting for a lock it should be + * sleeping. + * + *

+ * However, depending on the specific objects, it may be acceptable for a + * thread to wait for access to one object while having access to another. + * For example, a thread may attempt to acquire a lock while holding + * another lock. Note, though, that the processor cannot be held while + * waiting for access to anything else. + * + * @param thread the thread waiting for access. + */ + public abstract void waitForAccess(KThread thread); + + /** + * Notify this thread queue that another thread can receive access. Choose + * and return the next thread to receive access, or null if there + * are no threads waiting. + * + *

+ * If the limited access object transfers priority, and if there are other + * threads waiting for access, then they will donate priority to the + * returned thread. + * + * @return the next thread to receive access, or null if there + * are no threads waiting. + */ + public abstract KThread nextThread(); + + /** + * Notify this thread queue that a thread has received access, without + * going through request() and nextThread(). For example, + * if a thread acquires a lock that no other threads are waiting for, it + * should call this method. + * + *

+ * This method should not be called for a thread returned from + * nextThread(). + * + * @param thread the thread that has received access, but was not + * returned from nextThread(). + */ + public abstract void acquire(KThread thread); + + /** + * Print out all the threads waiting for access, in no particular order. + */ + public abstract void print(); +} diff --git a/threads/ThreadedKernel.java b/threads/ThreadedKernel.java new file mode 100644 index 0000000..486b402 --- /dev/null +++ b/threads/ThreadedKernel.java @@ -0,0 +1,86 @@ +package nachos.threads; + +import nachos.machine.*; + +/** + * A multi-threaded OS kernel. + */ +public class ThreadedKernel extends Kernel { + /** + * Allocate a new multi-threaded kernel. + */ + public ThreadedKernel() { + super(); + } + + /** + * Initialize this kernel. Creates a scheduler, the first thread, and an + * alarm, and enables interrupts. Creates a file system if necessary. + */ + public void initialize(String[] args) { + // set scheduler + String schedulerName = Config.getString("ThreadedKernel.scheduler"); + scheduler = (Scheduler) Lib.constructObject(schedulerName); + + // set fileSystem + String fileSystemName = Config.getString("ThreadedKernel.fileSystem"); + if (fileSystemName != null) + fileSystem = (FileSystem) Lib.constructObject(fileSystemName); + else if (Machine.stubFileSystem() != null) + fileSystem = Machine.stubFileSystem(); + else + fileSystem = null; + + // start threading + new KThread(null); + + alarm = new Alarm(); + + Machine.interrupt().enable(); + } + + /** + * Test this kernel. Test the KThread, Semaphore, + * SynchList, and ElevatorBank classes. Note that the + * autograder never calls this method, so it is safe to put additional + * tests here. + */ + public void selfTest() { + KThread.selfTest(); + Semaphore.selfTest(); + SynchList.selfTest(); + if (Machine.bank() != null) { + ElevatorBank.selfTest(); + } + } + + /** + * A threaded kernel does not run user programs, so this method does + * nothing. + */ + public void run() { + } + + /** + * Terminate this kernel. Never returns. + */ + public void terminate() { + Machine.halt(); + } + + /** Globally accessible reference to the scheduler. */ + public static Scheduler scheduler = null; + /** Globally accessible reference to the alarm. */ + public static Alarm alarm = null; + /** Globally accessible reference to the file system. */ + public static FileSystem fileSystem = null; + + // dummy variables to make javac smarter + private static RoundRobinScheduler dummy1 = null; + private static PriorityScheduler dummy2 = null; + private static LotteryScheduler dummy3 = null; + private static Condition2 dummy4 = null; + private static Communicator dummy5 = null; + private static Rider dummy6 = null; + private static ElevatorController dummy7 = null; +} diff --git a/threads/package.html b/threads/package.html new file mode 100644 index 0000000..28e9547 --- /dev/null +++ b/threads/package.html @@ -0,0 +1,3 @@ + +Provides classes that support a multithreaded kernel. + diff --git a/userprog/SynchConsole.java b/userprog/SynchConsole.java new file mode 100644 index 0000000..50e1985 --- /dev/null +++ b/userprog/SynchConsole.java @@ -0,0 +1,147 @@ +package nachos.userprog; + +import nachos.machine.*; +import nachos.threads.*; +import nachos.userprog.*; + +/** + * Provides a simple, synchronized interface to the machine's console. The + * interface can also be accessed through OpenFile objects. + */ +public class SynchConsole { + /** + * Allocate a new SynchConsole. + * + * @param console the underlying serial console to use. + */ + public SynchConsole(SerialConsole console) { + this.console = console; + + Runnable receiveHandler = new Runnable() { + public void run() { receiveInterrupt(); } + }; + Runnable sendHandler = new Runnable() { + public void run() { sendInterrupt(); } + }; + console.setInterruptHandlers(receiveHandler, sendHandler); + } + + /** + * Return the next unsigned byte received (in the range 0 through + * 255). If a byte has not arrived at, blocks until a byte + * arrives, or returns immediately, depending on the value of block. + * + * @param block true if readByte() should wait for a + * byte if none is available. + * @return the next byte read, or -1 if block was false + * and no byte was available. + */ + public int readByte(boolean block) { + int value; + boolean intStatus = Machine.interrupt().disable(); + readLock.acquire(); + + if (block || charAvailable) { + charAvailable = false; + readWait.P(); + + value = console.readByte(); + Lib.assertTrue(value != -1); + } + else { + value = -1; + } + + readLock.release(); + Machine.interrupt().restore(intStatus); + return value; + } + + /** + * Return an OpenFile that can be used to read this as a file. + * + * @return a file that can read this console. + */ + public OpenFile openForReading() { + return new File(true, false); + } + + private void receiveInterrupt() { + charAvailable = true; + readWait.V(); + } + + /** + * Send a byte. Blocks until the send is complete. + * + * @param value the byte to be sent (the upper 24 bits are ignored). + */ + public void writeByte(int value) { + writeLock.acquire(); + console.writeByte(value); + writeWait.P(); + writeLock.release(); + } + + /** + * Return an OpenFile that can be used to write this as a file. + * + * @return a file that can write this console. + */ + public OpenFile openForWriting() { + return new File(false, true); + } + + private void sendInterrupt() { + writeWait.V(); + } + + private boolean charAvailable = false; + + private SerialConsole console; + private Lock readLock = new Lock(); + private Lock writeLock = new Lock(); + private Semaphore readWait = new Semaphore(0); + private Semaphore writeWait = new Semaphore(0); + + private class File extends OpenFile { + File(boolean canRead, boolean canWrite) { + super(null, "SynchConsole"); + + this.canRead = canRead; + this.canWrite = canWrite; + } + + public void close() { + canRead = canWrite = false; + } + + public int read(byte[] buf, int offset, int length) { + if (!canRead) + return 0; + + int i; + for (i=0; i + * A thread capable of running user code actually has two sets of + * CPU registers: one for its state while executing user code, and one for + * its state while executing kernel code. While this thread is not running, + * its user state is stored here. + */ + public int userRegisters[] = new int[Processor.numUserRegisters]; + + /** + * The process to which this thread belongs. + */ + public UserProcess process; +} diff --git a/userprog/UserKernel.java b/userprog/UserKernel.java new file mode 100644 index 0000000..ad39e99 --- /dev/null +++ b/userprog/UserKernel.java @@ -0,0 +1,115 @@ +package nachos.userprog; + +import nachos.machine.*; +import nachos.threads.*; +import nachos.userprog.*; + +/** + * A kernel that can support multiple user processes. + */ +public class UserKernel extends ThreadedKernel { + /** + * Allocate a new user kernel. + */ + public UserKernel() { + super(); + } + + /** + * Initialize this kernel. Creates a synchronized console and sets the + * processor's exception handler. + */ + public void initialize(String[] args) { + super.initialize(args); + + console = new SynchConsole(Machine.console()); + + Machine.processor().setExceptionHandler(new Runnable() { + public void run() { exceptionHandler(); } + }); + } + + /** + * Test the console device. + */ + public void selfTest() { + super.selfTest(); + + System.out.println("Testing the console device. Typed characters"); + System.out.println("will be echoed until q is typed."); + + char c; + + do { + c = (char) console.readByte(true); + console.writeByte(c); + } + while (c != 'q'); + + System.out.println(""); + } + + /** + * Returns the current process. + * + * @return the current process, or null if no process is current. + */ + public static UserProcess currentProcess() { + if (!(KThread.currentThread() instanceof UThread)) + return null; + + return ((UThread) KThread.currentThread()).process; + } + + /** + * The exception handler. This handler is called by the processor whenever + * a user instruction causes a processor exception. + * + *

+ * When the exception handler is invoked, interrupts are enabled, and the + * processor's cause register contains an integer identifying the cause of + * the exception (see the exceptionZZZ constants in the + * Processor class). If the exception involves a bad virtual + * address (e.g. page fault, TLB miss, read-only, bus error, or address + * error), the processor's BadVAddr register identifies the virtual address + * that caused the exception. + */ + public void exceptionHandler() { + Lib.assertTrue(KThread.currentThread() instanceof UThread); + + UserProcess process = ((UThread) KThread.currentThread()).process; + int cause = Machine.processor().readRegister(Processor.regCause); + process.handleException(cause); + } + + /** + * Start running user programs, by creating a process and running a shell + * program in it. The name of the shell program it must run is returned by + * Machine.getShellProgramName(). + * + * @see nachos.machine.Machine#getShellProgramName + */ + public void run() { + super.run(); + + UserProcess process = UserProcess.newUserProcess(); + + String shellProgram = Machine.getShellProgramName(); + Lib.assertTrue(process.execute(shellProgram, new String[] { })); + + KThread.currentThread().finish(); + } + + /** + * Terminate this kernel. Never returns. + */ + public void terminate() { + super.terminate(); + } + + /** Globally accessible reference to the synchronized console. */ + public static SynchConsole console; + + // dummy variables to make javac smarter + private static Coff dummy1 = null; +} diff --git a/userprog/UserProcess.java b/userprog/UserProcess.java new file mode 100644 index 0000000..e75f7ef --- /dev/null +++ b/userprog/UserProcess.java @@ -0,0 +1,449 @@ +package nachos.userprog; + +import nachos.machine.*; +import nachos.threads.*; +import nachos.userprog.*; + +import java.io.EOFException; + +/** + * Encapsulates the state of a user process that is not contained in its + * user thread (or threads). This includes its address translation state, a + * file table, and information about the program being executed. + * + *

+ * This class is extended by other classes to support additional functionality + * (such as additional syscalls). + * + * @see nachos.vm.VMProcess + * @see nachos.network.NetProcess + */ +public class UserProcess { + /** + * Allocate a new process. + */ + public UserProcess() { + int numPhysPages = Machine.processor().getNumPhysPages(); + pageTable = new TranslationEntry[numPhysPages]; + for (int i=0; inachos.conf key + * Kernel.processClassName. + * + * @return a new process of the correct class. + */ + public static UserProcess newUserProcess() { + return (UserProcess)Lib.constructObject(Machine.getProcessClassName()); + } + + /** + * Execute the specified program with the specified arguments. Attempts to + * load the program, and then forks a thread to run it. + * + * @param name the name of the file containing the executable. + * @param args the arguments to pass to the executable. + * @return true if the program was successfully executed. + */ + public boolean execute(String name, String[] args) { + if (!load(name, args)) + return false; + + new UThread(this).setName(name).fork(); + + return true; + } + + /** + * Save the state of this process in preparation for a context switch. + * Called by UThread.saveState(). + */ + public void saveState() { + } + + /** + * Restore the state of this process after a context switch. Called by + * UThread.restoreState(). + */ + public void restoreState() { + Machine.processor().setPageTable(pageTable); + } + + /** + * Read a null-terminated string from this process's virtual memory. Read + * at most maxLength + 1 bytes from the specified address, search + * for the null terminator, and convert it to a java.lang.String, + * without including the null terminator. If no null terminator is found, + * returns null. + * + * @param vaddr the starting virtual address of the null-terminated + * string. + * @param maxLength the maximum number of characters in the string, + * not including the null terminator. + * @return the string read, or null if no null terminator was + * found. + */ + public String readVirtualMemoryString(int vaddr, int maxLength) { + Lib.assertTrue(maxLength >= 0); + + byte[] bytes = new byte[maxLength+1]; + + int bytesRead = readVirtualMemory(vaddr, bytes); + + for (int length=0; lengthreadVirtualMemory(vaddr, data, 0, data.length). + * + * @param vaddr the first byte of virtual memory to read. + * @param data the array where the data will be stored. + * @return the number of bytes successfully transferred. + */ + public int readVirtualMemory(int vaddr, byte[] data) { + return readVirtualMemory(vaddr, data, 0, data.length); + } + + /** + * Transfer data from this process's virtual memory to the specified array. + * This method handles address translation details. This method must + * not destroy the current process if an error occurs, but instead + * should return the number of bytes successfully copied (or zero if no + * data could be copied). + * + * @param vaddr the first byte of virtual memory to read. + * @param data the array where the data will be stored. + * @param offset the first byte to write in the array. + * @param length the number of bytes to transfer from virtual memory to + * the array. + * @return the number of bytes successfully transferred. + */ + public int readVirtualMemory(int vaddr, byte[] data, int offset, + int length) { + Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length); + + byte[] memory = Machine.processor().getMemory(); + + // for now, just assume that virtual addresses equal physical addresses + if (vaddr < 0 || vaddr >= memory.length) + return 0; + + int amount = Math.min(length, memory.length-vaddr); + System.arraycopy(memory, vaddr, data, offset, amount); + + return amount; + } + + /** + * Transfer all data from the specified array to this process's virtual + * memory. + * Same as writeVirtualMemory(vaddr, data, 0, data.length). + * + * @param vaddr the first byte of virtual memory to write. + * @param data the array containing the data to transfer. + * @return the number of bytes successfully transferred. + */ + public int writeVirtualMemory(int vaddr, byte[] data) { + return writeVirtualMemory(vaddr, data, 0, data.length); + } + + /** + * Transfer data from the specified array to this process's virtual memory. + * This method handles address translation details. This method must + * not destroy the current process if an error occurs, but instead + * should return the number of bytes successfully copied (or zero if no + * data could be copied). + * + * @param vaddr the first byte of virtual memory to write. + * @param data the array containing the data to transfer. + * @param offset the first byte to transfer from the array. + * @param length the number of bytes to transfer from the array to + * virtual memory. + * @return the number of bytes successfully transferred. + */ + public int writeVirtualMemory(int vaddr, byte[] data, int offset, + int length) { + Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length); + + byte[] memory = Machine.processor().getMemory(); + + // for now, just assume that virtual addresses equal physical addresses + if (vaddr < 0 || vaddr >= memory.length) + return 0; + + int amount = Math.min(length, memory.length-vaddr); + System.arraycopy(data, offset, memory, vaddr, amount); + + return amount; + } + + /** + * Load the executable with the specified name into this process, and + * prepare to pass it the specified arguments. Opens the executable, reads + * its header information, and copies sections and arguments into this + * process's virtual memory. + * + * @param name the name of the file containing the executable. + * @param args the arguments to pass to the executable. + * @return true if the executable was successfully loaded. + */ + private boolean load(String name, String[] args) { + Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")"); + + OpenFile executable = ThreadedKernel.fileSystem.open(name, false); + if (executable == null) { + Lib.debug(dbgProcess, "\topen failed"); + return false; + } + + try { + coff = new Coff(executable); + } + catch (EOFException e) { + executable.close(); + Lib.debug(dbgProcess, "\tcoff load failed"); + return false; + } + + // make sure the sections are contiguous and start at page 0 + numPages = 0; + for (int s=0; s pageSize) { + coff.close(); + Lib.debug(dbgProcess, "\targuments too long"); + return false; + } + + // program counter initially points at the program entry point + initialPC = coff.getEntryPoint(); + + // next comes the stack; stack pointer initially points to top of it + numPages += stackPages; + initialSP = numPages*pageSize; + + // and finally reserve 1 page for arguments + numPages++; + + if (!loadSections()) + return false; + + // store arguments in last page + int entryOffset = (numPages-1)*pageSize; + int stringOffset = entryOffset + args.length*4; + + this.argc = args.length; + this.argv = entryOffset; + + for (int i=0; itrue if the sections were successfully loaded. + */ + protected boolean loadSections() { + if (numPages > Machine.processor().getNumPhysPages()) { + coff.close(); + Lib.debug(dbgProcess, "\tinsufficient physical memory"); + return false; + } + + // load sections + for (int s=0; sloadSections(). + */ + protected void unloadSections() { + } + + /** + * Initialize the processor's registers in preparation for running the + * program loaded into this process. Set the PC register to point at the + * start function, set the stack pointer register to point at the top of + * the stack, set the A0 and A1 registers to argc and argv, respectively, + * and initialize all other registers to 0. + */ + public void initRegisters() { + Processor processor = Machine.processor(); + + // by default, everything's 0 + for (int i=0; ihandleException(). The + * syscall argument identifies which syscall the user executed: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
syscall#syscall prototype
0void halt();
1void exit(int status);
2int exec(char *name, int argc, char **argv); + *
3int join(int pid, int *status);
4int creat(char *name);
5int open(char *name);
6int read(int fd, char *buffer, int size); + *
7int write(int fd, char *buffer, int size); + *
8int close(int fd);
9int unlink(char *name);
+ * + * @param syscall the syscall number. + * @param a0 the first syscall argument. + * @param a1 the second syscall argument. + * @param a2 the third syscall argument. + * @param a3 the fourth syscall argument. + * @return the value to be returned to the user. + */ + public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) { + switch (syscall) { + case syscallHalt: + return handleHalt(); + + + default: + Lib.debug(dbgProcess, "Unknown syscall " + syscall); + Lib.assertNotReached("Unknown system call!"); + } + return 0; + } + + /** + * Handle a user exception. Called by + * UserKernel.exceptionHandler(). The + * cause argument identifies which exception occurred; see the + * Processor.exceptionZZZ constants. + * + * @param cause the user exception that occurred. + */ + public void handleException(int cause) { + Processor processor = Machine.processor(); + + switch (cause) { + case Processor.exceptionSyscall: + int result = handleSyscall(processor.readRegister(Processor.regV0), + processor.readRegister(Processor.regA0), + processor.readRegister(Processor.regA1), + processor.readRegister(Processor.regA2), + processor.readRegister(Processor.regA3) + ); + processor.writeRegister(Processor.regV0, result); + processor.advancePC(); + break; + + default: + Lib.debug(dbgProcess, "Unexpected exception: " + + Processor.exceptionNames[cause]); + Lib.assertNotReached("Unexpected exception"); + } + } + + /** The program being run by this process. */ + protected Coff coff; + + /** This process's page table. */ + protected TranslationEntry[] pageTable; + /** The number of contiguous pages occupied by the program. */ + protected int numPages; + + /** The number of pages in the program's stack. */ + protected final int stackPages = 8; + + private int initialPC, initialSP; + private int argc, argv; + + private static final int pageSize = Processor.pageSize; + private static final char dbgProcess = 'a'; +} diff --git a/userprog/package.html b/userprog/package.html new file mode 100644 index 0000000..bcc8368 --- /dev/null +++ b/userprog/package.html @@ -0,0 +1,4 @@ + +Provides classes that allow Nachos to load and execute single-threaded user +programs in separate address spaces. + diff --git a/vm/VMKernel.java b/vm/VMKernel.java new file mode 100644 index 0000000..62be1d8 --- /dev/null +++ b/vm/VMKernel.java @@ -0,0 +1,51 @@ +package nachos.vm; + +import nachos.machine.*; +import nachos.threads.*; +import nachos.userprog.*; +import nachos.vm.*; + +/** + * A kernel that can support multiple demand-paging user processes. + */ +public class VMKernel extends UserKernel { + /** + * Allocate a new VM kernel. + */ + public VMKernel() { + super(); + } + + /** + * Initialize this kernel. + */ + public void initialize(String[] args) { + super.initialize(args); + } + + /** + * Test this kernel. + */ + public void selfTest() { + super.selfTest(); + } + + /** + * Start running user programs. + */ + public void run() { + super.run(); + } + + /** + * Terminate this kernel. Never returns. + */ + public void terminate() { + super.terminate(); + } + + // dummy variables to make javac smarter + private static VMProcess dummy1 = null; + + private static final char dbgVM = 'v'; +} diff --git a/vm/VMProcess.java b/vm/VMProcess.java new file mode 100644 index 0000000..4530c06 --- /dev/null +++ b/vm/VMProcess.java @@ -0,0 +1,73 @@ +package nachos.vm; + +import nachos.machine.*; +import nachos.threads.*; +import nachos.userprog.*; +import nachos.vm.*; + +/** + * A UserProcess that supports demand-paging. + */ +public class VMProcess extends UserProcess { + /** + * Allocate a new process. + */ + public VMProcess() { + super(); + } + + /** + * Save the state of this process in preparation for a context switch. + * Called by UThread.saveState(). + */ + public void saveState() { + super.saveState(); + } + + /** + * Restore the state of this process after a context switch. Called by + * UThread.restoreState(). + */ + public void restoreState() { + super.restoreState(); + } + + /** + * Initializes page tables for this process so that the executable can be + * demand-paged. + * + * @return true if successful. + */ + protected boolean loadSections() { + return super.loadSections(); + } + + /** + * Release any resources allocated by loadSections(). + */ + protected void unloadSections() { + super.unloadSections(); + } + + /** + * Handle a user exception. Called by + * UserKernel.exceptionHandler(). The + * cause argument identifies which exception occurred; see the + * Processor.exceptionZZZ constants. + * + * @param cause the user exception that occurred. + */ + public void handleException(int cause) { + Processor processor = Machine.processor(); + + switch (cause) { + default: + super.handleException(cause); + break; + } + } + + private static final int pageSize = Processor.pageSize; + private static final char dbgProcess = 'a'; + private static final char dbgVM = 'v'; +} diff --git a/vm/package.html b/vm/package.html new file mode 100644 index 0000000..a711183 --- /dev/null +++ b/vm/package.html @@ -0,0 +1,4 @@ + +Provides classes that allow Nachos processes to be demand paged, and to use a +hardware TLB for address translation. +