首页 > 其他分享 >【读书笔记-《30天自制操作系统》-6】Day7

【读书笔记-《30天自制操作系统》-6】Day7

时间:2024-08-21 22:51:19浏览次数:16  
标签:鼠标 binfo 中断 Day7 30 fifo 读书笔记 io data

本篇向着移动鼠标的目标继续前进。先对中断处理进行一些补充说明,然后建立完善缓冲区来实现键盘数据接收。最后是在此基础上的初始化鼠标控制电路与鼠标的数据接收。
在这里插入图片描述

1. 中断处理程序补充说明

前面的处理中,接收到键盘中断后只是显示一行信息,现在把按键的信息也一并显示出来。将中断处理函数修改如下:

void inthandler21(int *esp)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	unsigned char data, s[4];
	io_out8(PIC0_OCW2, 0x61);	/* 通知PIC IRQ-01已经处理完毕 */
	data = io_in8(PORT_KEYDAT);

	sprintf(s, "%02X", data);
	boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);

	return;
}

这段程序中需要注意的点有两个。
首先是语句

io_out8(PIC0_OCW2, 0x61);

这里是通知PIC,中断IRQ1已处理,这样PIC才会继续监控IRQ1中断,否则后续即使IRQ1中断再次产生,PIC也不进行上报。
此外还有语句

data = io_in8(PORT_KEYDAT);

这里PORT_KEYDAT定义为0x0060,是一个端口号,对应的设备就是键盘。从这里读出的8位信息就是按键编码。
运行程序,按下按键或者松开按键,都会显示出一个十六进制数的按键编码。

按键编码显示出来了,但是还是要对中断处理程序做一些优化。
因为中断处理打断了CPU的正常工作流程,相当于CPU停下了手头全部的工作来处理这一中断,而且此过程中不再接收其他的中断。如果中断处理的时间过长,CPU就无法及时响应其他的中断,造成鼠标运动不连续,不能从网上接收数据等问题。
而字符显示要执行大量的if语句进行判断,执行大量的内存写入指令,花费的时间明显较长。因此这里将接收到的编码先存放在变量中,并不在中断程序中执行显示的动作,而是在Harimain函数中以轮询的方式查询变量的值,查询到变量中存放了值就将其显示出来。中断处理函数修改如下:

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* 通知PIC IRQ-01已经处理完毕 */
	data = io_in8(PORT_KEYDAT);
	if (keybuf.flag == 0) {
		keybuf.data = data;
		keybuf.flag = 1;
	}
	return;
}

在中断程序中,如果接收到了按键值,就将keybug结构体中的flag置为1,将按键值存放在data中。
HariMain函数如下:

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256];
	int mx, my, i;

	init_gdtidt();
	init_pic();
	io_sti(); 

	io_out8(PIC0_IMR, 0xf9); 
	io_out8(PIC1_IMR, 0xef); 

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; 
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

	for (;;) {
		io_cli();
		if (keybuf.flag == 0) {
			io_stihlt();
		} else {
			i = keybuf.data;
			keybuf.flag = 0;
			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);
		}
	}
}

可以看到,主函数完成前期的初始化之后,就进入for循环检测。由于没有循环判断条件,for循环会一直执行下去。在处理keybuf之前,需要先屏蔽中断,防止此时响应中断造成混乱。接下来对keybuf.flag进行检测,如果为0,说明没有收到按键值,不做处理,可以继续响应中断;如果置1,则说明收到了按键值,将按键值读取出来。按键值取出之后,再响应新的中断就不会造成混乱了,此时就可以放开中断,然后将按键值显示出来。
当然了,这里只能读取一个数据,如果数据处理期间又产生了中断,则无法进行处理,新产生的中断数据会被丢弃。

  1. 缓冲区:从初步到完善

2.1 数据移位的缓冲区

那么问题就来了。按下右Ctrl键时,会连续产生两次中断,发送了两个字节的按键值,对于上面处理能力只有一个字节的程序,就无法收到第二个字节了。
于是我们需要一定长度缓冲区,能够在中断处理完成前保存一定的数据,而不是只能处理一个字节。于是有下面的中断处理函数:

struct KEYBUF {
	unsigned char data[32];
	int next;
};

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	
	data = io_in8(PORT_KEYDAT);
	if (keybuf.next < 32) {
		keybuf.data[keybuf.next] = data;
		keybuf.next++;
	}
	return;
}

这里将缓冲区设置为32个字节的长度,并用next变量记录下一个数据存放的位置。保险起见,next达到32之后的数据就丢弃不使用了。相对应的主程序修改如下:

	for (;;) {
		io_cli();
		if (keybuf.next == 0) {
			io_stihlt();
		} else {
			i = keybuf.data[0];
			keybuf.next--;
			for (j = 0; j < keybuf.next; j++) {
				keybuf.data[j] = keybuf.data[j + 1];
			}
			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);
		}
	}

在主程序中,一次for循环只能处理1字节的数据。这里每次从keybuf.data数组中取出一个数据,都要将其他的数据依次向前移动一个位置,以保证每次执行循环体时data[0]都是最新需要被处理的数据。

