操作系统实验1

Uncategorized
29k words

太累了,不想好好写了,就发个草稿了

前言:操作系统系列实验的blog比较抽象,如有需要可以直接去搬我上传的项目直接用
毕竟这几篇都是汇报检查是我打的草稿,内容很乱
链接如下:
可运行项目文件直达

geekos

键盘驱动

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
//keyboard.c
/*
* 键盘驱动程序
* 版权所有 (c) 2001,2004 David H. Hovemeyer <daveho@cs.umd.edu>
* $版本: 1.14 $
*
* 这是一个自由软件。你可以按照 "COPYING" 文件中的规定
* 使用、重新分发和修改它。
*/

/*
* 信息来源:
* - Frank van Gilluwe 所著的 _The Undocumented PC_ 第二版第8章,
* ISBN 0-201-47950-8。
* - Thom Hogan 所著的 _The Programmers PC Sourcebook_ 第400-409页,
* ISBN 1-55615-118-7。
*/

/*
* 致谢:
* - Peter Gnodde <peter@pcswebdesign.nl> 添加了对
* CTRL 和 ALT 修饰键的支持
*/

/*
* 待办事项:
* - 目前我们假设使用的是83键键盘。
* 应该添加对101+键盘的支持。
* - 应该切换键盘LED。
*/

#include <geekos/kthread.h>
#include <geekos/kassert.h>
#include <geekos/screen.h>
#include <geekos/irq.h>
#include <geekos/io.h>
#include <geekos/keyboard.h>

/* ----------------------------------------------------------------------
* Private data and functions。私有数据和函数
* ---------------------------------------------------------------------- */

/*
* Current shift state.当前的 Shift 状态。
*/
#define LEFT_SHIFT 0x01 // 左Shift键的位掩码
#define RIGHT_SHIFT 0x02 // 右Shift键的位掩码
#define LEFT_CTRL 0x04 // 左Ctrl键的位掩码
#define RIGHT_CTRL 0x08 // 右Ctrl键的位掩码
#define LEFT_ALT 0x10 // 左Alt键的位掩码
#define RIGHT_ALT 0x20 // 右Alt键的位掩码
#define SHIFT_MASK (LEFT_SHIFT | RIGHT_SHIFT) // Shift键的组合状态掩码
#define CTRL_MASK (LEFT_CTRL | RIGHT_CTRL) // Ctrl键的组合状态掩码
#define ALT_MASK (LEFT_ALT | RIGHT_ALT) // Alt键的组合状态掩码
static unsigned s_shiftState = 0; // 当前的修饰键状态,初始值为0

/*
* 键码队列,以防它们到达的速度超过消费者处理的速度。
*/
#define QUEUE_SIZE 256 // 队列大小
#define QUEUE_MASK 0xff // 队列掩码,用于实现环形缓冲区
#define NEXT(index) (((index) + 1) & QUEUE_MASK) // 计算下一个索引,使用环形缓冲区
static Keycode s_queue[QUEUE_SIZE]; // 存储键码的队列
static int s_queueHead, s_queueTail; // 队列的头部和尾部索引

/*
* 等待键盘事件的线程等待队列。
*/
static struct Thread_Queue s_waitQueue;

/*
* 等待键盘事件的线程等待队列。
*/
static const Keycode s_scanTableNoShift[] = {
KEY_UNKNOWN, ASCII_ESC, '1', '2', /* 0x00 - 0x03 */
'3', '4', '5', '6', /* 0x04 - 0x07 */
'7', '8', '9', '0', /* 0x08 - 0x0B */
'-', '=', ASCII_BS, '\t', /* 0x0C - 0x0F */
'q', 'w', 'e', 'r', /* 0x10 - 0x13 */
't', 'y', 'u', 'i', /* 0x14 - 0x17 */
'o', 'p', '[', ']', /* 0x18 - 0x1B */
'\r', KEY_LCTRL, 'a', 's', /* 0x1C - 0x1F */
'd', 'f', 'g', 'h', /* 0x20 - 0x23 */
'j', 'k', 'l', ';', /* 0x24 - 0x27 */
'\'', '`', KEY_LSHIFT, '\\', /* 0x28 - 0x2B */
'z', 'x', 'c', 'v', /* 0x2C - 0x2F */
'b', 'n', 'm', ',', /* 0x30 - 0x33 */
'.', '/', KEY_RSHIFT, KEY_PRINTSCRN, /* 0x34 - 0x37 */
KEY_LALT, ' ', KEY_CAPSLOCK, KEY_F1, /* 0x38 - 0x3B */
KEY_F2, KEY_F3, KEY_F4, KEY_F5, /* 0x3C - 0x3F */
KEY_F6, KEY_F7, KEY_F8, KEY_F9, /* 0x40 - 0x43 */
KEY_F10, KEY_NUMLOCK, KEY_SCRLOCK, KEY_KPHOME, /* 0x44 - 0x47 */
KEY_KPUP, KEY_KPPGUP, KEY_KPMINUS, KEY_KPLEFT, /* 0x48 - 0x4B */
KEY_KPCENTER, KEY_KPRIGHT, KEY_KPPLUS, KEY_KPEND, /* 0x4C - 0x4F */
KEY_KPDOWN, KEY_KPPGDN, KEY_KPINSERT, KEY_KPDEL, /* 0x50 - 0x53 */
KEY_SYSREQ, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x54 - 0x57 */
};
#define SCAN_TABLE_SIZE (sizeof(s_scanTableNoShift) / sizeof(Keycode))
// 扫描表的大小,计算方式为无Shift状态下扫描表的字节大小除以单个键码的大小

