-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmpos-kern.c
executable file
·449 lines (376 loc) · 15.6 KB
/
mpos-kern.c
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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
#include "mpos-kern.h"
#include "x86.h"
#include "lib.h"
/*****************************************************************************
* mpos-kern
*
* This is the miniprocos's kernel.
* It sets up a process descriptor for the initial application, then runs
* that application, and responds to its system calls.
*
*****************************************************************************/
// The kernel is loaded starting at 0x100000.
// The miniprocos applications are also available in RAM in packed form.
// The kernel loads one of those applications into memory starting at 0x200000.
// It also allocates 1/4 MB for each possible miniprocess's stack, starting at
// 0x280000.
// Each process's stack grows down from the top of its stack space.
#define PROC1_STACK_ADDR 0x280000
#define PROC_STACK_SIZE 0x040000
// MINIPROCOS MEMORY MAP
//
// +--------------------------+--------------+----------------------------+-/
// | Base Memory (640K) | I/O Memory | Kernel Kernel |
// | (unused) | | Code + Data Stack |
// +--------------------------+--------------+----------------------------+-/
// 0 0xA0000 0x100000 0x200000
//
// /-+----------------+------------+------------+------------+---/
// | Application | Miniproc 1 | Miniproc 2 | Miniproc 3 |
// | Code + Globals | Stack | Stack | Stack |
// /-+----------------+------------+------------+------------+---/
// 0x200000 0x280000 0x2C0000 0x300000 0x340000
// | | |
// PROC1_STACK_ADDR | PROC1_STACK_ADDR
// | + 2*PROC_STACK_SIZE
// |
// PROC1_STACK_ADDR
// + PROC_STACK_SIZE
//
// There is also a shared 'cursorpos' variable, located at 0x60000 in the
// kernel's data area. (This is used by 'app_printf' in mpos-app.h.)
// A process descriptor for each possible miniprocess.
// Note that proc_array[0] is never used.
// The main application process descriptor is proc_array[1].
static process_t proc_array[NPROCS];
// A pointer to the currently running process.
// This is kept up to date by the run() function, in mpos-x86.c.
process_t *current;
/*****************************************************************************
* start
*
* Initialize the hardware and process descriptors to empty, then load
* and run the first process.
*
*****************************************************************************/
void
start(void)
{
const char *s;
int whichprocess;
pid_t i;
// Initialize process descriptors as empty
memset(proc_array, 0, sizeof(proc_array));
for (i = 0; i < NPROCS; i++) {
proc_array[i].p_pid = i;
proc_array[i].p_state = P_EMPTY;
proc_array[i].wait_pid = -1; //XIA: initializing waiting pid
}
// The first process has process ID 1.
current = &proc_array[1];
// Set up x86 hardware, and initialize the first process's
// special registers. This only needs to be done once, at boot time.
// All other processes' special registers can be copied from the
// first process.
segments_init();
special_registers_init(current);
// Erase the console, and initialize the cursor-position shared
// variable to point to its upper left.
console_clear();
// Figure out which program to run.
cursorpos = console_printf(cursorpos, 0x0700, "Type '1' to run mpos-app, or '2' to run mpos-app2.");
do {
whichprocess = console_read_digit();
} while (whichprocess != 1 && whichprocess != 2);
console_clear();
// Load the process application code and data into memory.
// Store its entry point into the first process's EIP
// (instruction pointer).
program_loader(whichprocess - 1, ¤t->p_registers.reg_eip);
// Set the main process's stack pointer, ESP.
current->p_registers.reg_esp = PROC1_STACK_ADDR + PROC_STACK_SIZE;
// Mark the process as runnable!
current->p_state = P_RUNNABLE;
// Switch to the main process using run().
run(current);
}
/*****************************************************************************
* interrupt
*
* This is the weensy interrupt and system call handler.
* New system calls are implemented by code in this function.
*
*****************************************************************************/
static pid_t do_fork(process_t *parent);
static pid_t do_newthread(void (*start_function)(void),process_t *parent);
void
interrupt(registers_t *reg)
{
// The processor responds to a system call interrupt by saving some of
// the application's state on the kernel's stack, then jumping to
// kernel assembly code (in mpos-int.S, for your information).
// That code saves more registers on the kernel's stack, then calls
// interrupt(). The first thing we must do, then, is copy the saved
// registers into the 'current' process descriptor.
current->p_registers = *reg;
switch (reg->reg_intno) {
case INT_SYS_GETPID:
// The 'sys_getpid' system call returns the current
// process's process ID. System calls return results to user
// code by putting those results in a register. Like Linux,
// we use %eax for system call return values. The code is
// surprisingly simple:
current->p_registers.reg_eax = current->p_pid;
run(current);
case INT_SYS_FORK:
// The 'sys_fork' system call should create a new process.
// You will have to complete the do_fork() function!
current->p_registers.reg_eax = do_fork(current);
run(current);
case INT_SYS_YIELD:
// The 'sys_yield' system call asks the kernel to schedule a
// different process. (MiniprocOS is cooperatively
// scheduled, so we need a special system call to do this.)
// The schedule() function picks another process and runs it.
schedule();
case INT_SYS_EXIT:
// 'sys_exit' exits the current process, which is marked as
// non-runnable.
// The process stored its exit status in the %eax register
// before calling the system call. The %eax REGISTER has
// changed by now, but we can read the APPLICATION's setting
// for this register out of 'current->p_registers'.
current->p_state = P_ZOMBIE;
current->p_exit_status = current->p_registers.reg_eax;
schedule();
case INT_SYS_WAIT: {
// 'sys_wait' is called to retrieve a process's exit status.
// It's an error to call sys_wait for:
// * A process ID that's out of range (<= 0 or >= NPROCS).
// * The current process.
// * A process that doesn't exist (p_state == P_EMPTY).
// (In the Unix operating system, only process P's parent
// can call sys_wait(P). In MiniprocOS, we allow ANY
// process to call sys_wait(P).)
/*
pid_t p = current->p_registers.reg_eax;
if (p <= 0 || p >= NPROCS || p == current->p_pid
|| proc_array[p].p_state == P_EMPTY)
current->p_registers.reg_eax = -1;
else if (proc_array[p].p_state == P_ZOMBIE){
current->p_registers.reg_eax = proc_array[p].p_exit_status;
proc_array[p].p_state = P_EMPTY;
}
else
current->p_registers.reg_eax = WAIT_TRYAGAIN;
schedule();
*/
pid_t p = current->p_registers.reg_eax;
if (p <= 0 || p >= NPROCS || p == current->p_pid
|| proc_array[p].p_state == P_EMPTY)
current->p_registers.reg_eax = -1;
else if (proc_array[p].p_state == P_ZOMBIE){
current->p_registers.reg_eax = proc_array[p].p_exit_status;
proc_array[p].p_state = P_EMPTY;
}
else
{
current->p_state = P_BLOCKED;
current->wait_pid = p;
}
schedule();
}
case INT_SYS_KILL:{
pid_t p = current->p_registers.reg_eax;
if (p <= 0 || p >= NPROCS || p == current->p_pid
|| proc_array[p].p_state == P_EMPTY)
current->p_registers.reg_eax = -1;
else if(proc_array[p].p_state != P_ZOMBIE){
proc_array[p].p_state = P_ZOMBIE;
proc_array[p].p_exit_status = -1;
current->p_registers.reg_eax = 1;
}
else{
current->p_registers.reg_eax = 1;
}
run(current);
}
//XIA: new thread
case INT_SYS_NEWTHREAD:{
// cursorpos = console_printf(cursorpos, 0x0700, "new Thread in kernel\n");
void (*f)(void) = (void(*)(void)) current->p_registers.reg_eax;
pid_t new = do_newthread(f,current);
current->p_registers.reg_eax = new;
// cursorpos = console_printf(cursorpos, 0x0700, "new Thread in kernel\n");
run(current);
//schedule();//havenot succeed yet
}
default:
while (1)
/* do nothing */;
}
}
/*****************************************************************************
* do_fork
*
* This function actually creates a new process by copying the current
* process's state. In MiniprocOS, a process's state consists of
* (1) its registers, and (2) its stack -- that's it. All processes share
* THE SAME code and global variables. (So really we should call them
* "miniprocesses" or something.)
* The parent process is passed in as an argument. The function should
* return the process ID of the child process, or -1 if the function can't
* create a child process.
* Your job is to fill it in!
*
*****************************************************************************/
//XIA: new thread
static pid_t
do_newthread(void (*start_function)(void),process_t *parent)
{
pid_t new = -1;
int i = 1;
while(i < NPROCS)
{
if(proc_array[i].p_state == P_EMPTY)
{
new = i;
break;
}
i++;
}
if(new == -1)
return -1;
//proc_array[new].p_registers = (*parent).p_registers;
special_registers_init(&proc_array[new]);
//fail to load the program don't have solution yet
proc_array[new].p_registers.reg_eip = (int)start_function;//this line needs modification
proc_array[new].p_registers.reg_esp = PROC1_STACK_ADDR + new * PROC_STACK_SIZE;
proc_array[new].p_state = P_RUNNABLE;
return new;
}
static void copy_stack(process_t *dest, process_t *src);
static pid_t
do_fork(process_t *parent)
{
// YOUR CODE HERE!
// First, find an empty process descriptor. If there is no empty
// process descriptor, return -1. Remember not to use proc_array[0].
// Then, initialize that process descriptor as a running process
// by copying the parent process's registers and stack into the
// child. Copying the registers is simple: they are stored in the
// process descriptor in the 'p_registers' field. Copying the stack
// is a little more involved; see the copy_stack function, below.
// The child process's registers will be equal to the parent's, with
// two differences:
// * reg_esp The child process's stack pointer will point into
// its stack, rather than the parent's. copy_stack
// should arrange this.
// * ??????? There is one other difference. What is it? (Hint:
// What should sys_fork() return to the child process?)
// You need to set one other process descriptor field as well.
// Finally, return the child's process ID to the parent.
pid_t child = -1;
int i = 1;
while(i < NPROCS)
{
if(proc_array[i].p_state == P_EMPTY)
{
child = i;
break;
}
i++;
}
if(child == -1)
return -1;
proc_array[child].p_registers = (*parent).p_registers; //initialize process descriptor
copy_stack(&proc_array[child],parent); //copy stack
proc_array[child].p_state = P_RUNNABLE; //initialize process state
proc_array[child].p_registers.reg_eax = 0; //return 0 to child process
return child;
}
static void
copy_stack(process_t *dest, process_t *src)
{
uint32_t src_stack_bottom, src_stack_top;
uint32_t dest_stack_bottom, dest_stack_top;
// YOUR CODE HERE!
// This function copies the 'src' process's stack into the 'dest'
// process's stack region. Then it sets 'dest's stack pointer to
// correspond to 'src's stack pointer.
// For example, assume that 'src->p_pid == 1' and 'dest->p_pid == 2'.
// Then this code should change this memory setup:
// Miniproc 1 Stack Miniproc 2 Stack
// /--+-------------------+-------------------+--/
// | ABXLQPAOSRJ| |
// /--+-------------------+-------------------+--/
// 0x280000 ^ 0x2C0000 0x300000
// |
// src->p_registers.reg_esp
// == 0x29A4CC
// into this:
// Miniproc 1 Stack Miniproc 2 Stack
// /--+-------------------+-------------------+--/
// | ABXLQPAOSRJ| ABXLQPAOSRJ|
// /--+-------------------+-------------------+--/
// 0x280000 ^ 0x2C0000 ^ 0x300000
// | |
// src->p_registers.reg_esp dest->p_registers.reg_esp
// == 0x29A4CC == 0x2DA4CC
// == 0x300000 + (0x29A4CC - 0x2C0000)
// You may implement this however you like, but we found it easiest
// to express with variables that locate the bottom and top of each
// stack. In our examples, the variables would equal:
// /--+-------------------+-------------------+--/
// | ABXLQPAOSRJ| ABXLQPAOSRJ|
// /--+-------------------+-------------------+--/
// 0x280000 ^ 0x2C0000 ^ 0x300000
// | ^ | ^
// src_stack_bottom | dest_stack_bottom |
// == 0x29A4CC | == 0x2DA4CC |
// src_stack_top dest_stack_top
// == 0x2C0000 == 0x300000
// Your job is to figure out how to calculate these variables,
// and then how to actually copy the stack. (Hint: use memcpy.)
// We have done one for you.
// YOUR CODE HERE!
src_stack_top = PROC1_STACK_ADDR + src->p_pid * PROC_STACK_SIZE; /* YOUR CODE HERE */
src_stack_bottom = src->p_registers.reg_esp;
dest_stack_top = PROC1_STACK_ADDR + dest->p_pid * PROC_STACK_SIZE; /* YOUR CODE HERE */
dest_stack_bottom = dest_stack_top - (src_stack_top - src_stack_bottom);
/* YOUR CODE HERE: calculate based on the other variables */
memcpy((void*)dest_stack_bottom,(void*)src_stack_bottom, (src_stack_top-src_stack_bottom));
dest->p_registers.reg_esp = dest_stack_bottom;
// YOUR CODE HERE: memcpy the stack and set dest->p_registers.reg_esp
}
/*****************************************************************************
* schedule
*
* This is the weensy process scheduler.
* It picks a runnable process, then context-switches to that process.
* If there are no runnable processes, it spins forever.
*
*****************************************************************************/
void
schedule(void)
{
pid_t pid = current->p_pid;
while (1) {
pid = (pid + 1) % NPROCS;
if (proc_array[pid].p_state == P_RUNNABLE)
run(&proc_array[pid]);
else if(proc_array[pid].p_state == P_BLOCKED) //XIA: the process it is waiting have EXIT
{
if(proc_array[(proc_array[pid].wait_pid)].p_state == P_ZOMBIE)
{
proc_array[pid].p_registers.reg_eax = proc_array[proc_array[pid].wait_pid].p_exit_status;
// proc_array[proc_array[pid].wait_pid].p_state = P_EMPTY; //SK:set the zombie process's status as empty
// cursorpos = console_printf(cursorpos, 0x0700, "proc %d is now empty by schedule\n",proc_array[proc_array[pid].wait_pid]);
// cursorpos = console_printf(cursorpos, 0x0700, "proc %d is now scheduled\n",pid);
proc_array[pid].p_state = P_RUNNABLE;
proc_array[pid].wait_pid = -1;
run(&proc_array[pid]);
}
}
}
}