day8补充(中断驱动和队列缓冲实现高效数据处理)
day8补充(中断驱动和队列缓冲实现高效数据处理)
主要就是通过中断驱动和队列缓冲实现了高效的数据处理。
业务处理与硬件操作解耦
这个也就是操作系统的生产者和消费者的问题:
中断处理程序(生产者):这个在中断发生立即执行(硬件级别的很快),从硬件0x0060键盘数据端口读数据(键盘鼠标一样的端口),存入缓冲区
主循环(消费者):就从缓冲区取数据,在共享一个缓冲区时,防止中断处理程序与主程序同时访问,关开中断保护临界区
而中断程序的话:
- 单核CPU假设
- 当前系统设计为单核运行,通过PIC芯片的硬件中断屏蔽机制和CPU自动关中断特性实现原子性:
- 硬件保证的原子操作:
- 端口I/O操作 (
io_in8/io_out8
) 是x86架构的原子指令 - PIC中断确认操作本身具有原子性
- 端口I/O操作 (
- FIFO队列的临界区设计:
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 ; 主PICNOP ; 短暂延迟OUT 0xa1,AL ; 从PICCLI ; 禁用CPU中断
; 在切换保护模式时需保证原子操作
LGDT [GDTR0] ; 加载全局描述符表MOV EAX,CR0AND EAX,0x7fffffff ; 禁用分页OR EAX,0x00000001 ; 启用保护模式位MOV CR0,EAXJMP 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-27io_out8(PIC0_ICW3, 1 << 2); // 从PIC连接在IRQ2io_out8(PIC0_ICW4, 0x01); // 非缓冲模式// 从PIC初始化io_out8(PIC1_ICW1, 0x11); // 边沿触发 + 级联模式io_out8(PIC1_ICW2, 0x28); // IRQ8-15映射到INT 28-2fio_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;
}