主要就是通过中断驱动和队列缓冲实现了高效的数据处理。
业务处理与硬件操作解耦
这个也就是操作系统的生产者和消费者的问题:
中断处理程序(生产者):这个在中断发生立即执行(硬件级别的很快),从硬件0x0060键盘数据端口读数据(键盘鼠标一样的端口),存入缓冲区
主循环(消费者):就从缓冲区取数据,在共享一个缓冲区时,防止中断处理程序与主程序同时访问,关开中断保护临界区
而中断程序的话:
io_in8/io_out8
) 是x86架构的原子指令int fifo8_put(struct FIFO8 *fifo, unsigned char data)
{
if (fifo->free == 0) {
fifo->flags |= FLAGS_OVERRUN;
return -1;
}
fifo->buf[fifo->p] = data;//单次写入
fifo->p++;
if (fifo->p == fifo->size) {
fifo->p = 0;
}
fifo->free--;//原子递减
return 0;
}
中断处理程序执行时间 (约100ns)
↓
FIFO写入操作 (约20ns)
↓
中断结束通知 (约10ns)
数据缓冲区(FIFO队列)
引导程序初始化环境,中断处理程序快速响应,主循环处理业务逻辑,图形模块负责显示
asmhead.nas这个引导程序完成了这两个内容
; 初始化PIC控制器
;仅做临时屏蔽,缺少ICW初始化
;为了在系统初始化关键阶段防止中断干扰,确保引导过程顺利进行
MOV AL,0xff ; 屏蔽所有中断
OUT 0x21,AL ; 主PIC
NOP ; 短暂延迟
OUT 0xa1,AL ; 从PIC
CLI ; 禁用CPU中断
; 在切换保护模式时需保证原子操作
LGDT [GDTR0] ; 加载全局描述符表
MOV EAX,CR0
AND EAX,0x7fffffff ; 禁用分页
OR EAX,0x00000001 ; 启用保护模式位
MOV CR0,EAX
JMP pipelineflush ; 清空流水线
然后就是初始化pic
/ 初始化PIC(可编程中断控制器)
void init_pic(void)
{
/* 初始化主PIC(PIC0)和从PIC(PIC1)*/
io_out8(PIC0_IMR, 0xff); // 屏蔽所有中断(主)
io_out8(PIC1_IMR, 0xff); // 屏蔽所有中断(从)
// 主PIC初始化
io_out8(PIC0_ICW1, 0x11); // 边沿触发 + 级联模式
io_out8(PIC0_ICW2, 0x20); // IRQ0-7映射到INT 20-27
io_out8(PIC0_ICW3, 1 << 2); // 从PIC连接在IRQ2
io_out8(PIC0_ICW4, 0x01); // 非缓冲模式
// 从PIC初始化
io_out8(PIC1_ICW1, 0x11); // 边沿触发 + 级联模式
io_out8(PIC1_ICW2, 0x28); // IRQ8-15映射到INT 28-2f
io_out8(PIC1_ICW3, 2); // 连接主PIC的IRQ2
io_out8(PIC1_ICW4, 0x01); // 非缓冲模式
// 允许级联中断
io_out8(PIC0_IMR, 0xfb); // 11111011 仅允许PIC1中断
io_out8(PIC1_IMR, 0xff); // 11111111 暂时屏蔽所有从PIC中断
}
鼠标中断处理程序和键盘中断处理程序:
/**中断处理程序要尽可能快,所以它们只负责将数据存入队列,而不进行复杂处理,这提高了响应速度。**/
// 键盘中断处理(IRQ1)
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61); // 通知PIC0 IRQ-01处理完成
data = io_in8(PORT_KEYDAT); // 读取键盘数据(0x60端口)
fifo8_put(&keyfifo, data); // 存入键盘缓冲区
}
// 鼠标中断处理(IRQ12)
void inthandler2c(int *esp)
{
unsigned char data;
io_out8(PIC1_OCW2, 0x64); // 通知PIC1 IRQ-12处理完成
io_out8(PIC0_OCW2, 0x62); // 通知PIC0级联中断完成
data = io_in8(PORT_KEYDAT); // PS/2鼠标共用键盘数据端口
fifo8_put(&mousefifo, data); // 存入鼠标缓冲区
}
然后主程序是这样的:
操作显存显示图像的就不用管,鼠标显示的内容看一下就够了
其他就是保护临界区,在键盘那个端口取数据然后显示了
for (;;) {
io_cli();// 关闭中断(保护临界区)
// 检查输入缓冲区状态
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
io_stihlt();// 开启中断并进入休眠(节能模式)
} else {
// 处理键盘输入
if (fifo8_status(&keyfifo) != 0) {
i = fifo8_get(&keyfifo);// 从键盘缓冲区取数据
io_sti();// 开启中断(退出临界区)
// 在屏幕左上角显示扫描码
sprintf(s, "%02X", i);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);// 清除显示区域
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);// 绘制新内容
// 处理鼠标输入
} else if (fifo8_status(&mousefifo) != 0) {
i = fifo8_get(&mousefifo);// 从鼠标缓冲区取数据
io_sti();// 开启中断
if (mouse_decode(&mdec, i) != 0) {// 成功解析完整数据包
/*-- 更新按钮状态显示 --*/
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
if ((mdec.btn & 0x01) != 0) {
s[1] = 'L';
}
if ((mdec.btn & 0x02) != 0) {
s[3] = 'R';
}
if ((mdec.btn & 0x04) != 0) {
s[2] = 'C';
}
/*-- 更新鼠标坐标 --*/
// 擦除旧光标(用背景色覆盖)
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15);
// 计算新坐标(带边界检查)
mx += mdec.x;
my += mdec.y;
if (mx < 0) {
mx = 0;
}
if (my < 0) {
my = 0;
}
if (mx > binfo->scrnx - 16) {
mx = binfo->scrnx - 16;
}
if (my > binfo->scrny - 16) {
my = binfo->scrny - 16;
}
// 更新坐标显示
sprintf(s, "(%3d, %3d)", mx, my);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15); // 清除坐标显示区域
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); // 绘制新坐标
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); // 在新位置绘制鼠标光标
}
}
}
}
鼠标动作 → 触发IRQ12 → PIC1通知PIC0 → CPU执行INT 2C(鼠标移动/点击 → 触发PS/2控制器的IRQ12→CPU通过中断门调用inthandler2c)
处理程序读取端口0x60获取数据
数据存入环形缓冲区mousefifo
主循环检测到缓冲区非空后处理数据
void enable_mouse(struct MOUSE_DEC *mdec)
{
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
mdec->phase = 0;
return;
}
/**mouse_decode函数负责解析鼠标数据,因为鼠标数据是分三个字节传输的,
所以需要状态机来跟踪当前处理阶段。每次中断处理一个字节,
直到三个字节都接收完毕,才更新鼠标的位置和按钮状态。**/
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{
if (mdec->phase == 0) {
if (dat == 0xfa) {
mdec->phase = 1;
}
return 0;
}
if (mdec->phase == 1) {
if ((dat & 0xc8) == 0x08) {
mdec->buf[0] = dat;
mdec->phase = 2;
}
return 0;
}
if (mdec->phase == 2) {
mdec->buf[1] = dat;
mdec->phase = 3;
return 0;
}
if (mdec->phase == 3) {
mdec->buf[2] = dat;
mdec->phase = 1;
mdec->btn = mdec->buf[0] & 0x07;
mdec->x = mdec->buf[1];
mdec->y = mdec->buf[2];
if ((mdec->buf[0] & 0x10) != 0) {
mdec->x |= 0xffffff00;
}
if ((mdec->buf[0] & 0x20) != 0) {
mdec->y |= 0xffffff00;
}
mdec->y = - mdec->y;
return 1;
}
return -1;
}