/*
* 当按下 Shift 键时,将扫描码转换为键码。
* 请确保与上面的未按下 Shift 键的表保持同步!
* 它们必须具有相同的大小。
*/
static const Keycode s_scanTableWithShift[] = {
KEY_UNKNOWN, ASCII_ESC, '!', '@', /* 0x00 - 0x03 */
'#', '$', '%', '^', /* 0x04 - 0x07 */
'&', '*', '(', ')', /* 0x08 - 0x0B */
'_', '+', ASCII_BS, '\t', /* 0x0C - 0x0F */
'Q', 'W', 'E', 'R', /* 0x10 - 0x13 */
'T', 'Y', 'U', 'I', /* 0x14 - 0x17 */
'O', 'P', '{', '}', /* 0x18 - 0x1B */
'\r', KEY_LCTRL, 'A', 'S', /* 0x1C - 0x1F */
'D', 'F', 'G', 'H', /* 0x20 - 0x23 */
'J', 'K', 'L', ':', /* 0x24 - 0x27 */
'"', '~', KEY_LSHIFT, '|', /* 0x28 - 0x2B */
'Z', 'X', 'C', 'V', /* 0x2C - 0x2F */
'B', 'N', 'M', '<', /* 0x30 - 0x33 */
'>', '?', KEY_RSHIFT, KEY_PRINTSCRN, /* 0x34 - 0x37 */
KEY_LALT, ' ', KEY_CAPSLOCK, KEY_F1, /* 0x38 - 0x3B */
KEY_F2, KEY_F3, KEY_F4, KEY_F5, /* 0x3C - 0x3F */
KEY_F6, KEY_F7, KEY_F8, KEY_F9, /* 0x40 - 0x43 */
KEY_F10, KEY_NUMLOCK, KEY_SCRLOCK, KEY_KPHOME, /* 0x44 - 0x47 */
KEY_KPUP, KEY_KPPGUP, KEY_KPMINUS, KEY_KPLEFT, /* 0x48 - 0x4B */
KEY_KPCENTER, KEY_KPRIGHT, KEY_KPPLUS, KEY_KPEND, /* 0x4C - 0x4F */
KEY_KPDOWN, KEY_KPPGDN, KEY_KPINSERT, KEY_KPDEL, /* 0x50 - 0x53 */
KEY_SYSREQ, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x54 - 0x57 */
};

// 判断队列是否为空
static __inline__ bool Is_Queue_Empty(void)
{
return s_queueHead == s_queueTail;
}

// 判断队列是否已满
static __inline__ bool Is_Queue_Full(void)
{
return NEXT(s_queueTail) == s_queueHead;
}

// 将键码加入队列
static __inline__ void Enqueue_Keycode(Keycode keycode)
{
if (!Is_Queue_Full()) {
s_queue[s_queueTail] = keycode;
s_queueTail = NEXT(s_queueTail);
}
}

// 从队列中取出键码
static __inline__ Keycode Dequeue_Keycode(void)
{
Keycode result;
KASSERT(!Is_Queue_Empty());
result = s_queue[s_queueHead];
s_queueHead = NEXT(s_queueHead);
return result;
}

/*
* 键盘中断处理程序。
*/
static void Keyboard_Interrupt_Handler(struct Interrupt_State* state)
{
uchar_t status, scanCode;
unsigned flag = 0;
bool release = false, shift;
Keycode keycode;

Begin_IRQ(state);

status = In_Byte(KB_CMD);
IO_Delay();

if ((status & KB_OUTPUT_FULL) != 0) {
// 有一个字节可用
scanCode = In_Byte(KB_DATA);
IO_Delay();
/*
* Print("code=%x%s\n", scanCode, (scanCode&0x80) ? " [release]" : "");
*/

if (scanCode & KB_KEY_RELEASE) {
release = true;
scanCode &= ~(KB_KEY_RELEASE);
}

if (scanCode >= SCAN_TABLE_SIZE) {
Print("Unknown scan code: %x\n", scanCode);
goto done;
}

// 处理按键
shift = ((s_shiftState & SHIFT_MASK) != 0);
keycode = shift ? s_scanTableWithShift[scanCode] : s_scanTableNoShift[scanCode];

// 更新 Shift、Ctrl 和 Alt 状态
switch (keycode) {
case KEY_LSHIFT:
flag = LEFT_SHIFT;
break;
case KEY_RSHIFT:
flag = RIGHT_SHIFT;
break;
case KEY_LCTRL:
flag = LEFT_CTRL;
break;
case KEY_RCTRL:
flag = RIGHT_CTRL;
break;
case KEY_LALT:
flag = LEFT_ALT;
break;
case KEY_RALT:
flag = RIGHT_ALT;
break;
default:
goto noflagchange;
}

if (release)
s_shiftState &= ~(flag);
else
s_shiftState |= flag;

/*
* Shift、Ctrl 和 Alt 键不需要排队,标志将被设置!
*/
goto done;

noflagchange:
// 格式化新的键码
if (shift)
keycode |= KEY_SHIFT_FLAG;
if ((s_shiftState & CTRL_MASK) != 0)
keycode |= KEY_CTRL_FLAG;
if ((s_shiftState & ALT_MASK) != 0)
keycode |= KEY_ALT_FLAG;
if (release)
keycode |= KEY_RELEASE_FLAG;

// 将键码放入缓冲区
Enqueue_Keycode(keycode);

// 唤醒事件消费者
Wake_Up(&s_waitQueue);

/*
* 在中断返回时选择一个新线程
* (希望是等待键盘事件的那个)
*/
g_needReschedule = true;
}

done:
End_IRQ(state);
}

/* ----------------------------------------------------------------------
* 公共函数
* ---------------------------------------------------------------------- */

void Init_Keyboard(void)
{
ushort_t irqMask;

Print("初始化键盘...\n");

// 初始状态下没有启用任何 Shift 键
s_shiftState = 0;

// 缓冲区初始为空
s_queueHead = s_queueTail = 0;

// 安装中断处理程序
Install_IRQ(KB_IRQ, Keyboard_Interrupt_Handler);

// 启用 IRQ1(键盘)
irqMask = Get_IRQ_Mask();
irqMask &= ~(1 << KB_IRQ);
Set_IRQ_Mask(irqMask);
}

/*
* 轮询键事件。
* 如果有键可用,返回 true;
* 否则返回 false。如果有键事件可用,
* 它将存储在 keycode 指向的位置。
*/
bool Read_Key(Keycode* keycode)
{
bool result, iflag;

iflag = Begin_Int_Atomic();

result = !Is_Queue_Empty();
if (result) {
*keycode = Dequeue_Keycode();
}

End_Int_Atomic(iflag);

return result;
}

