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: * *
*/ 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. * ** PiRun p = new PiRun(); * new KThread(p).fork(); *
* 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; }