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
/*
* The simple thread scheduler.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "sthread.h"
#include "queue.h"
/*! The default stack size of threads, 1MiB of stack space. */
#define DEFAULT_STACKSIZE (1 << 20)
/************************************************************************
* Interface to the assembly.
*/
/*! This function must be called to start thread processing. */
void __sthread_start(void);
/*!
* The is the entry point into the scheduler, to be called
* whenever a thread action is required.
*/
void __sthread_switch(void);
/*!
* Initialize the context for a new thread.
*
* The stackp pointer should point to the *end* of the area allocated for the
* thread's stack. (Don't forget that x86 stacks grow downward in memory.)
*
* The function f is initially called with the argument given. When f
* returns, __sthread_finish() is automatically called by the threading
* library to ensure that the thread is cleaned up and deallocated properly.
*/
ThreadContext *__sthread_initialize_context(void *stackp, ThreadFunction f,
void *arg);
/************************************************************************
* Internal helper functions.
*/
/*
* The initial thread context is initialized such that this function
* will be called automatically when a thread's function returns.
*/
void __sthread_finish(void);
/*
* This function deallocates the memory associated with a thread
* when it terminates.
*/
void __sthread_delete(Thread *threadp);
/************************************************************************
* Global variables and scheduler-level queue operations.
*/
/*!
* The thread that is currently running.
*
* Invariant: during normal operation, there is exactly one thread in
* the ThreadRunning state, and this variable points to that thread.
*
* Note that at start-up of the threading library, this will be NULL.
*/
static Thread *current;
/*!
* The queue of ready threads. Invariant: All threads in the ready queue
* are in the state ThreadReady.
*/
static Queue ready_queue;
/*!
* The queue of blocked threads. Invariant: All threads in the blocked
* queue are in the state ThreadBlocked.
*/
static Queue blocked_queue;
/*!
* Add a thread to the appropriate scheduling queue, based on its state.
*/
static void enqueue_thread(Thread *threadp) {
assert(threadp != NULL);
switch(threadp->state) {
case ThreadReady:
queue_append(&ready_queue, threadp);
break;
case ThreadBlocked:
queue_append(&blocked_queue, threadp);
break;
default:
fprintf(stderr, "Thread state has been corrupted: %d\n",
threadp->state);
exit(1);
}
}
/************************************************************************
* Scheduler.
*/
/*
* The scheduler is called with the context of the current thread,
* or NULL when the scheduler is first started.
*
* The general operation of this function is:
* 1. Save the context argument into the current thread.
* 2. Either queue up or deallocate the current thread,
* based on its state.
* 3. Select a new "ready" thread to run, and set the "current"
* variable to that thread.
* - If no "ready" thread is available, examine the system
* state to handle this situation properly.
* 4. Return the context of the thread to run, so that a context-
* switch will be performed to start running the next thread.
*
* This function is global because it needs to be called from the assembly.
*/
ThreadContext *__sthread_scheduler(ThreadContext *context) {
/* TODO: Replace these lines with your implementation */
/* TODO */ assert(0); /* TODO */
/* Return the next thread to resume executing. */
return current->context;
}
/************************************************************************
* Thread operations.
*/
/*
* Start the scheduler.
*/
void sthread_start(void)
{
__sthread_start();
}
/*
* Create a new thread.
*
* This function allocates a new context, and a new Thread
* structure, and it adds the thread to the Ready queue.
*/
Thread * sthread_create(void (*f)(void *arg), void *arg) {
/* TODO: Replace this function's body with your implementation */
/* TODO */ assert(0); /* TODO */
return NULL;
}
/*
* This function is called automatically when a thread's function returns,
* so that the thread can be marked as "finished" and can then be reclaimed.
* The scheduler will call __sthread_delete() on the thread before scheduling
* a new thread for execution.
*
* This function is global because it needs to be referenced from assembly.
*/
void __sthread_finish(void) {
printf("Thread %p has finished executing.\n", (void *) current);
current->state = ThreadFinished;
__sthread_switch();
}
/*!
* This function is used by the scheduler to release the memory used by the
* specified thread. The function deletes the memory used for the thread's
* context, as well as the memory for the Thread struct.
*/
void __sthread_delete(Thread *threadp) {
/* TODO: Replace this function's body with your implementation */
/* TODO */ assert(0); /* TODO */
}
/*!
* Yield, so that another thread can run. This is easy: just
* call the scheduler, and it will pick a new thread to run.
*/
void sthread_yield() {
__sthread_switch();
}
/*!
* Block the current thread. Set the state of the current thread
* to Blocked, and call the scheduler.
*/
void sthread_block() {
current->state = ThreadBlocked;
__sthread_switch();
}
/*!
* Unblock a thread that is blocked. The thread is placed on
* the ready queue.
*/
void sthread_unblock(Thread *threadp) {
/* Make sure the thread was blocked */
assert(threadp->state == ThreadBlocked);
/* Remove from the blocked queue */
queue_remove(&blocked_queue, threadp);
/* Re-queue it */
threadp->state = ThreadReady;
enqueue_thread(threadp);
}