/*
* 等待键码到达。
* 使用键盘等待队列休眠,直到
* 键码到达。
*/
Keycode Wait_For_Key(void)
{
bool gotKey, iflag;
Keycode keycode = KEY_UNKNOWN;

iflag = Begin_Int_Atomic();

do {
gotKey = !Is_Queue_Empty();
if (gotKey)
keycode = Dequeue_Keycode();
else
Wait(&s_waitQueue);
}
while (!gotKey);

End_Int_Atomic(iflag);

return keycode;
}

屏幕驱动?

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
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
/*
* GeekOS 文本屏幕输出
* 版权所有 (c) 2001,2003,2004 David H. Hovemeyer <daveho@cs.umd.edu>
* $版本: 1.24 $
*
* 这是一个自由软件。你可以按照 "COPYING" 文件中的规定
* 使用、重新分发和修改它。
*/

#include <stdarg.h>
#include <geekos/kassert.h>
#include <geekos/ktypes.h>
#include <geekos/io.h>
#include <geekos/int.h>
#include <geekos/fmtout.h>
#include <geekos/screen.h>

/*
* VT100 和 ANSI 转义序列的信息来源:
* - http://www.lns.cornell.edu/~pvhp/dcl/vt100.html
* - http://en.wikipedia.org/wiki/ANSI_escape_code
*/

/* ----------------------------------------------------------------------
* 私有函数和数据
* ---------------------------------------------------------------------- */

#define ESC ((char) 0x1B)
#define DEFAULT_ATTRIBUTE ATTRIB(BLACK, GRAY)

enum State {
S_NORMAL, /* 正常状态 - 输出按原样回显 */
S_ESC, /* 看到 ESC 字符 - 开始输出转义序列 */
S_ESC2, /* 看到 '[' 字符 - 继续输出转义序列 */
S_ARG, /* 扫描一个数字参数 */
S_CMD, /* 命令 */
};

#define MAXARGS 8 /* 转义序列可以传递的最大参数数量 */

struct Console_State {
/* 当前状态信息 */
int row, col;
int saveRow, saveCol;
uchar_t currentAttr;

/* 处理转义序列的工作变量。 */
enum State state;
int argList[MAXARGS];
int numArgs;
};

static struct Console_State s_cons;

#define NUM_SCREEN_DWORDS ((NUMROWS * NUMCOLS * 2) / 4) // 屏幕占用的双字数
#define NUM_SCROLL_DWORDS (((NUMROWS-1) * NUMCOLS * 2) / 4) // 滚动时占用的双字数
#define NUM_DWORDS_PER_LINE ((NUMCOLS*2)/4) // 每行占用的双字数
#define FILL_DWORD (0x00200020 | (s_cons.currentAttr<<24) | (s_cons.currentAttr<<8)) // 用于填充屏幕的双字,包含当前属性

/*
* 滚动显示一行。
* 我们通过一次复制4个字节来加快速度。
*/
static void Scroll(void)
{
uint_t* v;
int i, n = NUM_SCROLL_DWORDS;
uint_t fill = FILL_DWORD;

/* 将第1行到第NUMROWS-1行上移一行。 */
for (v = (uint_t*)VIDMEM, i = 0; i < n; ++i) {
*v = *(v + NUM_DWORDS_PER_LINE);
++v;
}

/* 清除最后一行。 */
for (v = (uint_t*)VIDMEM + n, i = 0; i < NUM_DWORDS_PER_LINE; ++i)
*v++ = fill;
}

/*
* 使用当前属性清除从当前光标位置到行尾的内容。
*/
static void Clear_To_EOL(void)
{
int n = (NUMCOLS - s_cons.col);
uchar_t* v = VIDMEM + s_cons.row*(NUMCOLS*2) + s_cons.col*2;
while (n-- > 0) {
*v++ = ' ';
*v++ = s_cons.currentAttr;
}
}

/*
* 移动到下一行的开头,如果需要则滚动。
*/
static void Newline(void)
{
++s_cons.row;
s_cons.col = 0;
if (s_cons.row == NUMROWS) {
Scroll();
s_cons.row = NUMROWS - 1;
}
}

/*
* 将给定字符的图形表示写到屏幕上的当前位置,
* 使用当前属性,如果需要则滚动。
*/
static void Put_Graphic_Char(int c)
{
uchar_t* v = VIDMEM + s_cons.row*(NUMCOLS*2) + s_cons.col*2;

/* 将字符放在当前位置 */
*v++ = (uchar_t) c;
*v = s_cons.currentAttr;

if (s_cons.col < NUMCOLS - 1)
++s_cons.col;
else
Newline();
}

/*
* 使用当前光标位置和属性将一个字符输出到屏幕上,
* 如果需要则滚动。调用者应在所有字符写入后更新光标位置。
*/
static void Output_Literal_Character(int c)
{
int numSpaces;

switch (c) {
case '\n':
Clear_To_EOL();
Newline();
break;

case '\t':
numSpaces = TABWIDTH - (s_cons.col % TABWIDTH);
while (numSpaces-- > 0)
Put_Graphic_Char(' ');
break;

default:
Put_Graphic_Char(c);
break;
}

#ifndef NDEBUG
/*
* 当使用 --enable-port-e9-hack 编译时,Bochs 会将写入端口 E9 的内容发送到控制台。
* 这对调试非常有帮助,因为它允许在 Bochs 退出后仍然可见调试 Print() 语句。
*/
Out_Byte(0xE9, c);
#endif
}

/*
* 将光标移动到新位置,停在屏幕边界。
*/
static void Move_Cursor(int row, int col)
{
if (row < 0)
row = 0;
else if (row >= NUMROWS)
row = NUMROWS - 1;

if (col < 0)
col = 0;
else if (col >= NUMCOLS)
col = NUMCOLS - 1;

s_cons.row = row;
s_cons.col = col;
}

