30天自制操作系统日志第7天.docx
- 文档编号:9071607
- 上传时间:2023-02-03
- 格式:DOCX
- 页数:20
- 大小:538.76KB
30天自制操作系统日志第7天.docx
《30天自制操作系统日志第7天.docx》由会员分享,可在线阅读,更多相关《30天自制操作系统日志第7天.docx(20页珍藏版)》请在冰豆网上搜索。
30天自制操作系统日志第7天
操作系统实验日志
学号
20160810520
姓名
甘昆禄
专业年级班级
智能1601
实验日期
2018.11.07
实验项目
第7天:
FIFO与鼠标控制
一、实验主要内容
1.获取按键编码
实现功能:
让程序在按一个键后不结束,然后在屏幕上显示出信息。
这样就可以切实完成中断处理程序了。
(显示按键编码,本来以为会显示按键表示内容,比如按A显示A,还是太天真了哈哈,估计后面还要改)
修改int.c程序中的inthandler21函数:
#definePORT_KEYDAT0x0060
voidinthandler21(int*esp)
/*来自PS/2键盘的中断*/
{
structBOOTINFO*binfo=(structBOOTINFO*)ADR_BOOTINFO;
unsignedchardata,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中断。
如果是IRQ3,则写成0x63.也就是说,将”0x60+IRQ号码”输出给OCW2就可以。
执行这句话之后,PIC继续时刻监视IRQ1中断是否发生。
反过来,如果忘记了执行这句话,PIC就不在监视IRQ1中断,不管下次由键盘输入什么信息,系统都感知不到了。
但事实上,我发现按字母键和其他健还是有些区别的。
按其他健后再按另外的健会有反应。
但假若你一开始按字母键再按另外的健就没有反应了。
2、加快中断处理
中断处理是打断CPU本来的工作,加塞要求进行处理。
在处理进行期间不再接受别的中断。
若中断处理速度太慢会影响CPU的的工作。
将处理字符显示内容的功能从中断处理中提出来,将按键编码接收下来保存到变量中,然后由HariMain偶尔去查看这个变量。
发现有数据就显示出来。
structKEYBUF{
unsignedchardata,flag;
};
#definePORT_KEYDAT0x0060
structKEYBUFkeybuf;
voidinthandler21(int*esp)
/*来自PS/2键盘的中断*/
{
unsignedchardata;
io_out8(PIC0_OCW2,0x61);/*通知PIC*IRQ-01已经受理完毕*/
data=io_in8(PORT_KEYDAT);
if(keybuf.flag==0)
{
keybuf.data=data;
keybuf.flag=1;
}
return;
}
在函数中用到了两个变量:
data和flag,所以可以建一个结构体把两个变量集中起来。
将bootpack.c中MariMain函数中的io_halt()函数更改如下:
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);
}
}
即将最初的inthandler21()函数拆解成了inthandler21()和for循环函数。
开始先用io_cli指令屏蔽中断(是为了防止被另外的指令中断),然后去看一看keybuf.flag的值是什么。
如果flag是0说明键还没有按下,就去执行io_hlt指令。
但是由于已经执行io_cli屏蔽了中断,所以就这样去之心HLT指令的话,即使有键按下,程序也不会有任何反应。
所以STI和HLT两个指令都要执行,而执行这两个指令的函数就是io_stihlt。
执行HLT指令后,如果收到PIC的通知,CPU就会被唤醒。
这样,CPU首先回去执行中断程序。
中断处理程序执行完之后又回到for语句的开头,再执行io_cli函数。
若执行到else语句,说明在keybuf.data里存入了按键编码。
先将这个键码(keybuf.data)值保存到变量i中,然后将flag置为0表示把键码值清为空,再通过io_sti语句开放中断。
这样一来,在屏蔽中断期间所做的处理非常少,且会加快操作系统的中断处理过程。
最后莫忘了在bootpack中添加这一句:
externstructKEYBUFkeybuf;以及早for循环中使用的i的声明,还需要将
structKEYBUF{
unsignedchardata,flag;
};
添加到.h文件中。
右ctrl键的键码值比较特殊,按下会产生两个字节的键码值”E01D”,而松开这个键之后会产生两个字节的键码值”E09D”。
在一次产生两个字节键码值的情况下,因为键盘内部电路一次只能发送一个字节,所以一次按键就会产生两次中断,第一次中断时发送E0,第二次中断时发送1D。
3、制作、整理缓冲区
上一步在按右ctrl键时存入参数应该有两个字节,但只显示了一个字节舍弃了最开始那个。
所以设计一个能够存储多字节的缓冲区,就不会马上存满并舍弃较早存入的字节了。
在结构体KEYBUF里增加变量,可以定义一个数组:
structKEYBUF{
unsignedchardata[4];
};
数组data[4]就是存入字节的缓冲区,这里可以使用先前讲到的FIFO、FILO这种栈,这里需要的是FIFO型,也就是先接收到的字节先显示出来。
于是代码可以修改成这样:
structKEYBUF{
unsignedchardata[32];
intnext;
};
这一段代码还是放在.h文件中,然后在int.c中修改代码如下:
voidinthandler21(int*esp)
/*来自PS/2键盘的中断*/
{
unsignedchardata;
io_out8(PIC0_OCW2,0x61);/*通知PIC*IRQ-01已经受理完毕*/
data=io_in8(PORT_KEYDAT);
if(keybuf.flag<32)
{
keybuf.data[keybuf.next]=data;
keybuf.next++;
}
return;
}
keybuf的起始点是“0”,所以最初存储的数据是keybuf.data[0]。
下一个数据是keybuf.data[1],接着是[2],一次类推,一共是32个存储位置。
下一个存储位置用变量next来管理,这样就能记住32个数据而不会溢出。
修改HariMain中代码:
for(;;){
io_cli();
if(keybuf.next==0){
io_stihlt();
}else{
i=keybuf.data[0];
keybuf.next--;
for(j=0;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); } } 注意里面用到的j还是要先声明。 如果next不是0,则说明至少有一个数据。 最开始的一个数据肯定是放在data[0]中的,将这个数存入到变量i中去。 这样,数就减少了一个,所以讲next减去1。 如图: 然后遇到的问题就是上面的缓冲区处理需要数据移送,而且还是在中断期间,花时还容易出错。 ,这时候就需要改善了。 主要看代码就可以理解这个缓冲区,相当于定义了两个指针,一个指向下一个要读的地方,一个指向下一个要存的地方。 数据读出位置追着数据写入位置,且可以在写入位置到达尽头即缓冲区为空时重新回到0的位置,循环使用缓冲区空间。 Bootpack.h的结构体修改: structKEYBUF{ unsignedchardata[32]; intnext_r,next_w,len; }; Int.c中inthandler21函数修改如下: voidinthandler21(int*esp) /*来自PS/2键盘的中断*/ { unsignedchardata; io_out8(PIC0_OCW2,0x61);/*通知PIC*IRQ-01已经受理完毕*/ data=io_in8(PORT_KEYDAT); if(keybuf.next<32) { keybuf.data[keybuf.next_w]=data; keybuf.len++; keybuf.next_w++; if(keybuf.next_w==32) { keybuf.next_w=0; } } return; } HariMain函数读出数据代码如下: for(;;){ io_cli(); if(keybuf.len==0){ io_stihlt(); }else{ i=keybuf.data[keybuf.next_r]; keybuf.len--; keybuf.next_r++; if(keybuf.next_r==32){ keybuf.next_r=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); } } 这样修改之后没有任何数据移送操作,这个缓冲区可以记录大量数据,执行速度又快。 缓冲区固定为32字节以后改起来就不方便,所以把它定义成可变的。 所以采用了指针的方法*buf(data[32]),将缓冲区的总字节保存在变量size(len)中。 变量free用于保存缓冲区里没有数据的字节数。 缓冲区的地址保存在变量buf里。 p代表下一个数据写入地址next_w,q代表写一个数据读出地址next_r。 结构体修改之后如下: structKEYBUF{ unsignedchar*buf; intp,q,size,free,flags; }; 为了初始化缓冲区以及实现对缓冲区的相关操作,新建了一个fifo.c的文件。 /*FIFO*/ #include"bootpack.h" #defineFLAGS_OVERRUN0x0001 //fifo_init是结构的初始化函数,用来设定各种初始值,也就是设定FIFO8结构的地址以及与结构有关 voidfifo8_init(structFIFO8*fifo,intsize,unsignedchar*buf) /*初始化FIFO缓冲区*/ { fifo->size=size; fifo->buf=buf; fifo->free=size;/*缓冲区的大小*/ fifo->flags=0; fifo->p=0;/*下一个数据写入位置*/ fifo->q=0;/*下一个数据读出位置*/ return; } //fifo8_put是往FIFO缓冲区存储1字节信息的函数。 如果一出返回-1,没有溢出就返回0 intfifo8_put(structFIFO8*fifo,unsignedchardata) /*想FIFO传递数据并保存*/ { 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--; return0; } //fifo8_get函数是从FIFO缓冲区取出1字节的函数 intfifo8_get(structFIFO8*fifo) /*从FIFO取得一个数据*/ { intdata; if(fifo->free==fifo->size) { //如果缓冲区为空,则返回-1 return-1; } data=fifo->buf(fifo->q); fifo->q++; if(fifo->q==fifo->size) { fifo->q=0; } fifo->free++; returndata; } //报告一下到底积攒了多少数据 intfifo8_status(structFIFO8*fifo) { returnfifo->size-fifo->free; } 然后根据fifo.c中的函数在int.c中修改inthandler21函数如下 structFIFO8keyfifo; voidinthandler21(int*esp) /*来自PS/2键盘的中断*/ { unsignedchardata; io_out8(PIC0_OCW2,0x61);/*通知PIC*IRQ-01已经受理完毕*/ data=io_in8(PORT_KEYDAT); fifo8_put(&keyfifo,data); return; } 该函数中的&是取地址运算符,用它可以取得结构或变量的地址值。 Fifo8_put接受的第一个参数是内存地址,与之匹配,这里调用时传递的第一个参数也要是内存地址。 HariMain函数内容如下: chars[40],mcursor[256],keybuf[32]; 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); } } 4、鼠标中断 分配给鼠标的中断号码是IRQ12,与键盘的IRQ1比起来差了好多代。 要让鼠标信号得到处理,要让下面两个装置有效,一个是鼠标控制电路,一个是鼠标本身。 关于控制电路的设定: 鼠标控制电路包含在键盘控制电路里,如果键盘控制电路的初始化正常完成,鼠标电路控制器的激活也就完成了。 在bootpack.c后添加如下代码: #definePORT_KEYDAT0x0060 #definePORT_KEYSTA0x0064 #definePORT_KEYCMD0x0064 #defineKEYSTA_SEND_NOTREADY0x02 #defineKEYCMD_WRITE_MODE0x60 #defineKBC_MODE0x47 //让键盘控制电路(KBC)做好准备动作,等待控制指令的到来。 voidwait_KBC_sendready(void)//等待键盘控制电路准备完毕 { for(;;){ if((io_in8(PORT_KEYSTA)&KEYSTA_SEND_NOTREADY)==0){ break;/*break语句是从for循环中强制退出*/ } } return; } voidinit_keyboard(void)//初始化键盘控制电路,然后在HariMain函数调用init_keyboard函数,鼠标控制电路的准备就完成了 { wait_KBC_sendready(); io_out8(PORT_KEYCMD,KEYCMD_WRITE_MODE); wait_KBC_sendready(); io_out8(PORT_KEYDAT,KBC_MODE); return; } //然后开始发送激活鼠标的指令。 归根结底还是要向键盘控制器发送指令 #defineKEYCMD_SENDTO_MOUSE0xd4 #defineMOUSECMD_ENABLE0xf4 //这个函数与init_keyboard函数非常相似,不同点在于写入的数据不同。 如果往键盘控制电路发送指令0xd4,下一个数据就会自动发送给鼠标 voidenable_mouse(void)/*激活鼠标*/ { wait_KBC_sendready(); io_out8(PORT_KEYCMD,KEYCMD_SENDTO_MOUSE); wait_KBC_sendready(); io_out8(PORT_KEYDAT,MOUSECMD_ENABLE); return;/*顺利的话,键盘控制器会返送回ACK(0xfa)*/ } 鼠标中断现在已经有了,接下来是取出中断数据,int.c中添加代码: structFIFO8mousefifo; voidinthandler2c(int*esp) /*来自PS/2鼠标的中断*/ { unsignedchardata; io_out8(PIC1_OCW2,0x64);/*通知PIC1IRQ-12的受理已经完成*/ io_out8(PIC0_OCW2,0x62);/*通知PIC0IRQ-02的受理已经完成*/ data=io_out8(PORT_KEYDAT); fifo8_put(&mousefifo,data); return; } 鼠标和键盘的原理几乎相同,所以程序也就非常相似。 不同之处只有送给PIC的中断受理通知。 IRQ-12时从PIC的第4号(从PIC相当于IRQ-08~IRQ-15),首先要通知IRQ-12受理已完成,然后再通知主PIC。 这是因为主/从PIC的协调不能够自动完成,如果程序不交给主PIC该怎么做,它就会忽视从PIC的下一个中断请求。 鼠标数据取得方法如下: ifo8_init(&mousefifo,128,mousebuf); 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); }elseif(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); } } } 因为鼠标往往会比键盘更快地送出大量数据,所以我们将它的FIFO缓冲区增加到了128字节,避免溢出。 取得数据的程序中,如果键盘和鼠标的FIFO缓冲区都为空了,就执行HLT。 如果不是两个都空,就先检查keyinfo,如果有数据就取出一个显示出来。 如果keyinfo是空,就再去检查mouseinfo,如果有数据,就取出一个显示出来。 二、遇到的问题及解决方法 1、弄了很久还是对键盘的中断有很多问题,第一个就是作者说的按一下键有两个字节信息,即会产生两个中断,这两个中断究竟怎么处理的。 因为就算到了今天的第四个工程,作者说要搞个聪明的缓冲区,但还是觉得怪怪的,按键的前面那个字节编码一直就没有显示出来,这样做的缓冲区真的聪明吗,还是说真的只要这两个字节编码的后面那个就够了。 在网上查询的资料是说我们按下按键一般会持续50ms左右,我们知道计算机1S达到10的9次方数量级的处理能力,那么这按一下键我们CPU指令的处理至少都有10^6数量级了,这样说这个中断都不知道产生了多少次了。 而事实是我们看见画面只显示了按键的后面那个编码,所以有理由相信,如果持续有中断,那么CPU会优先处理后来的中断,再处理后面那个字节的中断,所以我们缓冲区就会存的是后面那个字节的编码。 2、关于字母的按键信息 还有一个问题就是发现字母的按键信息和其他的真的不同,按键似乎怎么样都只显示一个字符的编码没有出现过其他的,在右Ctrl键可以显示前面那个字节编码(即E0)的情况下,按下A,你仍然见到的是编码1E。 而且字母按键很霸道,按下去再按其他的键就没有反应了,只有按下shift键才行,我猜这个应该就是编辑键和功能键的差别吧。 (后面补充,后来和同学讨论时发现有的键盘的字母键是可以显示两个字节编码的,就可以顺利显示出按下按键表示的内容,且只显示一个。 然后我发现我的电脑有时候按了很多次shift键后再按字母键也可以看见按下和松开的区别了,但一般都是不行的。 然后还发现作者写的是PS/2标准键盘的中断,同学的键盘是的,而我的是增强型,不知道是不是这
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 30 自制 操作系统 日志