console

“Hello, it’s nice to meet you”

控制台是一个操作系统必备的上层程序,负责和用户之间的交互,这里以xv6程序中的控制台为例,总结一个基本控制台程序的要素和功能。

概述

一个完善的控制台程序结构应当如下:

图片名称

这个程序对底层硬件进行了很好的封装和隔离,并提供了设备无关代码和上层的读写函数。这个结构可以在下面的xv6控制台源码中很好地体现出来。

xv6控制台源码

点击显示源代码内容
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
/* See COPYRIGHT for copyright information. */

#include <inc/x86.h>
#include <inc/memlayout.h>
#include <inc/kbdreg.h>
#include <inc/string.h>
#include <inc/assert.h>

#include <kern/console.h>

static void cons_intr(int (*proc)(void));
static void cons_putc(int c);

// 一个延时函数
// Stupid I/O delay routine necessitated by historical PC design flaws
static void
delay(void)
{
inb(0x84);
inb(0x84);
inb(0x84);
inb(0x84);
}

/***** Serial I/O code *****/
// 一些端口及寄存器的定义
#define COM1 0x3F8

#define COM_RX 0 // In: Receive buffer (DLAB=0)
#define COM_TX 0 // Out: Transmit buffer (DLAB=0)
#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1)
#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1)
#define COM_IER 1 // Out: Interrupt Enable Register
#define COM_IER_RDI 0x01 // Enable receiver data interrupt
#define COM_IIR 2 // In: Interrupt ID Register
#define COM_FCR 2 // Out: FIFO Control Register
#define COM_LCR 3 // Out: Line Control Register
#define COM_LCR_DLAB 0x80 // Divisor latch access bit
#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits
#define COM_MCR 4 // Out: Modem Control Register
#define COM_MCR_RTS 0x02 // RTS complement
#define COM_MCR_DTR 0x01 // DTR complement
#define COM_MCR_OUT2 0x08 // Out2 complement
#define COM_LSR 5 // In: Line Status Register
#define COM_LSR_DATA 0x01 // Data available
#define COM_LSR_TXRDY 0x20 // Transmit buffer avail
#define COM_LSR_TSRE 0x40 // Transmitter off

static bool serial_exists; // 串口是否存在

static int
serial_proc_data(void) // 从串口读取数据
{
if (!(inb(COM1+COM_LSR) & COM_LSR_DATA)) // 读状态并且数据available,也许关中断就看不到这些了?
return -1;
return inb(COM1+COM_RX); // 从寄存器中读取
}

void
serial_intr(void)
{
if (serial_exists)
cons_intr(serial_proc_data); //串口中断并调用读取数据函数
}