/*
* 将 ANSI 颜色映射到 VGA 文本模式颜色的表。
*/
static const uchar_t s_ansiToVgaColor[] = {
BLACK, RED, GREEN, AMBER, BLUE, MAGENTA, CYAN, GRAY
};

/*
* 更新转义序列参数指定的属性。
*/
static void Update_Attributes(void)
{
int i;
int attr = s_cons.currentAttr & ~(BRIGHT);

for (i = 0; i < s_cons.numArgs; ++i) {
int value = s_cons.argList[i];
if (value == 0)
attr = DEFAULT_ATTRIBUTE;
else if (value == 1)
attr |= BRIGHT;
else if (value >= 30 && value <= 37)
attr = (attr & ~0x7) | s_ansiToVgaColor[value - 30];
else if (value >= 40 && value <= 47)
attr = (attr & ~(0x7 << 4)) | (s_ansiToVgaColor[value - 40] << 4);
}
s_cons.currentAttr = attr;
}

/* 重置以取消或完成处理转义序列。 */
static void Reset(void)
{
s_cons.state = S_NORMAL;
s_cons.numArgs = 0;
}

/* 开始一个转义序列。 */
static void Start_Escape(void)
{
s_cons.state = S_ESC;
s_cons.numArgs = 0;
}

/* 开始一个转义序列的数字参数。 */
static void Start_Arg(int argNum)
{
KASSERT(s_cons.numArgs == argNum);
s_cons.numArgs++;
s_cons.state = S_ARG;
if (argNum < MAXARGS)
s_cons.argList[argNum] = 0;
}

/* 保存当前光标位置。 */
static void Save_Cursor(void)
{
s_cons.saveRow = s_cons.row;
s_cons.saveCol = s_cons.col;
}

/* 恢复保存的光标位置。 */
static void Restore_Cursor(void)
{
s_cons.row = s_cons.saveRow;
s_cons.col = s_cons.saveCol;
}

/* 将一个数字添加到当前的数字参数。 */
static void Add_Digit(int c)
{
KASSERT(ISDIGIT(c));
if (s_cons.numArgs < MAXARGS) {
int argNum = s_cons.numArgs - 1;
s_cons.argList[argNum] *= 10;
s_cons.argList[argNum] += (c - '0');
}
}

/*
* 获取一个数字参数。
* 如果该参数未实际指定,则返回零。
*/
static int Get_Arg(int argNum)
{
return argNum < s_cons.numArgs ? s_cons.argList[argNum] : 0;
}
/*
* 核心输出函数。
* 根据当前控制台输出状态,
* 执行字符输出或处理部分转义序列。
*/
static void Put_Char_Imp(int c)
{
again:
switch (s_cons.state) {
case S_NORMAL:
if (c == ESC)
Start_Escape();
else
Output_Literal_Character(c);
break;

case S_ESC:
if (c == '[')
s_cons.state = S_ESC2;
else
Reset();
break;

case S_ESC2:
if (ISDIGIT(c)) {
Start_Arg(0);
goto again;
} else if (c == ';') {
/* 特殊情况:对于 "n;m" 命令,如果省略 "n",则隐式为 1 */
Start_Arg(0);
Add_Digit('1');
Start_Arg(1);
} else {
s_cons.state = S_CMD;
goto again;
}
break;

case S_ARG:
if (ISDIGIT(c))
Add_Digit(c);
else if (c == ';')
Start_Arg(s_cons.numArgs);
else {
s_cons.state = S_CMD;
goto again;
}
break;

case S_CMD:
switch (c) {
case 'K': Clear_To_EOL(); break;
case 's': Save_Cursor(); break;
case 'u': Restore_Cursor(); break;
case 'A': Move_Cursor(s_cons.row - Get_Arg(0), s_cons.col); break;
case 'B': Move_Cursor(s_cons.row + Get_Arg(0), s_cons.col); break;
case 'C': Move_Cursor(s_cons.row, s_cons.col + Get_Arg(0)); break;
case 'D': Move_Cursor(s_cons.row, s_cons.col - Get_Arg(0)); break;
case 'm': Update_Attributes(); break;
case 'f': case 'H':
if (s_cons.numArgs == 2) Move_Cursor(Get_Arg(0)-1, Get_Arg(1)-1); break;
case 'J':
if (s_cons.numArgs == 1 && Get_Arg(0) == 2) {
Clear_Screen();
Put_Cursor(0, 0);
}
break;
default: break;
}
Reset();
break;

default:
KASSERT(false);
}
}

/*
* 更新硬件光标的位置。
*/
static void Update_Cursor(void)
{
/*
* 光标位置是从页面内存开始的字符偏移量(我认为)。
*/
uint_t characterPos = (s_cons.row * NUMCOLS) + s_cons.col;
uchar_t origAddr;

/*
* 保存 CRT 地址寄存器的原始内容。
* 修改后恢复其原始值被认为是良好的编程实践。
*/
origAddr = In_Byte(CRT_ADDR_REG);
IO_Delay();

/* 设置高光标位置字节 */
Out_Byte(CRT_ADDR_REG, CRT_CURSOR_LOC_HIGH_REG);
IO_Delay();
Out_Byte(CRT_DATA_REG, (characterPos >> 8) & 0xff);
IO_Delay();

/* 设置低光标位置字节 */
Out_Byte(CRT_ADDR_REG, CRT_CURSOR_LOC_LOW_REG);
IO_Delay();
Out_Byte(CRT_DATA_REG, characterPos & 0xff);
IO_Delay();

/* 恢复 CRT 地址寄存器的内容 */
Out_Byte(CRT_ADDR_REG, origAddr);
}

/* ----------------------------------------------------------------------
* 公共函数
* ---------------------------------------------------------------------- */

/*
* 初始化屏幕模块。
*/
void Init_Screen(void)
{
bool iflag = Begin_Int_Atomic();

s_cons.row = s_cons.col = 0;
s_cons.currentAttr = DEFAULT_ATTRIBUTE;
Clear_Screen();

End_Int_Atomic(iflag);
}

