《30天自制操作系统》读书笔记(6) 鼠标键盘
-
总览
从现在开始我把这些代码都放在了GitHub上, 欢迎围观 地址是:
https://github.com/LastAvenger
上一篇笔记介绍的是GDT,IDT,PIC等各种我也不太懂的东西, 但是这些了解这些东西对启用鼠标键盘是必须的.
我们在键盘上按下一个键之后, 键盘产生一个信号, PIC接收到信号后, 会通过IRQ1向CPU发送一个中断信号,而这个中断信号是我们自己设定的, 这里的是int 21, CPU接收到int 21 中断后, 会停下现在的工作, 在IDT中寻找int 21 对应的ISR, 并执行它, 我们就通过这个ISR函数来处理键盘事件, (这对于USB键盘竟然也是有效的).
对于鼠标, 大概由于鼠标是后来才有的, PS/2鼠标的中断是通过IRQ12发送给CPU的, 而USB鼠标的中断不然, OSDev 上面这样写:
A USB mouse generally emulates a PS2 mouse, except that it generates IRQs from the USB bus, and not IRQ 12.
所以我们的系统暂时不能支持USB鼠标, 而且鼠标的控制电路一开始是不被启用的, 我们需要激活这个控制电路, 之后的步骤和键盘的相差无几, 这里鼠标的中断号设置为int 2c.
综上我们要先 设定GDT, IDT(装入ISR)->设定PIC->初始化键盘鼠标. 而为了防止我们在处理当前键盘中断的时候又有中断要处理, 所以处理的机制是: ISR不停接收中断, 将数据填入循环队列, ISR是回调的(可以这么说吧?), 有中断的时候会自动执行, 而主函数则不断地检查队列是否空, 非空的话就处理数据(很有趣的样子), 原理和Windows的消息队列是一样的.
以上其实还包括了很多繁琐细节, 略去不表, 详见原书, 这只是笔记.
设定GDT是为了进入32位模式, 这个书的作者已经在asmhead.nas里面先实现了, 具体细节由于能力限制无法了解.
- 队列缓冲区
队列的实现采用了静态链表做循环队列的方式, 数据结构课上有教过, 代码如下:
1 /* FIFO*/ 2 3 #include "bootpack.h" 4 5 #define FLAGS_OVERRUN 0x0001 6 7 void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf) 8 /* FIFO缓冲区初始化*/ 9 { 10 fifo->size = size; 11 fifo->buf = buf; 12 fifo->free = size; /* 缓冲区大小*/ 13 fifo->flags = 0; 14 fifo->p = 0; /* 队列尾*/ 15 fifo->q = 0; /* 队列头*/ 16 return; 17 } 18 19 int fifo8_put(struct FIFO8 *fifo, unsigned char data) 20 /* 入队*/ 21 { 22 if (fifo->free == 0) { 23 /* 队列已满*/ 24 fifo->flags |= FLAGS_OVERRUN; 25 return -1; 26 } 27 fifo->buf[fifo->p] = data; 28 fifo->p++; 29 if (fifo->p == fifo->size) { 30 fifo->p = 0; 31 } 32 fifo->free--; 33 return 0; 34 } 35 36 int fifo8_get(struct FIFO8 *fifo) 37 /* 出队*/ 38 { 39 int data; 40 if (fifo->free == fifo->size) { 41 /* 队列空 */ 42 return -1; 43 } 44 data = fifo->buf[fifo->q]; 45 fifo->q++; 46 if (fifo->q == fifo->size) { 47 fifo->q = 0; 48 } 49 fifo->free++; 50 return data; 51 } 52 53 int fifo8_status(struct FIFO8 *fifo) 54 /* 取得状态*/ 55 { 56 return fifo->size - fifo->free; 57 }
- ISR(不停将数据写入缓冲区)
设定IDT的代码之前已经给出, 现在需要知道的是ISR怎么写, ISR 如下:
1 struct FIFO8 keyfifo; 2 void inthandler21(int *esp) 3 {/* 来自PS/2键盘的中断*/ 4 unsigned char data; 5 io_out8(PIC0_OCW2, 0x61); /* 通知PIC, IRQ-01的受理已经完成*/ 6 data = io_in8(PORT_KEYDAT); 7 fifo8_put(&keyfifo, data); 8 return; 9 } 10 11 struct FIFO8 mousefifo; 12 void inthandler2c(int *esp) 13 /*来自PS/2 鼠标的中断 */ 14 { 15 unsigned char data; 16 io_out8(PIC1_OCW2, 0x64); /* 通知PIC IRQ-12 的受理已经完成*/ 17 io_out8(PIC0_OCW2, 0x62); /* 通知PIC IRQ-02 的受理已经完成*/ 18 data = io_in8(PORT_KEYDAT); 19 fifo8_put(&mousefifo, data); 20 return; 21 }
- 设定PIC的代码见上文
- 主函数中处理数据的部分
键盘传来的数据容易理解, 都是键盘的字节码, 一个键的一个状态(Press/Up) 对应一个字节码(size =byte), 将其显示出来即可.
鼠标发生一次中断传回来3个byte 的数据:
byte 1:
Y overflow |
X overflow |
Y sign bit |
X sign bit |
Always 1 |
Middle Btn |
Right Btn |
Left Btn |
byte 2 :
X movement |
byte 3:
Y movement |
我们主要需要的是第一个byte的后三位, 分别代表中键, 左键和右键, byte2 和 byte3 则是在X,Y轴上的位移, 主函数处理的代码如下:
1 for (;;) 2 { 3 io_cli(); 4 if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) 5 { 6 io_stihlt(); 7 } 8 else 9 { 10 if (fifo8_status(&keyfifo) != 0) 11 { 12 i = fifo8_get(&keyfifo); 13 io_sti(); 14 sprintf(s, "%02X", i); 15 boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); 16 putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); 17 } 18 else if (fifo8_status(&mousefifo) != 0) 19 { 20 i = fifo8_get(&mousefifo); 21 io_sti(); 22 if (mouse_decode(&mdec, i) != 0) { 23 /* 凑齐三个字节后显示 */ 24 sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y); 25 if ((mdec.btn & 0x01) != 0) { 26 s[1] = 'L'; 27 } 28 if ((mdec.btn & 0x02) != 0) { 29 s[3] = 'R'; 30 } 31 if ((mdec.btn & 0x04) != 0) { 32 s[2] = 'C'; 33 } 34 boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31); 35 putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s); 36 37 /* 鼠标指针的移动*/ 38 boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15); /* 隐藏鼠标*/ 39 mx += mdec.x; 40 my += mdec.y; 41 if (mx < 0) { 42 mx = 0; 43 } 44 if (my < 0) { 45 my = 0; 46 } 47 if (mx > binfo->scrnx - 16) { 48 mx = binfo->scrnx - 16; 49 } 50 if (my > binfo->scrny - 16) { 51 my = binfo->scrny - 16; 52 } 53 sprintf(s, "(%3d, %3d)", mx, my); 54 boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 隐藏坐标*/ 55 putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 显示坐标*/ 56 putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); /* 绘制鼠标*/ 57 } 58 } 59 } 60 }
最后上图: