forked from NachoSquad/nachos
-
Notifications
You must be signed in to change notification settings - Fork 0
/
TCB.java
414 lines (356 loc) · 13.8 KB
/
TCB.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
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.
*
* <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);
}
}
}