/*
* 使用当前属性清除屏幕。
*/
void Clear_Screen(void)
{
uint_t* v = (uint_t*)VIDMEM;
int i;
uint_t fill = FILL_DWORD;

bool iflag = Begin_Int_Atomic();

for (i = 0; i < NUM_SCREEN_DWORDS; ++i)
*v++ = fill;

End_Int_Atomic(iflag);
}

/*
* 获取当前光标位置。
*/
void Get_Cursor(int* row, int* col)
{
bool iflag = Begin_Int_Atomic();
*row = s_cons.row;
*col = s_cons.col;
End_Int_Atomic(iflag);
}

/*
* 设置当前光标位置。
* 如果成功返回 true,否则返回 false。
*/
bool Put_Cursor(int row, int col)
{
bool iflag;

if (row < 0 || row >= NUMROWS || col < 0 || col >= NUMCOLS)
return false;

iflag = Begin_Int_Atomic();
s_cons.row = row;
s_cons.col = col;
Update_Cursor();
End_Int_Atomic(iflag);

return true;
}

/*
* 获取当前字符属性。
*/
uchar_t Get_Current_Attr(void)
{
return s_cons.currentAttr;
}

/*
* 设置当前字符属性。
*/
void Set_Current_Attr(uchar_t attrib)
{
bool iflag = Begin_Int_Atomic();
s_cons.currentAttr = attrib;
End_Int_Atomic(iflag);
}

/*
* 在当前光标位置使用当前属性写入一个字符,
* 处理滚动、特殊字符等。
*/
void Put_Char(int c)
{
bool iflag = Begin_Int_Atomic();
Put_Char_Imp(c);
Update_Cursor();
End_Int_Atomic(iflag);
}

/*
* 在当前光标位置使用当前属性写入一个字符串。
*/
void Put_String(const char* s)
{
bool iflag = Begin_Int_Atomic();
while (*s != '\0')
Put_Char_Imp(*s++);
Update_Cursor();
End_Int_Atomic(iflag);
}

/*
* 在当前光标位置使用当前属性写入一个字符缓冲区。
*/
void Put_Buf(const char* buf, ulong_t length)
{
bool iflag = Begin_Int_Atomic();
while (length > 0) {
Put_Char_Imp(*buf++);
--length;
}
Update_Cursor();
End_Int_Atomic(iflag);
}

/* 支持 Print()。 */
static void Print_Emit(struct Output_Sink *o, int ch) { Put_Char_Imp(ch); }
static void Print_Finish(struct Output_Sink *o) { Update_Cursor(); }
static struct Output_Sink s_outputSink = { &Print_Emit, &Print_Finish };

/*
* 使用 printf() 样式格式化打印到控制台。
* 调用公共库中的 Format_Output。
*/
void Print(const char *fmt, ...)
{
va_list args;

bool iflag = Begin_Int_Atomic();

va_start(args, fmt);
Format_Output(&s_outputSink, fmt, args);
va_end(args);

End_Int_Atomic(iflag);
}

主函数

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
// main.c
/*
* GeekOS C 代码入口点
* 版权所有 (c) 2001,2003,2004 David H. Hovemeyer <daveho@cs.umd.edu>
* 版权所有 (c) 2003, Jeffrey K. Hollingsworth <hollings@cs.umd.edu>
* 版权所有 (c) 2004, Iulian Neamtiu <neamtiu@cs.umd.edu>
* $版本: 1.51 $
*
* 这是一个自由软件。你可以按照 "COPYING" 文件中的规定
* 使用、重新分发和修改它。
*/

#include <geekos/bootinfo.h>
#include <geekos/string.h>
#include <geekos/screen.h>
#include <geekos/mem.h>
#include <geekos/crc32.h>
#include <geekos/tss.h>
#include <geekos/int.h>
#include <geekos/kthread.h>
#include <geekos/trap.h>
#include <geekos/timer.h>
#include <geekos/keyboard.h>

/*
* 键盘输入处理线程。
*/
void print_key(ulong_t arg)
{
Keycode keycode;
char ch;

while (true) {
// 等待键盘输入
keycode = Wait_For_Key();

// 检查是否为松开事件
if (keycode & KEY_RELEASE_FLAG) {
continue;
}

// 检查是否为 Ctrl + d
if ((keycode & 0xFF) == 'd' && (keycode & KEY_CTRL_FLAG)) {
break;
}

// 将键码转换为字符并显示
ch = (char)(keycode & 0xFF);
Print("%c", ch);
}
}

/*
* 内核 C 代码入口点。
* 初始化内核子系统,挂载文件系统,
* 并生成 init 进程。
*/
void Main(struct Boot_Info* bootInfo)
{
Init_BSS();
Init_Screen();
Init_Mem(bootInfo);
Init_CRC32();
Init_TSS();
Init_Interrupts();
Init_Scheduler();
Init_Traps();
Init_Timer();
Init_Keyboard();

Set_Current_Attr(ATTRIB(BLACK, GREEN|BRIGHT));
Print("Welcome to GeekOS!\n");
Set_Current_Attr(ATTRIB(BLACK, GRAY));

// 启动一个内核线程以回显按下的键并打印计数
Start_Kernel_Thread(print_key, 0, PRIORITY_NORMAL, true);

/* 现在这个线程已经完成。 */
Exit(0);
}


参考:
7.5在屏幕上显示键盘输入