2.2 “环形”的缓冲区

这样的处理似乎只是逻辑上看起来略显复杂,实际不止于此。采用这种数据移动的方式仍然要执行很多条指令,主程序要在禁止中断时进行这些操作,可能导致无法及时响应中断。

于是继续对缓冲区做优化。维护两个位置信息,一个是中断程序写入数据的位置,一个是主程序读出数据的位置。
在初始过程中,读出数据的位置落后于写入数据的位置:
在这里插入图片描述这样一直写入,会有写入的位置到达缓冲区末尾时的情况:
在这里插入图片描述这时只需要将写入位置重置为缓冲区起始位置,从起始位置重新开始写入。
在这里插入图片描述
从图中可以看出,只要写入的位置没有“追上”读出的位置,那么这段缓冲区就可以一直重复利用。
根据以上的思路,程序整理如下。
首先是定义缓冲区的结构体:

struct FIFO8 {
	unsigned char *buf;
	int p, q, size, free, flags;
};

其中buf即是可自定义长度的缓冲区,p与q分别 用于表示写入的位置与读取的位置,size和free分别表示缓冲区的总长度与剩余可写入的长度。缓冲区的初始化函数:

void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/* 初始化FIFO缓冲区 */
{
	fifo->size = size;
	fifo->buf = buf;
	fifo->free = size; /* 缓冲区的大小 */
	fifo->flags = 0;
	fifo->p = 0; /* 下一个数据的写入位置 */
	fifo->q = 0; /* 下一个数据的读出位置 */
	return;
}

向缓冲区写入数据的函数与从缓冲区读取数据的函数:

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* 向FIFO传送数据并保存 */
{
	if (fifo->free == 0) {
		/* FIFO中无空余空间,丢弃 */
		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;
}

int fifo8_get(struct FIFO8 *fifo)
/* 从FIFO中取得一个数据 */
{
	int data;
	if (fifo->free == fifo->size) {
		/* 缓冲区没有存入数据,返回 */
		return -1;
	}
	data = fifo->buf[fifo->q];
	fifo->q++;
	if (fifo->q == fifo->size) {
		fifo->q = 0;
	}
	fifo->free++;
	return data;
}

int fifo8_status(struct FIFO8 *fifo)
/* 获取当前缓冲区存放的数据个数 */
{
	return fifo->size - fifo->free;
}

每次产生中断,调用fifo8_put向缓冲区写入一个数据;主程序中,每执行一次循环体,调用fifo8_get从缓冲区读取一个数据。

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* 通知PIC, IRQ-01受理已经完成 */
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&keyfifo, data);
	return;
}
	char s[40], mcursor[256], keybuf[32];
	fifo8_init(&keyfifo, 32, keybuf);
	for (;;) {
		io_cli();
		if (fifo8_status(&keyfifo) == 0) {
			io_stihlt();
		} else {
			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);
		}
	}

3. 鼠标:从控制电路初始化到接收鼠标数据

优化了以上中断处理程序,再次回到鼠标上来。

前一篇中鼠标的中断处理程序之所以未能生效,是因为有两方面的初始化没有做。一是鼠标的控制电路,二是鼠标本身,其中鼠标控制电路的初始化要在鼠标本身的初始化之前完成。
鼠标的控制电路包含在键盘的控制电路中,键盘的控制电路初始化完成后,鼠标的控制电路也就初始化完成了。

#define PORT_KEYDAT				0x0060
#define PORT_KEYSTA				0x0064
#define PORT_KEYCMD				0x0064
#define KEYSTA_SEND_NOTREADY	0x02
#define KEYCMD_WRITE_MODE		0x60
#define KBC_MODE				0x47

void wait_KBC_sendready(void)
{
	/* 等待键盘控制电路准备完毕 */
	for (;;) {
		if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
			break;
		}
	}
	return;
}

void init_keyboard(void)
{
	/* 初始化键盘控制电路 */
	wait_KBC_sendready();
	io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
	wait_KBC_sendready();
	io_out8(PORT_KEYDAT, KBC_MODE);
	return;
}

wait_KBC_sendready函数用于CPU确认键盘控制电路已经做好准备,可以接收指令。而确定键盘控制电路处于可以接收指令的状态后,发送设定模式的指令,键盘控制电路的初始化就完成了。

最后还需要激活鼠标本身,同样是向键盘控制电路发送指令。

#define KEYCMD_SENDTO_MOUSE		0xd4
#define MOUSECMD_ENABLE			0xf4

void enable_mouse(void)
{
	/* 激活鼠标 */
	wait_KBC_sendready();
	io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
	wait_KBC_sendready();
	io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
	return; /*成功状态下键盘控制器会返回ACK(0xfa) */
}

向键盘控制器写入0xd4后,下一条指令就会被发送给鼠标,这样就可以发送激活鼠标的指令了。

以上工作全部完成后,运行程序,鼠标的中断信息也终于可以显示出来了。
在这里插入图片描述
既然中断处理函数正常,那么继续来接收鼠标的数据。

struct FIFO8 mousefifo;

