p1/machine/TCB.java

415 lines
14 KiB
Java

// 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.
*
* <p>
* Do not use any methods in <tt>java.lang.Thread</tt>, as they are not
* compatible with the TCB API. Most <tt>Thread</tt> methods will either crash
* Nachos or have no useful effect.
*
* <p>
* Do not use the <i>synchronized</i> keyword <b>anywhere</b> in your code.
* It's against the rules, <i>and</i> 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 <tt>Machine.terminate()</tt>.
*/
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 <tt>true</tt> 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 <tt>done</tt> 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 <tt>running</tt> flag
* is set to <tt>true</tt>. <tt>waitForInterrupt()</tt> 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 <tt>currentTCB</tt>, since it
* is updated by <tt>contextSwitch()</tt> before we get called.
*/
private synchronized void waitForInterrupt() {
while (!running) {
try { wait(); }
catch (InterruptedException e) { }
}
}
/**
* Wake up this TCB by setting its <tt>running</tt> flag to <tt>true</tt>
* 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
* <tt>null</tt> when the <tt>TCB</tt> class is loaded, and then the first
* invocation of <tt>start(Runnable)</tt> assigns <tt>currentTCB</tt> a
* reference to the first TCB. After that, only <tt>yield()</tt> can
* change <tt>currentTCB</tt> to the current TCB, and only after
* <tt>waitForInterrupt()</tt> returns.
*
* <p>
* Note that <tt>currentTCB.javaThread</tt> 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 <i>running</i> TCB objects. It is initialized to
* an empty vector when the <tt>TCB</tt> class is loaded. TCB objects are
* added only in <tt>start(Runnable)</tt>, which can only be invoked once
* on each TCB object. TCB objects are removed only in each of the
* <tt>catch</tt> clauses of <tt>threadroot()</tt>, one of which is always
* invoked on thread termination. The maximum number of threads in
* <tt>runningThreads</tt> is limited to <tt>maxThreads</tt> by
* <tt>start(Runnable)</tt>. If <tt>threadroot()</tt> drops the number of
* TCB objects in <tt>runningThreads</tt> to zero, Nachos exits, so once
* the first TCB is created, this vector is basically never empty.
*/
private static Vector<TCB> runningThreads = new Vector<TCB>();
private static Privilege privilege;
private static KThread toBeDestroyed = null;
/**
* <tt>true</tt> if and only if this TCB is the first TCB to start, the one
* started in <tt>Machine.main(String[])</tt>. Initialized by
* <tt>start(Runnable)</tt>, on the basis of whether <tt>currentTCB</tt>
* has been initialized.
*/
private boolean isFirstTCB;
/**
* A reference to the Java thread bound to this TCB. It is initially
* <tt>null</tt>, assigned to a Java thread in <tt>start(Runnable)</tt>,
* and set to <tt>null</tt> again in <tt>destroy()</tt>.
*/
private Thread javaThread = null;
/**
* <tt>true</tt> if and only if the Java thread bound to this TCB ought to
* be running. This is an entirely different condition from membership in
* <tt>runningThreads</tt>, which contains all TCB objects that have
* started and have not terminated. <tt>running</tt> is only <tt>true</tt>
* 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 <tt>true</tt> by <tt>destroy()</tt>, so that when
* <tt>waitForInterrupt()</tt> returns in the doomed TCB, <tt>yield()</tt>
* 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);
}
}
}