static void
serial_putc(int c) // 向串口中写数据
{
int i;

for (i = 0;
!(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800;
i++)
delay();
// 如果符合写条件,那么不循环,直接写
// 如果不符合写条件且等待时间未到,那么等待
// 如果不符合写条件,但等待时间超时,写一个int

outb(COM1 + COM_TX, c);
}

static void
serial_init(void) // 串口初始化
{
// Turn off the FIFO
outb(COM1+COM_FCR, 0);

// Set speed; requires DLAB latch
outb(COM1+COM_LCR, COM_LCR_DLAB);
outb(COM1+COM_DLL, (uint8_t) (115200 / 9600)); // 设置波特率
outb(COM1+COM_DLM, 0);

// 8 data bits, 1 stop bit, parity off; turn off DLAB latch
outb(COM1+COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB);

// No modem controls
outb(COM1+COM_MCR, 0);
// Enable rcv interrupts
outb(COM1+COM_IER, COM_IER_RDI);

// Clear any preexisting overrun indications and interrupts
// Serial port doesn't exist if COM_LSR returns 0xFF
serial_exists = (inb(COM1+COM_LSR) != 0xFF);
(void) inb(COM1+COM_IIR);
(void) inb(COM1+COM_RX);

}



/***** Parallel port output code *****/
// For information on PC parallel port programming, see the class References
// page. LPT line printer terminal,早期的终端是打印在纸上的,所以是LPT
// 并行口输出

// 0x378 是并行口的地址

static void
lpt_putc(int c)
{
int i;

for (i = 0; !(inb(0x378+1) & 0x80) && i < 12800; i++)
delay();
outb(0x378+0, c);
outb(0x378+2, 0x08|0x04|0x01);
outb(0x378+2, 0x08);
}




/***** Text-mode CGA/VGA display output *****/
// CGA:彩色图形适配器
// VGA:视频图像阵列
static unsigned addr_6845;
static uint16_t *crt_buf;
static uint16_t crt_pos;

static void
cga_init(void) // CGA初始化
{
volatile uint16_t *cp;
uint16_t was;
unsigned pos;

cp = (uint16_t*) (KERNBASE + CGA_BUF);
was = *cp;
*cp = (uint16_t) 0xA55A;
if (*cp != 0xA55A) {
cp = (uint16_t*) (KERNBASE + MONO_BUF);
addr_6845 = MONO_BASE;
} else {
*cp = was;
addr_6845 = CGA_BASE;
}

/* Extract cursor location */
outb(addr_6845, 14);
pos = inb(addr_6845 + 1) << 8;
outb(addr_6845, 15);
pos |= inb(addr_6845 + 1);

crt_buf = (uint16_t*) cp;
crt_pos = pos;
}



static void
cga_putc(int c) //向CGA写c
{
// if no attribute given, then use black on white
if (!(c & ~0xFF))
c |= 0x0700;

switch (c & 0xff) {
case '\b':
if (crt_pos > 0) {
crt_pos--;
crt_buf[crt_pos] = (c & ~0xff) | ' ';
}
break;
case '\n':
crt_pos += CRT_COLS;
/* fallthru */
case '\r':
crt_pos -= (crt_pos % CRT_COLS);
break;
case '\t':
cons_putc(' ');
cons_putc(' ');
cons_putc(' ');
cons_putc(' ');
cons_putc(' ');
break;
default:
crt_buf[crt_pos++] = c; /* write the character */
break;
}

// What is the purpose of this?
if (crt_pos >= CRT_SIZE) {
int i;

memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
crt_buf[i] = 0x0700 | ' ';
crt_pos -= CRT_COLS;
}

/* move that little blinky thing */
outb(addr_6845, 14);
outb(addr_6845 + 1, crt_pos >> 8);
outb(addr_6845, 15);
outb(addr_6845 + 1, crt_pos);
}


/***** Keyboard input code *****/
// 键盘输入
#define NO 0

#define SHIFT (1<<0)
#define CTL (1<<1)
#define ALT (1<<2)

#define CAPSLOCK (1<<3)
#define NUMLOCK (1<<4)
#define SCROLLLOCK (1<<5)

#define E0ESC (1<<6)

// 字符映射
static uint8_t shiftcode[256] =
{
[0x1D] = CTL,
[0x2A] = SHIFT,
[0x36] = SHIFT,
[0x38] = ALT,
[0x9D] = CTL,
[0xB8] = ALT
};

static uint8_t togglecode[256] =
{
[0x3A] = CAPSLOCK,
[0x45] = NUMLOCK,
[0x46] = SCROLLLOCK
};

static uint8_t normalmap[256] =
{
NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00
'7', '8', '9', '0', '-', '=', '\b', '\t',
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10
'o', 'p', '[', ']', '\n', NO, 'a', 's',
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20
'\'', '`', NO, '\\', 'z', 'x', 'c', 'v',
'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30
NO, ' ', NO, NO, NO, NO, NO, NO,
NO, NO, NO, NO, NO, NO, NO, '7', // 0x40
'8', '9', '-', '4', '5', '6', '+', '1',
'2', '3', '0', '.', NO, NO, NO, NO, // 0x50
[0xC7] = KEY_HOME, [0x9C] = '\n' /*KP_Enter*/,
[0xB5] = '/' /*KP_Div*/, [0xC8] = KEY_UP,
[0xC9] = KEY_PGUP, [0xCB] = KEY_LF,
[0xCD] = KEY_RT, [0xCF] = KEY_END,
[0xD0] = KEY_DN, [0xD1] = KEY_PGDN,
[0xD2] = KEY_INS, [0xD3] = KEY_DEL
};

static uint8_t shiftmap[256] =
{
NO, 033, '!', '@', '#', '$', '%', '^', // 0x00
'&', '*', '(', ')', '_', '+', '\b', '\t',
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10
'O', 'P', '{', '}', '\n', NO, 'A', 'S',
'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20
'"', '~', NO, '|', 'Z', 'X', 'C', 'V',
'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30
NO, ' ', NO, NO, NO, NO, NO, NO,
NO, NO, NO, NO, NO, NO, NO, '7', // 0x40
'8', '9', '-', '4', '5', '6', '+', '1',
'2', '3', '0', '.', NO, NO, NO, NO, // 0x50
[0xC7] = KEY_HOME, [0x9C] = '\n' /*KP_Enter*/,
[0xB5] = '/' /*KP_Div*/, [0xC8] = KEY_UP,
[0xC9] = KEY_PGUP, [0xCB] = KEY_LF,
[0xCD] = KEY_RT, [0xCF] = KEY_END,
[0xD0] = KEY_DN, [0xD1] = KEY_PGDN,
[0xD2] = KEY_INS, [0xD3] = KEY_DEL
};

#define C(x) (x - '@')

static uint8_t ctlmap[256] =
{
NO, NO, NO, NO, NO, NO, NO, NO,
NO, NO, NO, NO, NO, NO, NO, NO,
C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'),
C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'),
C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO,
NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'),
C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO,
[0x97] = KEY_HOME,
[0xB5] = C('/'), [0xC8] = KEY_UP,
[0xC9] = KEY_PGUP, [0xCB] = KEY_LF,
[0xCD] = KEY_RT, [0xCF] = KEY_END,
[0xD0] = KEY_DN, [0xD1] = KEY_PGDN,
[0xD2] = KEY_INS, [0xD3] = KEY_DEL
};

static uint8_t *charcode[4] = {
normalmap,
shiftmap,
ctlmap,
ctlmap
};

/*
* Get data from the keyboard. If we finish a character, return it. Else 0.
* Return -1 if no data. 从键盘上读取字符
*/
static int
kbd_proc_data(void)
{
int c;
uint8_t stat, data;
static uint32_t shift;

stat = inb(KBSTATP);
if ((stat & KBS_DIB) == 0)
return -1;
// Ignore data from mouse.
if (stat & KBS_TERR)
return -1;

data = inb(KBDATAP);

if (data == 0xE0) {
// E0 escape character
shift |= E0ESC;
return 0;
} else if (data & 0x80) {
// Key released
data = (shift & E0ESC ? data : data & 0x7F);
shift &= ~(shiftcode[data] | E0ESC);
return 0;
} else if (shift & E0ESC) {
// Last character was an E0 escape; or with 0x80
data |= 0x80;
shift &= ~E0ESC;
}

shift |= shiftcode[data];
shift ^= togglecode[data];

c = charcode[shift & (CTL | SHIFT)][data];
if (shift & CAPSLOCK) {
if ('a' <= c && c <= 'z')
c += 'A' - 'a';
else if ('A' <= c && c <= 'Z')
c += 'a' - 'A';
}

// Process special keys
// Ctrl-Alt-Del: reboot
if (!(~shift & (CTL | ALT)) && c == KEY_DEL) {
cprintf("Rebooting!\n");
outb(0x92, 0x3); // courtesy of Chris Frost
}

return c;
}

// 键盘中断
void
kbd_intr(void)
{
cons_intr(kbd_proc_data);
}

// 键盘初始化
static void
kbd_init(void)
{
}



/***** General device-independent console code *****/
// Here we manage the console input buffer,
// where we stash characters received from the keyboard or serial port
// whenever the corresponding interrupt occurs.
// 设备无关的控制台代码,上面都是各种驱动
#define CONSBUFSIZE 512

static struct {
uint8_t buf[CONSBUFSIZE];
uint32_t rpos;
uint32_t wpos;
} cons;

// called by device interrupt routines to feed input characters
// into the circular console input buffer. 控制台中断,将控制台程序写入buf中
static void
cons_intr(int (*proc)(void))
{
int c;

while ((c = (*proc)()) != -1) {
if (c == 0)
continue;
cons.buf[cons.wpos++] = c;
if (cons.wpos == CONSBUFSIZE)
cons.wpos = 0;
}
}

// return the next input character from the console, or 0 if none waiting
// 从控制台中读取字符
int
cons_getc(void)
{
int c;

// poll for any pending input characters,
// so that this function works even when interrupts are disabled
// (e.g., when called from the kernel monitor).
serial_intr(); //
kbd_intr(); //

// grab the next character from the input buffer.
if (cons.rpos != cons.wpos) {
c = cons.buf[cons.rpos++];
if (cons.rpos == CONSBUFSIZE)
cons.rpos = 0;
return c;
}
return 0;
}

// output a character to the console
// 将字符输出至控制台
static void
cons_putc(int c)
{
serial_putc(c); // 串口
lpt_putc(c); // LPT
cga_putc(c); // CGA 照顾到了各种设备
}

// initialize the console devices
// 控制台设备初始化
void
cons_init(void)
{
cga_init();
kbd_init();
serial_init();

if (!serial_exists)
cprintf("Serial port does not exist!\n");
}


// `High'-level console I/O. Used by readline and cprintf.
// 高层封装的控制台IO,用于隔离硬件
void
cputchar(int c)
{
cons_putc(c); //输出字符
}

int
getchar(void)
{
int c;
while ((c = cons_getc()) == 0) // 是0就一直等待,直到读取到了内容为止
/* do nothing */;
return c;
}

int
iscons(int fdnum) // 是控制台吗
{
// used by readline
return 1;
}

参考文献

0%