我们趁热打铁,完成GeekOS 的第一个项目。projectO 要求创建一个内核线程响应按键操作,并 将输入的字符显示到屏幕上,当遇到Ctrl+d时结束线程的运行。
7.5.1 实现路径
创建内核线程我们在第六章分析过了,调用函数 Start_Kernel_Thread 即可开始一个新的内核线 程。该函数的参数Thread_Start_Func startFunc就是我们要实现的显示键盘输入的函数。在kthread.h 中找到该参数类型的定义
82 typedef void (*Thread_Start_Func)( ulong_t arg);
原来Thread_Start_Func类型是一个函数指针,指向一个无返回值,带一个类型为ulong_t参数的 函数。所以我们实现的函数需要无返回值,带一个类型为ulong_t参数。
既然是接受键盘输入,那就先看看 GeekOS 给我们提供了哪些键盘接口。位 于./src/geekos/keyboard.h。
122 /*Public functions /
125 void Init_Keyboard(void);
126 bool Read_Key(Keycode
keycode);

Keycode Wait_For_Key(void );
Init_Keyboard在7.4节已经分析过了,我们来看看剩下两个。位于./src/geekos/keyboard.c。
bool Read_Key(Keycode*keycode)
{
bool result,iflag;
iflag =Begin_Int_Atomic(); result =!Is_Queue_Empty(); if (result){
*keycode =Dequeue_Keycode();
}
End_Int_Atomic(iflag);

内部资料请勿外传

302 return result;
303 }
Read_Key 负责从缓冲区队列中取出一个keycode。
310 Keycode Wait_For_Key(void ) 311 {
312 bool gotKey,iflag;
313 Keycode keycode =KEY_UNKNOWN;
315 iflag =Begin_Int_Atomic();
317 do {
318 gotKey =!Is_Queue_Empty();
319 if (gotKey)
320 keycode =Dequeue_Keycode();
321 else
322 Wait(&S_waitQueue); 323 }
324 while (!gotKey);
326 End_Int_Atomic(iflag);
328 return keycode; 329 }
注意,键盘缓冲区属于临界区,所以使用关中断来保护。
Wait_For_Key 也是从缓冲区取数据,如果gotKey为1,说明键盘缓冲区不空,则从缓冲区出一个 键盘码。若gotKey为0则调用Wait将线程置于等待队列s_waitQueue中。7.4节中已知在键盘中断函 数中会唤醒此等待队列。
综合比较这两个接口,明显Wait_For_Key更适合我们的任务。我们的线程只需要调用Wait_For_Key 获取 keycode,再显示出来即可。当没有键盘输入的时候,线程自动进入等待队列,当有键盘操作时, 线程被自动唤醒。
由7.4节可知,keycode是16位无符号数,其高6位是标志位,低8位是按键对应的键码,我们 输出时可以不管其高位,直接按字符输出其低8位即可。记得输出前检测keycode是否是Ctrl+d。
7 .5 .2实现参考
在 main.c文件中 main 函数前实现一个名为 print_key 的函数。根据线程的要求,该函数定义为 void print_key (ulong_t arg);。在函数中实现一个无限循环,循环中调用Wait_For_Key()取得键盘输入, 如果该输入等于’d’|KEY_CTRL_FLAG则跳出循环结束函数,否则将读取到的键盘输入使用Print函数输 入到屏幕上。
在main 函数中把宏TODO 注释掉,在该位置使用Start_Kernel_Thread 增加一个线程。 Start_Kernel_Thread(print_key,0,PRIORITY_NORMAL,true);
按第二章的方法,编译运行。
运行时发现按一个键会打印2个同样的字符,这是因为按下和松开都会产生扫描码,中断处理程 序都识别并压入了缓冲区。

个性化设计

设计了一个内核线程,该线程不仅会显示按键输入,还会统计每个字符的输入次数,并在屏幕上显示统计结果。当遇到 Ctrl + d 时,线程将退出并显示最终的统计结果。

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
#include <geekos/bootinfo.h>
#include <geekos/string.h>
#include <geekos/screen.h>
#include <geekos/mem.h>
#include <geekos/crc32.h>
#include <geekos/tss.h>
#include <geekos/int.h>
#include <geekos/kthread.h>
#include <geekos/trap.h>
#include <geekos/timer.h>
#include <geekos/keyboard.h>

#define MAX_CHAR_COUNT 256

/*
* 键盘输入处理线程。
*/
void print_key(ulong_t arg)
{
Keycode keycode;
char ch;
int charCount[MAX_CHAR_COUNT] = {0};
int i;
while (true) {
// 等待键盘输入
keycode = Wait_For_Key();

// 检查是否为松开事件
if (keycode & KEY_RELEASE_FLAG) {
continue;
}

// 检查是否为 Ctrl + d
if ((keycode & 0xFF) == 'd' && (keycode & KEY_CTRL_FLAG)) {
break;
}

// 将键码转换为字符并显示
ch = (char)(keycode & 0xFF);
Print("%c", ch);

// 更新字符计数
if (ch >= 0 && ch < MAX_CHAR_COUNT) {
charCount[(int)ch]++;
}
}

// 显示最终的字符计数结果
Print("\nCharacter Count:\n");
for (i = 0; i < MAX_CHAR_COUNT; i++) {
if (charCount[i] > 0) {
Print("%c: %d\n", i, charCount[i]);
}
}
}

/*
* 内核 C 代码入口点。
* 初始化内核子系统,挂载文件系统,
* 并生成 init 进程。
*/
void Main(struct Boot_Info* bootInfo)
{
Init_BSS();
Init_Screen();
Init_Mem(bootInfo);
Init_CRC32();
Init_TSS();
Init_Interrupts();
Init_Scheduler();
Init_Traps();
Init_Timer();
Init_Keyboard();

Set_Current_Attr(ATTRIB(BLACK, GREEN|BRIGHT));
Print("Welcome to GeekOS!\n");
Set_Current_Attr(ATTRIB(BLACK, GRAY));

// 启动一个内核线程以回显按下的键并打印计数
Start_Kernel_Thread(print_key, 0, PRIORITY_NORMAL, true);

/* 现在这个线程已经完成。 */
Exit(0);
}

思考题回答

根据提供的讲义内容,以下是针对第6章思考题的回答:

1. 系统的第一个线程是什么?初始化它的时候为何不调用Setup_Kernel_Thread?