void inthandler2c(int *esp)
/* 来自PS/2鼠标的中断 */
{
	unsigned char data;
	io_out8(PIC1_OCW2, 0x64);	/* 通知PIC1,IRQ-12已受理完成 */
	io_out8(PIC0_OCW2, 0x62);	/* 通知PIC0,IRQ-02已受理完成 */
	data = io_in8(PORT_KEYDAT);
	fifo8_put(&mousefifo, data);
	return;
}

由于鼠标的IRQ12在从PIC上,在通知从PIC后还需要通知主PIC的IRQ2,否则主PIC会忽略之后的IRQ2中断。

	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();
				sprintf(s, "%02X", i);
				boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);
				putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
			}
		}
	}

从键盘获取数据与从鼠标获取数据的方式相同,这里将二者进行合并。在中断处理函数中,键盘和鼠标的数据分别写入了不同的缓冲区,这里也从不同的缓冲区读取。运行程序,移动鼠标或按下键盘,会分别产生响应:
在这里插入图片描述
距离移动鼠标只有一步之遥了!下一篇继续,敬请期待!

标签:鼠标,binfo,中断,Day7,30,fifo,读书笔记,io,data
From: https://blog.csdn.net/Ocean1994/article/details/141323456

相关文章

  • 2024暑假集训测试30
    前言比赛链接。T1普及了一下异或哈希,T2、T3赛时应该算乱搞题,还搞挂了,T4高级平衡树题,不太可做。原题全部出自:2022牛客OI赛前集训营-提高组(第四场)。T1博弈部分分\(30pts\):\(O(n^2)\)暴力。正解:不难推出必胜策略就是\((x,y)\)路径上每个边权出现的次数不全为......
  • day7哈希表 454.四数相加II |383. 赎金信|15. 三数之和 |18. 四数之和
    454.四数相加IIclassSolution{publicintfourSumCount(int[]nums1,int[]nums2,int[]nums3,int[]nums4){//前两数相加,key是合,次数是value,跟后两数相加的和等于0的话,就取出map里的次数。//两个forloop时间复杂度n方。intres=0;......
  • 《Prometheus监控实战》读书笔记
    监控简介Google服务层次结构图,监控是底座一些监控反模式:事后监控机械式监控不(够)准确的监控静态监控:不是说超过某个绝对阈值系统就一定出现问题,更有意义的监控是对比(环比)动态监控。数据库性能分析供应商VividCortex的首席执行官BaronSchwartz对此评论道[插图]:它们比一个停......
  • 全球创新药商业化服务平台市场展望:2030年预计达到113660百万美元
    随着全球医药行业的快速发展和创新药物研发的不断涌现,创新药商业化服务平台行业作为支持新药上市和商业化的关键服务领域,其市场前景受到业界广泛关注。据恒州恒思(YHresearch)团队研究的数据显示,2023年全球创新药商业化服务平台市场规模已达到33210百万美元,并预计在未来六年内,该......
  • 深度调研全球催产药品市场:2030年市场规模达到220.8百万美元
    在全球范围内,催产药品市场作为医疗行业的重要组成部分,正日益受到关注。据恒州恒思(YHresearch)团队研究,本报告全面分析了催产药品的发展趋势、主要竞争者、供应链结构、研发进展及法规政策环境,并预测了该行业的未来投资机会与增长点。2023年全球催产药品市场规模大约为148.1百万......
  • 电力系统潮流计算(牛顿-拉夫逊法、高斯-赛德尔法、快速解耦法)【6节点 9节点 14节点 26
      ......
  • SBT30100VFCT-ASEMI无人机专用SBT30100VFCT
    编辑:llSBT30100VFCT-ASEMI无人机专用SBT30100VFCT型号:SBT30100VFCT品牌:ASEMI封装:TO-220F批号:最新最大平均正向电流(IF):30A最大循环峰值反向电压(VRRM):100V最大正向电压(VF):0.70V~0..90V工作温度:-65°C~175°C反向恢复时间:35ns芯片个数:2芯片尺寸:74mil引脚数量:3正向浪涌电流......
  • MBR30100CT-ASEMI低压降肖特基MBR30100CT
    编辑:llMBR30100CT-ASEMI低压降肖特基MBR30100CT型号:MBR30100CT品牌:ASEMI封装:TO-220批号:最新恢复时间:35ns最大平均正向电流(IF):30A最大循环峰值反向电压(VRRM):100V最大正向电压(VF):0.70V~0.90V工作温度:-65°C~175°C芯片个数:2芯片尺寸:mil正向浪涌电流(IFMS):250AMBR30100CT特......
  • CH32V307
    学习目标:掌握CH32V307沁恒微电子开发流程例如:一周掌握CH32V307开发流程学习内容:在编写的代码中,掌握UART的串口打印调试信息代码区:结果展示:代码解释:......
  • LeetCode300.最长递增子序列
    LeetCode300.最长递增子序列力扣题目链接(opensnewwindow)给你一个整数数组nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]是数组[0,3,1,6,2,2,7]的子序列。示例1:输入:nums=[......