从 0 开始写一个操作系统
作者:解琛
时间:2020 年 9 月 6 日
完成了 bootloader 的启动之后,开始初始化系统的串口,来实现对终端的支持。
4.1 CGA 输出
在 kern/driver
文件夹中新建一个 console.c
文件。
4.1.1 cga_init
在 console.c
文件中,对 cga 进行初始化,初始化为文本输出模式,即让系统可以与显示器进行交互,输出文本信息。
CGA 是 Color Graphics Adapter 的缩写,信号类型为 TTL,最多能显示 16 色,显示卡端的接口为 9 针母插座。
这里要明确一件事,我们的 qemu 上面虚拟出来的电脑,黑色的窗口就是它的显示器,后面有一个虚拟的电脑主机。
- 虚拟主机和虚拟显示器(黑色窗口)间通过 CGA 建立连接,可以显示信息;
- 虚拟主机和外部的物理机之间可以通过串口进行交互,虚拟主机可以将信息通过串口方式发送给外部物理机,并将信息显示在外部物理机的终端上。
#include <defs.h>
#include <x86.h>
/***** 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
#define MONO_BASE 0x3B4
#define MONO_BUF 0xB0000
#define CGA_BASE 0x3D4
#define CGA_BUF 0xB8000
#define CRT_ROWS 25
#define CRT_COLS 80
#define CRT_SIZE (CRT_ROWS * CRT_COLS)
#define LPTPORT 0x378
static uint16_t *crt_buf;
static uint16_t crt_pos;
static uint16_t addr_6845;
/* TEXT-mode CGA/VGA display output */
static void
cga_init(void) {
volatile uint16_t *cp = (uint16_t *)CGA_BUF;
uint16_t was = *cp;
*cp = (uint16_t) 0xA55A;
if (*cp != 0xA55A) {
cp = (uint16_t*)MONO_BUF;
addr_6845 = MONO_BASE;
} else {
*cp = was;
addr_6845 = CGA_BASE;
}
// Extract cursor location
uint32_t pos;
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;
}
由于显示的方式是多种多样的,不仅仅可以通过 CGA 一种方式来进行信息的展示,为了后面实现的方便,这里使用函数 cons_init
对多种显示方式的初始化进行封装。
/* cons_init - initializes the console devices */
void
cons_init(void) {
cga_init();
}
接着,在 ucore 总控函数 kern/init/init.c
文件中,对 CGA 支持进行初始化。
#include <defs.h>
#include <console.h>
void kern_init(void) __attribute__((noreturn));
void kern_init(void) {
cons_init(); // init the console
while(1);
}
这样,这台虚拟电脑和虚拟显示器的连接就建立起来了,接着尝试通过 CGA 在虚拟显示器上面展示一些信息。
4.1.2 cga_putc
文件树如下。
jerome@jerome:~/6.本地实验室/4.os/1.动手写一个操作系统/1.终端初始化$ tree
.
├── boot
│ ├── asm.h
│ ├── bootasm.S
│ └── bootmain.c
├── kern
│ ├── driver
│ │ ├── console.c
│ │ └── console.h
│ ├── init
│ │ └── init.c
│ └── libs
│ └── stdio.c
├── libs
│ ├── defs.h
│ ├── elf.h
│ ├── error.h
│ ├── printfmt.c
│ ├── printfmt.h
│ ├── stdarg.h
│ ├── stdio.h
│ ├── string.c
│ ├── string.h
│ └── x86.h
├── Makefile
├── tags
└── tools
├── function.mk
├── kernel.ld
└── sign.c
7 directories, 22 files
首先在 kern/libs/stdio.c
中编写最为关键的一个函数,用来将字符串通过 CGA 打印到虚拟显示器上。
/* cga_putc - print character to console */
static void
cga_putc(int c) {
// set 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;
case '\r':
crt_pos -= (crt_pos % CRT_COLS);
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);
}
接着,将这个接口封装起来,后面如果我们会加入多种打印方式,直接在这个接口里面添加即可。
/* cons_putc - print a single character @c to console devices */
void
cons_putc(int c) {
cga_putc(c);
}
在头文件 ken/driver/console.h
中对这个接口进行声明。
#ifndef __KERN_DRIVER_CONSOLE_H__
#define __KERN_DRIVER_CONSOLE_H__
#include <defs.h>
#include <x86.h>
#include <string.h>
void cons_init(void);
void cons_putc(int c);
#endif /* !__KERN_DRIVER_CONSOLE_H__ */
接着,基于这个接口,我们对上层进行封装。
在 kern/libs/stdio.c
通用 I/O 组件库中,进行以下三层封装。
#include <stdio.h>
#include <console.h>
/* HIGH level console I/O */
/* *
* cputch - writes a single character @c to stdout, and it will
* increace the value of counter pointed by @cnt.
* */
static void
cputch(int c, int *cnt) {
cons_putc(c);
(*cnt) ++;
}
/* *
* vcprintf - format a string and writes it to stdout
*
* The return value is the number of characters which would be
* written to stdout.
*
* Call this function if you are already dealing with a va_list.
* Or you probably want cprintf() instead.
* */
int
vcprintf(const char *fmt, va_list ap) {
int cnt = 0;
vprintfmt((void*)cputch, &cnt, fmt, ap);
return cnt;
}
/* *
* cprintf - formats a string and writes it to stdout
*
* The return value is the number of characters which would be
* written to stdout.
* */
int
cprintf(const char *fmt, ...) {
va_list ap;
int cnt;
va_start(ap, fmt);
cnt = vcprintf(fmt, ap);
va_end(ap);
return cnt;
}
这样,一个可以在终端进行字符串打印的接口 cprintf
就可以正常使用了。
修改 kern/init/init.c
文件,快乐的在显示器上打印我们操作系统的第一个信息。
#include <defs.h>
#include <console.h>
#include <string.h>
#include <stdio.h>
void kern_init(void) __attribute__((noreturn));
void kern_init(void) {
cons_init(); // init the console
cprintf("Hello, OS ~\n");
while(1);
}
编译文件,并启动虚拟机。
moocos-> make
+ cc kern/init/init.c
+ cc kern/libs/stdio.c
+ cc kern/driver/console.c
+ cc libs/printfmt.c
+ cc libs/string.c
+ ld bin/kernel
+ cc boot/bootasm.S
+ cc boot/bootmain.c
+ cc tools/sign.c
+ ld bin/bootblock
'obj/bootblock.out' size: 484 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.0576925 s, 88.7 MB/s
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.000300132 s, 1.7 MB/s
41+1 records in
41+1 records out
21139 bytes (21 kB) copied, 0.000333034 s, 63.5 MB/s
[~/Desktop/1.终端初始化]
moocos-> make qemu
4.2 LPC 总线输出
通过 LPC 总线,虚拟电脑可以将信息发送到物理机的终端,在终端来打印信息。
4.2.1 lpt_putc
在 kern/driver/console.c
中定义 lpt 字符串输出的基本方法。
static void
lpt_putc_sub(int c) {
int i;
for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) {
delay();
}
outb(LPTPORT + 0, c);
outb(LPTPORT + 2, 0x08 | 0x04 | 0x01);
outb(LPTPORT + 2, 0x08);
}
/* lpt_putc - copy console output to parallel port */
static void
lpt_putc(int c) {
if (c != '\b') {
lpt_putc_sub(c);
}
else {
lpt_putc_sub('\b');
lpt_putc_sub(' ');
lpt_putc_sub('\b');
}
}
接着,将这个接口封装到通用的字符输出函数里面去。
/* cons_putc - print a single character @c to console devices */
void
cons_putc(int c) {
cga_putc(c);
serial_putc(c);
}
4.3 串口输出
同理,串口交互的实现步骤也是一样的。
4.3.2 serial_init
在 kern/driver/console.c
中,对串口进行初始化。
static bool serial_exists = 0;
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);
if (serial_exists) {
/* pic_enable(IRQ_COM1); */
}
}
目前不关注中断部分,所以这里预留一个 pic 使能的接口在这里。
接着,我们将中断的初始化,放到终端设备的初始化通用接口里。
/* cons_init - initializes the console devices */
void
cons_init(void) {
cga_init();
serial_init();
}
4.3.3 serial_putc
在 kern/driver/console.c
中,对串口输出字符串的基本方法进行编写。
/* stupid I/O delay routine necessitated by historical PC design flaws */
static void
delay(void) {
inb(0x84);
inb(0x84);
inb(0x84);
inb(0x84);
}
static void
serial_putc_sub(int c) {
int i;
for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) {
delay();
}
outb(COM1 + COM_TX, c);
}
/* serial_putc - print character to serial port */
static void
serial_putc(int c) {
if (c != '\b') {
serial_putc_sub(c);
}
else {
serial_putc_sub('\b');
serial_putc_sub(' ');
serial_putc_sub('\b');
}
}
接着,将串口的输出放到终端通用输出接口里面。
/* cons_putc - print a single character @c to console devices */
void
cons_putc(int c) {
lpt_putc(c);
cga_putc(c);
serial_putc(c);
}
4.4 串口中断输入捕获
以上三种方式是用于对终端输出信息的处理,再配合中断的输入捕获,一个终端就初具雏形了。
4.4.1 serial_intr
在 kern/driver/console.c
中编写串口中断的处理。
/* *
* 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;
/* *
* cons_intr - called by device interrupt routines to feed input
* characters into the circular console input buffer.
* */
static void
cons_intr(int (*proc)(void)) {
int c;
while ((c = (*proc)()) != -1) {
if (c != 0) {
cons.buf[cons.wpos ++] = c;
if (cons.wpos == CONSBUFSIZE) {
cons.wpos = 0;
}
}
}
}
/* serial_proc_data - get data from serial port */
static int
serial_proc_data(void) {
if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) {
return -1;
}
int c = inb(COM1 + COM_RX);
if (c == 127) {
c = '\b';
}
return c;
}
/* serial_intr - try to feed input characters from serial port */
void
serial_intr(void) {
if (serial_exists) {
cons_intr(serial_proc_data);
}
}
通过 serial_intr
函数实现了串口中断有效信息的捕获,接着将这个函数封装到一个通用接口里。
/* *
* cons_getc - return the next input character from console,
* or 0 if none waiting.
* */
int
cons_getc(void) {
// 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();
return 0;
}
4.5 键盘中断输入捕获
4.5.1 kbd_init
首先,定义键盘的中断函数。
键盘寄存器对应的键位关系定义在头文件 kern\driver\kbdreg.h
中。
/***** 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
};
/* *
* kbd_proc_data - get data from keyboard
*
* The kbd_proc_data() function gets data from the keyboard.
* If we finish a character, return it, else 0. And return -1 if no data.
* */
static int
kbd_proc_data(void) {
int c;
uint8_t data;
static uint32_t shift;
if ((inb(KBSTATP) & KBS_DIB) == 0) {
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;
}
这里,我们定义了 Ctrl + Alt + Del
用于重启系统,方便最后的测试。
接着,将键盘输入捕获的字符串放到终端的中断中。
/* kbd_intr - try to feed input characters from keyboard */
static void
kbd_intr(void) {
cons_intr(kbd_proc_data);
}
完成键盘中断的初始化函数之后,对中断进行使能。
4.5.2 pic_init
在 kern/driver/picirq.c
文件中对 pic 的初始化即中断开启方式进行声明。
#include <picirq.h>
// I/O Addresses of the two programmable interrupt controllers
#define IO_PIC1 0x20 // Master (IRQs 0-7)
#define IO_PIC2 0xA0 // Slave (IRQs 8-15)
#define IRQ_SLAVE 2 // IRQ at which slave connects to master
// Current IRQ mask.
// Initial IRQ mask has interrupt 2 enabled (for slave 8259A).
static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE);
static bool did_init = 0;
static void
pic_setmask(uint16_t mask) {
irq_mask = mask;
if (did_init) {
outb(IO_PIC1 + 1, mask);
outb(IO_PIC2 + 1, mask >> 8);
}
}
void
pic_enable(unsigned int irq) {
pic_setmask(irq_mask & ~(1 << irq));
}
/* pic_init - initialize the 8259A interrupt controllers */
void
pic_init(void) {
did_init = 1;
// mask all interrupts
outb(IO_PIC1 + 1, 0xFF);
outb(IO_PIC2 + 1, 0xFF);
// Set up master (8259A-1)
// ICW1: 0001g0hi
// g: 0 = edge triggering, 1 = level triggering
// h: 0 = cascaded PICs, 1 = master only
// i: 0 = no ICW4, 1 = ICW4 required
outb(IO_PIC1, 0x11);
// ICW2: Vector offset
outb(IO_PIC1 + 1, IRQ_OFFSET);
// ICW3: (master PIC) bit mask of IR lines connected to slaves
// (slave PIC) 3-bit # of slave's connection to master
outb(IO_PIC1 + 1, 1 << IRQ_SLAVE);
// ICW4: 000nbmap
// n: 1 = special fully nested mode
// b: 1 = buffered mode
// m: 0 = slave PIC, 1 = master PIC
// (ignored when b is 0, as the master/slave role
// can be hardwired).
// a: 1 = Automatic EOI mode
// p: 0 = MCS-80/85 mode, 1 = intel x86 mode
outb(IO_PIC1 + 1, 0x3);
// Set up slave (8259A-2)
outb(IO_PIC2, 0x11); // ICW1
outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2
outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3
// NB Automatic EOI mode doesn't tend to work on the slave.
// Linux source code says it's "to be investigated".
outb(IO_PIC2 + 1, 0x3); // ICW4
// OCW3: 0ef01prs
// ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask
// p: 0 = no polling, 1 = polling mode
// rs: 0x = NOP, 10 = read IRR, 11 = read ISR
outb(IO_PIC1, 0x68); // clear specific mask
outb(IO_PIC1, 0x0a); // read IRR by default
outb(IO_PIC2, 0x68); // OCW3
outb(IO_PIC2, 0x0a); // OCW3
if (irq_mask != 0xFFFF) {
pic_setmask(irq_mask);
}
}
在头文件中对相关的外部接口进行声明。
#ifndef __KERN_DRIVER_PICIRQ_H__
#define __KERN_DRIVER_PICIRQ_H__
#include <defs.h>
#include <x86.h>
void pic_init(void);
void pic_enable(unsigned int irq);
#define IRQ_OFFSET 32
#endif /* !__KERN_DRIVER_PICIRQ_H__ */
修改 kern/driver/console.h
文件,引用 pic 的头文件。
#ifndef __KERN_DRIVER_CONSOLE_H__
#define __KERN_DRIVER_CONSOLE_H__
#include <defs.h>
#include <x86.h>
#include <string.h>
#include <kbdreg.h>
#include <stdio.h>
#include <picirq.h>
void cons_init(void);
void cons_putc(int c);
void serial_intr(void);
#endif /* !__KERN_DRIVER_CONSOLE_H__ */
在 kern\init\init.c
中对 pic 进行初始化。
#include <defs.h>
#include <console.h>
#include <string.h>
#include <stdio.h>
#include <picirq.h>
void kern_init(void) __attribute__((noreturn));
void kern_init(void) {
cons_init(); // init the console
cprintf("Hello, jerome ~\n");
pic_init(); // init interrupt controller
while(1);
}
在 kern/trap/trap.h
中对键盘中断进行定义。
#ifndef __KERN_TRAP_TRAP_H__
#define __KERN_TRAP_TRAP_H__
#include <defs.h>
/* Trap Numbers */
/* Processor-defined: */
#define T_DIVIDE 0 // divide error
#define T_DEBUG 1 // debug exception
#define T_NMI 2 // non-maskable interrupt
#define T_BRKPT 3 // breakpoint
#define T_OFLOW 4 // overflow
#define T_BOUND 5 // bounds check
#define T_ILLOP 6 // illegal opcode
#define T_DEVICE 7 // device not available
#define T_DBLFLT 8 // double fault
// #define T_COPROC 9 // reserved (not used since 486)
#define T_TSS 10 // invalid task switch segment
#define T_SEGNP 11 // segment not present
#define T_STACK 12 // stack exception
#define T_GPFLT 13 // general protection fault
#define T_PGFLT 14 // page fault
// #define T_RES 15 // reserved
#define T_FPERR 16 // floating point error
#define T_ALIGN 17 // aligment check
#define T_MCHK 18 // machine check
#define T_SIMDERR 19 // SIMD floating point error
#define T_SYSCALL 0x80 // SYSCALL, ONLY FOR THIS PROJ
/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */
#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET
#define IRQ_TIMER 0
#define IRQ_KBD 1
#define IRQ_COM1 4
#define IRQ_IDE1 14
#define IRQ_IDE2 15
#define IRQ_ERROR 19
#define IRQ_SPURIOUS 31
/* *
* These are arbitrarily chosen, but with care not to overlap
* processor defined exceptions or interrupt vectors.
* */
#define T_SWITCH_TOU 120 // user/kernel switch
#define T_SWITCH_TOK 121 // user/kernel switch
#endif /* !__KERN_TRAP_TRAP_H__ */
引入 trap.h
,在键盘初始化的地方,对 pic 进行使能。
static void
kbd_init(void) {
// drain the kbd buffer
kbd_intr();
pic_enable(IRQ_KBD);
}
在终端输入字符串捕获的通用接口里,对键盘输入的中断捕获进行初始化。
/* *
* cons_getc - return the next input character from 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;
}
在终端设备的初始化里,加入键盘的初始化。
/* cons_init - initializes the console devices */
void
cons_init(void) {
cga_init();
serial_init();
kbd_init();
if (!serial_exists) {
cprintf("serial port does not exist!!\n");
}
}