415 lines
14 KiB
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);
|
|
}
|
|
}
|
|
}
|