回答:

  • 系统的第一个线程是 Main 函数。 这个函数是内核的入口点,负责初始化各种子系统,并启动其他内核线程。
  • 初始化 Main 线程时不调用 Setup_Kernel_Thread 的原因
    • Main 函数是内核的第一个线程,它的上下文和堆栈已经在引导加载程序中设置好了。因此,不需要通过 Setup_Kernel_Thread 来设置其上下文。
    • Main 函数执行时,许多内核子系统(如内存管理、调度器等)还没有完全初始化。如果在这个阶段调用 Setup_Kernel_Thread,可能会导致依赖问题。
    • Main 函数直接控制了内核的启动流程,包括初始化各个子系统和创建其他内核线程。这种控制流确保了所有初始化步骤按顺序执行,避免了并发带来的复杂性。

2. 内核线程结构体中定义了三组指针,它们的作用是什么?

回答:

  • 内核线程结构体 Kernel_Thread 中定义的三组指针及其作用
    • userContext 指针:指向用户态进程上下文。对于内核线程,这个指针为空;对于用户态进程,这个指针指向 User_Context 结构体,用于存储用户态进程的相关信息。
    • stackPage 指针:指向该线程的堆栈页。每个线程都有自己的堆栈空间,用于保存局部变量、函数参数和返回地址等。
    • nextprev 指针:用于将线程链接到链表中。这些指针使得线程可以被组织成链表结构,便于管理和调度。例如,s_runQueue 是一个运行队列,s_allThreadList 是所有线程的链表。

3. 区别 Setup_Kernel_Thread 中的 Push 函数与汇编指令 push

回答:

  • Push 函数

    • Push 函数是 C 语言中的一个函数,位于 kthread.c 文件中。它通过操作线程结构体内部的 esp 指针来模拟对线程堆栈的操作。
    • 具体实现如下:
      1
      2
      3
      4
      5
      static inline void Push(struct Kernel_Thread *kthread, ulong_t value)
      {
      kthread->esp -= 4;
      *((ulong_t *)kthread->esp) = value;
      }
    • Push 函数主要用于在设置新线程的上下文时,将一些初始值压入线程的堆栈中。
  • **汇编指令 push**:

    • push 是 x86 汇编语言中的一个指令,用于将数据压入当前执行线程的堆栈中。
    • 例如,在 Switch_To_Thread 函数中,push 指令用于保存当前线程的上下文信息,以便稍后恢复。

4. Switch_To_Thread 中调整堆栈的目的是什么?

回答:

  • 调整堆栈的目的
    • 保存当前线程的上下文:在切换到新的线程之前,需要保存当前线程的上下文信息,包括寄存器状态、堆栈指针等。这通常是通过将这些信息压入堆栈来完成的。
    • 为新线程准备上下文:调整堆栈以使新线程能够正确地开始执行。这包括将新线程的堆栈指针设置为正确的值,以及将新线程的上下文信息从堆栈中恢复。
    • 模拟中断返回Switch_To_Thread 通过调整堆栈,使其看起来像是从一个中断返回。这样,当新线程开始执行时,它会从正确的地址继续执行,而不会丢失任何上下文信息。

具体来说,Switch_To_Thread 中的堆栈调整步骤包括:

  • 保存当前线程的 eax 寄存器。
  • 获取并调整返回地址。
  • eflagscs 寄存器的值压入堆栈。
  • 保存通用寄存器的状态。
  • 设置新线程的堆栈指针。
  • 恢复新线程的上下文信息。

通过这些步骤,Switch_To_Thread 确保了线程切换过程中的上下文完整性,使得新线程能够顺利地从上次暂停的地方继续执行。

去牛么的实验分析:不想写。。。。。。

好的,根据你提供的实验报告和已完成的实验内容,我将帮助你完成第四部分的实验分析。以下是基于你的实验过程和结果的详细实验分析:

四、实验分析

1. 实验背景

  • 项目简介:本实验基于 GeekOS 操作系统,旨在通过编写和调试内核代码来理解操作系统的基本原理和实现方法。
  • 实验目标
    • 熟悉 GeekOS 实验环境,了解操作系统引导过程。
    • 分析 GeekOS 中内核代码,掌握内核线程的实现原理和中断管理方法。

2. 实现细节

  • 操作系统引导扇区代码

    • 编写了一个简单的引导扇区代码,该代码在启动时显示 “Hello, OS world!”。
    • 使用 NASM 编译器编译生成引导扇区二进制文件,并将其写入软盘镜像文件中。
    • 通过 Bochs 模拟器加载并运行软盘镜像文件,成功显示了预期的欢迎信息。
  • 创建键盘输入处理线程

    • 定义了一个名为 print_key 的内核线程函数,用于从键盘接收按键并在屏幕上显示。
    • 该线程函数使用 Wait_For_Key 函数等待键盘输入,并检查是否为松开事件或特殊组合键(如 Ctrl + d)。
    • 如果是按下事件,则将按键转换为字符并在屏幕上显示。
  • 创建两个打印字符串的线程

    • 创建了两个内核线程,分别打印不同的字符串信息。
    • 通过 Start_Kernel_Thread 函数启动这两个线程,并观察它们在屏幕上的输出行为。
  • 设计个性化的内核线程

    • 设计了一个内核线程,不仅会显示按键输入,还会统计每个字符的输入次数,并在屏幕上显示统计结果。
    • 当遇到 Ctrl + d 时,线程将退出并显示最终的统计结果。

3. 遇到的问题及解决方案

  • 问题1:每次按下按键都会输出打印两个同样的字符。

    • 原因:键盘事件在按下和松开按键时都会产生扫描码,处理程序都识别并压入了缓冲区。
    • 解决方案:通过添加检查是否为松开事件的代码部分解决了问题。具体代码如下:
      1
      2
      3
      if (keycode & KEY_RELEASE_FLAG) {
      continue;
      }
  • 问题2:个性化内核线程设计时,make 报错。

    • 原因for 循环初始声明仅在 C99 模式下允许,但默认编译器模式不是 C99。
    • 解决方案:修改代码将变量 i 声明提前,并使用 -std=c99-std=gnu99 选项进行编译。

