day8补充(中断驱动和队列缓冲实现高效数据处理)

day8补充(中断驱动和队列缓冲实现高效数据处理)

主要就是通过中断驱动和队列缓冲实现了高效的数据处理。

业务处理与硬件操作解耦

这个也就是操作系统的生产者和消费者的问题:
中断处理程序(生产者):这个在中断发生立即执行(硬件级别的很快),从硬件0x0060键盘数据端口读数据(键盘鼠标一样的端口),存入缓冲区
主循环(消费者):就从缓冲区取数据,在共享一个缓冲区时,防止中断处理程序与主程序同时访问,关开中断保护临界区

而中断程序的话:

  • 单核CPU假设
    • 当前系统设计为单核运行,通过PIC芯片的硬件中断屏蔽机制和CPU自动关中断特性实现原子性:
  • 硬件保证的原子操作
    • 端口I/O操作 (io_in8/io_out8) 是x86架构的原子指令
    • PIC中断确认操作本身具有原子性
  • 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		; 主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; 
}

你可能感兴趣的:(操作系统,计算机外设,操作系统,汇编,计算机组成原理,生产者和消费者,中断)