4. 实验结果

  • 引导扇区代码:成功编译并运行了引导扇区代码,在 Bochs 模拟器中正确显示了 “Hello, OS world!”。
  • 键盘输入处理线程:实现了从键盘接收按键并在屏幕上显示的功能,能够正确区分按下和松开事件。
  • 打印字符串的线程:成功创建了两个内核线程,分别打印不同的字符串信息,观察到了多任务调度的效果。
  • 个性化内核线程:设计的内核线程不仅能显示按键输入,还能统计每个字符的输入次数,并在退出时显示统计结果。

5. 总结与反思

  • 收获

    • 深入理解了操作系统内核的基本结构和工作原理。
    • 掌握了内核线程的创建和管理方法。
    • 学习了如何处理键盘中断和屏幕输出。
    • 了解了多任务调度的基本概念和实现方法。
  • 不足之处

    • 在某些复杂功能的实现上还存在不足,需要进一步学习和实践。
    • 对于一些底层硬件的操作还不够熟练,需要更多的实践和经验积累。
  • 未来改进方向

    • 继续完善 GeekOS 的其他功能,如文件系统支持、进程调度等。
    • 加深对操作系统底层机制的理解,提高调试和解决问题的能力。

通过这次实验,你不仅学到了许多关于操作系统内核的知识,还积累了宝贵的实践经验。希望你在未来的项目中能够继续提升自己的技能!

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
// 线程1函数:打印字符串1
void print_thread1(ulong_t arg) {
while (1) {
Print("Thread 1: Hello from thread 1!\n");
Yield(); // 主动让出CPU
}
}

// 线程2函数:打印字符串2
void print_thread2(ulong_t arg) {
while (1) {
Print("Thread 2: Hello from thread 2!\n");
Yield(); // 主动让出CPU
}
}

int main(void) {
// 创建并启动线程1
Start_Kernel_Thread(print_thread1, 0, PRIORITY_NORMAL, true);

// 创建并启动线程2
Start_Kernel_Thread(print_thread2, 0, PRIORITY_NORMAL, true);

// 其他初始化代码...
return 0;
}

好的,你的代码创建了两个内核线程 print_thread1print_thread2,它们分别打印不同的字符串信息,并使用 Yield() 函数主动让出 CPU。下面是对你代码的详细分析以及可能的观察结果。

代码分析

线程函数定义

  • **print_thread1**:

    1
    2
    3
    4
    5
    6
    void print_thread1(ulong_t arg) {
    while (1) {
    Print("Thread 1: Hello from thread 1!\n");
    Yield(); // 主动让出CPU
    }
    }
    • 这个线程在无限循环中打印 “Thread 1: Hello from thread 1!”。
    • 每次打印后调用 Yield() 函数,主动让出 CPU,使得其他线程有机会执行。
  • **print_thread2**:

    1
    2
    3
    4
    5
    6
    void print_thread2(ulong_t arg) {
    while (1) {
    Print("Thread 2: Hello from thread 2!\n");
    Yield(); // 主动让出CPU
    }
    }
    • 这个线程在无限循环中打印 “Thread 2: Hello from thread 2!”。
    • 每次打印后调用 Yield() 函数,主动让出 CPU,使得其他线程有机会执行。

创建并启动线程

  • 创建并启动线程1

    1
    Start_Kernel_Thread(print_thread1, 0, PRIORITY_NORMAL, true);
  • 创建并启动线程2

    1
    Start_Kernel_Thread(print_thread2, 0, PRIORITY_NORMAL, true);
    • Start_Kernel_Thread 函数用于创建并启动一个新的内核线程。
    • 参数说明:
      • print_thread1print_thread2:线程函数的入口点。
      • 0:传递给线程函数的参数。
      • PRIORITY_NORMAL:线程的优先级。
      • true:表示线程是否应该立即开始执行。

观察与分析

预期行为

  • 由于两个线程都具有相同的优先级 PRIORITY_NORMAL,并且都调用了 Yield() 函数主动让出 CPU,因此这两个线程应该会交替执行。
  • 在屏幕上,你应该看到 Thread 1: Hello from thread 1!Thread 2: Hello from thread 2! 交替出现。

实际观察

  • 交替输出

    • 如果调度器正常工作,你会看到以下输出(顺序可能会有所不同):
      1
      2
      3
      4
      5
      Thread 1: Hello from thread 1!
      Thread 2: Hello from thread 2!
      Thread 1: Hello from thread 1!
      Thread 2: Hello from thread 2!
      ...
  • 可能的非均匀分布

    • 由于调度器的具体实现和系统负载等因素,你可能会观察到某些情况下一个线程连续执行多次,然后切换到另一个线程。例如:
      1
      2
      3
      4
      5
      Thread 1: Hello from thread 1!
      Thread 1: Hello from thread 1!
      Thread 2: Hello from thread 2!
      Thread 2: Hello from thread 2!
      ...
  • 系统响应

    • 如果系统的其他部分(如中断处理、定时器等)也占用了 CPU 时间,可能会导致线程切换不完全均匀。

调试与验证

  • 确保调度器正常工作

    • 检查 Yield() 函数的实现,确保它正确地将当前线程的状态设置为 READY 并选择下一个可运行的线程。
    • 检查调度器的实现,确保它能够公平地选择线程进行调度。
  • 检查日志输出

    • 可以在 Yield() 函数中添加一些调试信息,记录每次让出 CPU 的时间点,以便更好地理解调度行为。
    • 例如,在 Yield() 中添加:
      1
      Print("Yielding from thread %d\n", GetCurrentThreadId());
  • 调整优先级

    • 尝试改变其中一个线程的优先级,观察其对调度行为的影响。例如:
      1
      2
      Start_Kernel_Thread(print_thread1, 0, PRIORITY_HIGH, true);
      Start_Kernel_Thread(print_thread2, 0, PRIORITY_NORMAL, true);

总结

通过这个实验,你可以观察到多任务调度的基本行为。两个线程通过 Yield() 函数主动让出 CPU,实现了交替执行的效果。实际观察结果可能会因调度器的具体实现和其他系统因素而略有不同。通过调试和验证,可以进一步了解调度器的工作原理和优化方法。

希望这些分析对你有所帮助!如果你有更多具体的问题或需要进一步的帮助,请告诉我。

Comments