本节实现内核的中断机制,即实现操作系统的中断响应机制,来实现基本的键盘 鼠标响应事件。
中断基本概念
有关中断的基本概念可以参考:https://blog.csdn.net/u014106644/article/details/94625210
计算机在执行程序过程中,出现异常情况或者特殊请求时,计算机暂停现行程序,转而执行对于这些异常情况和特殊请求的处理,处理结束后再返回现行程序间断处,继续执行。如下所示,当程序执行到K处时,执行中断服务程序1,当中断服务程序执行结束后,继续执行主程序K+1。
可编程中断控制器PIC
外设硬件要给CPU发送信号,需要通过专门的处理芯片,这个芯片叫可编程控制器,俗称8259A:
从上图可以得知,每一个8259A控制器有8根中断信号线,总共可以接入15个外设硬件,一般情况下,鼠标接入的是从8259A所对应的IRQ4这根信号线,鼠标发送信号时,先通过管线IRQ4将信号传递到从8259A,然后通过管线IRQ2传递到主8259A,最后信号再传递给CPU,键盘产生的中断通过主8259A的IRQ1管线向CPU发送信号。
在一个8259A芯片有如下几个内部寄存器:
Interrupt Mask Register (IMR)
Interrupt Request Register (IRR)
In Service Register (ISR)
IMR被用作过滤被屏蔽的中断,IRR被用作暂时放置未被进一步处理的Interrupt,当一个Interrupt正在被CPU处理时,此中断被放置在ISR中。除了这几个寄存器之外,8259A还有一个单元叫做Priority Resolver,当多个中断同时发生时,Priority Resolver根据它们的优先级,将高优先级者优先传递给CPU。当一个中断请求从IR0到IR7中的某根线到达IMR时,IMR首先判断此IR是否被屏蔽,如果被屏蔽,则此中断请求被丢弃;否则,则将其放入IRR中。
在此中断请求不能进行下一步处理之前,它一直被放在IRR中。一旦发现处理中断的时机已到,Priority Resolver将从所有被放置于IRR中的中断中挑选出一个优先级最高的中断,将其传递给CPU去处理。IR号越低的中断优先级别越高,比如IR0的优先级别是最高的。
8259A通过发送一个INTR(Interrupt Request)信号给CPU,通知CPU有一个中断到达。CPU收到这个信号后,会暂停执行下一条指令,然后发送一个INTA(Interrupt Acknowledge)信号给8259A。8259A收到这个信号之后,马上将ISR中对应此中断请求的Bit设置,同时IRR中相应的bit会被reset。比如,如果当前的中断请求是IR3的话,那么ISR中的bit-3就会被设置,IRR中IR3对应的bit就会被reset。这表示此中断请求正在被CPU处理,而不是正在等待CPU处理。
随后,CPU会再次发送一个INTA信号给8259A,要求它告诉CPU此中断请求的中断向量是什么,这是一个从0到255的一个数。8259A根据被设置的起始向量号(起始向量号通过中断控制字ICW2被初始化)加上中断请求号计算出中断向量号,并将其放置在Data Bus上。比如被初始化的起始向量号为8,当前的中断请求为IR3,则计算出的中断向量为8+3=11。
CPU从Data Bus上得到这个中断向量之后,就去IDT中找到相应的中断服务程序ISR,并调用它。如果8259A的End of Interrupt (EOI)通知被设定位人工模式,那么当ISR处理完该处理的事情之后,应该发送一个EOI给8259A。
8259A得到EOI通知之后,ISR寄存器中对应于此中断请求的Bit会被Reset。
如果8259A的End of Interrupt (EOI)通知被设定位自动模式,那么在第2个INTA信号收到后,8259A ISR寄存器中对应于此中断请求的Bit就会被Reset。
在此期间,如果又有新的中断请求到达,并被放置于IRR中,如果这些新的中断请求中有比在ISR寄存中放置的所有中断优先级别还高的话,那么这些高优先级别的中断请求将会被马上按照上述过程进行处理;否则,这些中断将会被放在IRR中,直到ISR中高优先级别的中断被处理结束,也就是说知道ISR寄存器中高优先级别的bit被Reset为止。
程序员可以向8259A写两种命令字:
Initialization Command Word (ICW);这种命令字被用作对8259A芯片的初始化。
Operation Command Word (OCW):这种命令被用来向8259A发布命令,以对其进行控制。OCW可以在8259A被初始化之后的任何时候被使用。
硬件初始化,通过端口发送命令来完成,要配置这两个控制器,需要对指定端口发送1字节的数据ICW(initialization control word)。
主8259A对应的端口地址是20h,21h,从8259A对应的端口是A0h和A1h。对端口发送数据时顺序:
- 往端口20h(主片)或A0h(从片)发送ICW1
- 往端口21h(主片)或A1h(从片)发送ICW2
- 往端口21h(主片)或A1h(从片)发送ICW3
- 往端口20h(主片)或A0h(从片)发送ICW4
每个ICW的结构和意义:
ICW1[0...7]:
ICW1[0] 设置为1表示需要发送ICW4,0表示不需要发送ICW4
ICW1[1] 设置为1表示单个8259, 0表示级联8259
ICW1[2] 设置为1表示4字节中断向量,0表示8字节中断向量
ICW1[3] 设置为1表示中断形式是水平触发,0表示边沿触发
ICW1[4] 必须设置为1
ICW1[5,6,7] 必须设置为0
ICW2[0...7]:
ICW2[0,1,2] 对于80X86架构必须设置为0
ICW2[3...7]: 80X86中断向量
ICW3[0...7](主片):
ICW3[0] 设置为1, IR0级联从片,0无从片
ICW3[1] 设置为1, IR1级联从片,0无从片
ICW3[2] 设置为1, IR2级联从片,0无从片
ICW3[3] 设置为1, IR3级联从片,0无从片
ICW3[4] 设置为1, IR4级联从片,0无从片
ICW3[5] 设置为1, IR5级联从片,0无从片
ICW3[6] 设置为1, IR6级联从片,0无从片
ICW3[7] 设置为1, IR7级联从片,0无从片
ICW3[0...7](从片):
ICW3[0,1,2] 从片连接主片的IR号
ICw3[3...7] 必须是0
ICW4[0...7]:
ICW4[0] 设置为1,表示x86模式,0表示MCS 80/85模式
ICW4[1] 设置为1,自动EOI;0 正常EOI
ICW4[2,3] 表示主从缓冲模式
ICW4[4] 1表示SFNM模式; 0 sequential 模式
ICW4[5,6,7] 设置为0
3.内核中断实现
kernel.asm修改如下:
; 全局描述符结构 8字节
; byte7 byte6 byte5 byte4 byte3 byte2 byte1 byte0
; byte6低四位和 byte1 byte0 表示段偏移上限
; byte7 byte4 byte3 byte2 表示段基址
;定义全局描述符数据结构
;3 表示有3个参数分别用 %1、%2、%3引用参数
;%1:段基址 %2:段偏移上限 %3:段属性
%macro GDescriptor 3
dw %2 & 0xffff ;设置段偏移上限0,1字节
dw %1 & 0xffff ;设置段基址2,3字节
db (%1>>16) & 0xff ;设置段基址4字节
dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff) ;设置段偏移上限6字节低四位
db (%1>>24) & 0xff ;设置段基址7字节
%endmacro
DA_32 EQU 0x4000 ;32位段属性
DA_CODE EQU 0x98 ;执行代码段属性
DA_RW EQU 0x92 ;读写代码段属性值
DA_RWA EQU 0x93 ;存在的已访问可读写数据段类型值
;中断描述符表
;专门用于描述可执行代码所在的内存
;offset_low 和 offset_high 合在一起作为中断函数在代码执行段中的偏移
;selector 用来指向全局描述符表中的某个描述符,中断函数的代码就处于该描述符所指向的段中
;Gate selector, offset, DCount, Attr
%macro Gate 4
dw (%2 & 0FFFFh)
dw %1
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h)
dw ((%2 >> 16) & 0FFFFh)
%endmacro
DA_386IGate EQU 0x8e ;中断调用门
org 0x9000 ;内核代码在内存中起始加载处
jmp entry
[SECTION .gdt]
;全局描述符 段基址 段偏移上线 段属性
LABLE_GDT: GDescriptor 0, 0, 0
LABLE_DESC_CODE: GDescriptor 0, SegCodeLen-1, DA_CODE + DA_32
LABLE_DESC_VIDEO: GDescriptor 0xb8000, 0xffff, DA_RW ;显存内存地址从0xB8000开始
LABLE_DESC_STACK: GDescriptor 0, STACK_TOP-1, DA_RWA + DA_32 ;函数堆栈
LABLE_DESC_VRAM: GDescriptor 0, 0xffffffff, DA_RW
;GDT表大小
GdtLen EQU $ - LABLE_GDT
;GDT表偏移上限和基地址
GdtPtr dw GdtLen-1
dd 0
;cpu寻址方式
;实模式 段寄存器×16+偏移地址
;保护模式下 段寄存器中存储的是GDT描述表各个描述符的偏移
SelectorCode32 EQU LABLE_DESC_CODE - LABLE_GDT
SelectorVideo EQU LABLE_DESC_VIDEO - LABLE_GDT
SelectorStack EQU LABLE_DESC_STACK - LABLE_GDT
SelectorVRAM EQU LABLE_DESC_VRAM - LABLE_GDT
;中断描述符表
LABLE_IDT:
%rep 255
Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
%endrep
IdtLen EQU $ - LABLE_IDT
IdtPtr dw IdtLen - 1
dd 0
[SECTION .s16]
[BITS 16]
entry:
mov ax, cs
mov ss, ax
mov ds, ax
mov es, ax
mov sp, 0x100
;设置屏幕色彩模式
mov al, 0x13
mov ah, 0
INT 0x10
;设置LABLE_DESC_CODE描述符段基址
mov eax, 0
mov ax, cs
shl eax, 4
add eax, SEG_CODE32
mov word [LABLE_DESC_CODE+2], ax
shr eax, 16
mov [LABLE_DESC_CODE+4], al
mov [LABLE_DESC_CODE+7], ah
;设置栈空间
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABLE_STACK
mov word [LABLE_DESC_STACK+2], ax
shr eax, 16
mov byte [LABLE_DESC_STACK+4], al
mov byte [LABLE_DESC_STACK+7], ah
mov eax, 0
mov ax, ds
shl eax, 4
add eax, LABLE_GDT
mov dword [GdtPtr+2], eax
;设置GDTR寄存器
lgdt [GdtPtr]
cli ;关中断
;打开A20
in al, 0x92
or al, 0x02
out 0x92, al
;进入保护模式CR0寄存器最低位PE设为1
mov eax, cr0
or eax, 1
mov cr0, eax
;初始化8259A
call init8259A
;加载中断描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABLE_IDT
mov dword [IdtPtr + 2], eax
lidt [IdtPtr]
;恢复中断
sti
jmp dword SelectorCode32:0
;初始化8259A中断控制器
init8259A:
;向主8259A发生ICW1
;011h 对应的二进制是00010001
;ICW1[0]=1表示需要发送ICW4
;ICW1[1] = 0,说明有级联8259A
;ICW1[2] =0 表示用8字节来设置中断向量号
;ICW1[3]=0表示中断形式是边沿触发
;ICW[4]必须设置为1,ICW[5,6,7]必须是0
mov al, 011h
out 20h, al
call io_delay
;向从8259A发送ICW1
out 0A0h, al
call io_delay
;向主8259A发送ICW2
;20h 对应二进制00100000
;ICW2[0,1,2] = 0
;8259A根据被设置的起始向量号(起始向量号通过中断控制字ICW2被初始化)加上中断请求号计算出中断向量号
;当主8259A对应的IRQ0管线向CPU发送信号时,CPU根据0x20这个值去查找要执行的代码,
;,CPU根据0x21这个值去查找要执行的代码,依次类推。
mov al, 020h
out 021h, al
call io_delay
;向从8259A发送ICW2
;28h 对应二进制00100100
;8259A根据被设置的起始向量号(起始向量号通过中断控制字ICW2被初始化)加上中断请求号计算出中断向量号
;当从8259A对应的IRQ0管线向CPU发送信号时,CPU根据0x28这个值去查找要执行的代码,
;IRQ1管线向CPU发送信号时,CPU根据0x29这个值去查找要执行的代码,依次类推。
mov al, 028h
out 0A1h, al
call io_delay
;向主8259A发送ICW3
;004h 00000100
;ICW[2] = 1, 表示从8259A通过主IRQ2管线连接到主8259A控制器
mov al, 004h
out 021h, al
call io_delay
;向从8259A 发送 ICW3
;ICW[0,1,2] = 2, 表示当前从片是从IRQ2管线接入主8259A芯片的
mov al, 002h
out 0A1h, al
call io_delay
;向主8259A发送ICW4
;ICW4[0]=1表示当前CPU架构师80X86
;ICW4[1]=1表示自动EOI, 如果这位设置成0的话,那么中断响应后,代码要想继续处理中断,就得主动给CPU发送一个信号,
;如果设置成1,那么代码不用主动给CPU发送信号就可以再次处理中断。
mov al, 002h
out 021h, al
call io_delay
;向从8259A发送ICW4
out 0A1h, al
call io_delay
;OCW(operation control word)
;当OCW[i] = 1 时,屏蔽对应的IRQ(i)管线的信号
;IRQ1对应的是键盘产生的中断
mov al, 11111101b
out 021h, al
call io_delay
;CPU忽略所有来自从8259A芯片的信号
;鼠标是通过从8259A的IRQ4管线向CPU发送信号
mov al, 11111111b
out 0A1h, al
call io_delay
ret
io_delay:
nop
nop
nop
nop
ret
[SECTION .s32]
[BITS 32]
SEG_CODE32:
mov ax, SelectorStack
mov ss, ax
mov esp, STACK_TOP
mov ax, SelectorVRAM
mov ds, ax
mov ax, SelectorVideo
mov gs, ax
;恢复中断
sti
call kernel_main ;调用c语言函数
fin:
hlt
jmp fin
LABLE_8259A:
SpuriousHandler EQU LABLE_8259A - $$
call int_8259A
iretd
;注意汇编文件引入位置 在代码段结束符之上
%include "io.asm"
%include "write_vram.asm"
;32位模式代码长度
SegCodeLen EQU $ - SEG_CODE32
[SECTION .gs]
ALIGN 32
[BITS 32]
LABLE_STACK:
TIMES 1024 DB 0
STACK_TOP EQU $ - LABLE_STACK
kernel.asm主要添加内容如下:
1.中断描述符 描述cpu响应中断之后 待执行的中断处理代码在内存中的位置信息
2.8259A中断控制器的初始化 这里只响应键盘中断
3.中断处理函数声明
write_vram.c修改如下:
#include<stdio.h>
#include "io.h"
#include "ascii_font.h"
//定义调色板颜色
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15
//屏幕宽度
#define SCREEN_WIDTH 320
//屏幕高度
#define SCREEN_HEIGHT 200
//显存起始地址
#define VGA_ADDR 0xa0000
//调色板初始化
void initPallet();
//绘制简单图形
void draw_simple();
//绘制矩形功能
void draw_rectangle();
//绘制矩形
void fillRect(int x, int y, int width, int height, char colIndex);
//绘制桌面
void draw_desktop();
/**
*绘制字体
*@param addr 绘制的起始显存地址
*@param x 绘制的x坐标
*@param y 绘制的y坐标
*@param col 绘制颜色
*@param pch 绘制的字符数组8*16,每一行共8位,共16行
*@param screenWidth 屏幕宽度
*/
void showChar(char *addr, int x, int y, char col, unsigned char *pch, int screenWidth);
void draw_char();
//初始化鼠标指针
void init_mouse_cursor(char *vram, int x, int y, char bc);
void showString(char *addr, int x, int y, char col);
void int_8259A(char *index);
//C程序入口
void kernel_main(){
initPallet();
//draw_simple();
//draw_rectangle();
draw_desktop();
//draw_char();
//init_mouse_cursor((char *)VGA_ADDR, 100, 100, COL8_008484);
//showString("Interrupt", 100, 100, COL8_FFFFFF);
for(;;){
io_hlt();
}
}
void initPallet(){
//定义调色板
static char table_rgb[16*3] = {
0x00, 0x00, 0x00,
0xff, 0x00, 0x00,
0x00, 0xff, 0x00,
0xff, 0xff, 0x00,
0x00, 0x00, 0xff,
0xff, 0x00, 0xff,
0x00, 0xff, 0xff,
0xff, 0xff, 0xff,
0xc6, 0xc6, 0xc6,
0x84, 0x00, 0x00,
0x00, 0x84, 0x00,
0x84, 0x84, 0x00,
0x00, 0x00, 0x84,
0x84, 0x00, 0x84,
0x00, 0x84, 0x84,
0x84, 0x84, 0x84,
};
// 注意此指针变量的声明
unsigned char *p = table_rgb;
//读取eflags寄存器值
int flag = io_readFlag();
//关中断
io_cli();
//写入调色板编号
io_out8(0x03c8, 0);
//调色板颜色与坐标索引对应值
for(int i=0; i<16; i++){
io_out8(0x03c9, p[0]/4);
io_out8(0x03c9, p[1]/4);
io_out8(0x03c9, p[2]/4);
p += 3;
}
//将eflags寄存器重新赋值
io_writeFlag(flag);
// 开中断
// 此处注意开中断
// 在写调色板信息时关闭中断 这里要及时打开中断 否则后面中断无法响应
io_seti();
}
void draw_simple(){
//显存起始地址
char *p = (char *)VGA_ADDR;
//绘制画面
for(int i=0; i<=0xFFFF; i=i+2){
*p = i & 0x0F;
p++;
}
}
void fillRect(int x, int y, int width, int height, char colIndex){
char *vram = (char *)VGA_ADDR;
for(int i=y; i<=y+height; i++){
for(int j=x; j<=x+width; j++){
vram[i*SCREEN_WIDTH+j] = colIndex;
}
}
}
void draw_rectangle(){
fillRect(10, 30, 100, 100, COL8_FF0000);
fillRect(50, 80, 100, 100, COL8_00FF00);
fillRect(100, 100, 100, 100, COL8_0000FF);
}
void draw_desktop(){
fillRect(0, 0, SCREEN_WIDTH-1, SCREEN_HEIGHT-29, COL8_008484);
fillRect(0, SCREEN_HEIGHT-28, SCREEN_WIDTH-1, 28, COL8_848484);
fillRect(0, SCREEN_HEIGHT-27, SCREEN_WIDTH, 1, COL8_848484);
fillRect(0, SCREEN_HEIGHT-26, SCREEN_WIDTH, 25, COL8_C6C6C6);
fillRect(3, SCREEN_HEIGHT-24, 56, 1, COL8_FFFFFF);
fillRect(2, SCREEN_HEIGHT-24, 1, 20, COL8_FFFFFF);
fillRect(3, SCREEN_HEIGHT-4, 56, 1, COL8_848484);
fillRect(59, SCREEN_HEIGHT-23, 1, 19, COL8_848484);
fillRect(2, SCREEN_HEIGHT-3, 57, 0, COL8_000000);
fillRect(60, SCREEN_HEIGHT-24, 0, 19, COL8_000000);
fillRect(SCREEN_WIDTH-47, SCREEN_HEIGHT-24, 43, 1, COL8_848484);
fillRect(SCREEN_WIDTH-47, SCREEN_HEIGHT-23, 0, 19, COL8_848484);
fillRect(SCREEN_WIDTH-47, SCREEN_HEIGHT-3, 43, 0, COL8_FFFFFF);
fillRect(SCREEN_WIDTH-3, SCREEN_HEIGHT-24, 0, 21, COL8_FFFFFF);
}
void showChar(char *addr, int x, int y, char col, unsigned char *pch, int screenWidth){
for(int i=0; i<16; i++){
char ch = pch[i];
// 对于每一个字符 8*16
// 从上到下 从右向左依次绘制
// 遍历每一位 如果1 则将显存对应位置设置为特定颜色
int off = (y + i) * screenWidth;
if((ch & 0x01) != 0){
addr[off+x+7] = col;
}
if((ch & 0x02) != 0){
addr[off+x+6] = col;
}
if((ch & 0x04) != 0){
addr[off+x+5] = col;
}
if((ch & 0x08) != 0){
addr[off+x+4] = col;
}
if((ch & 0x10) != 0){
addr[off+x+3] = col;
}
if((ch & 0x20) != 0){
addr[off+x+2] = col;
}
if((ch & 0x40) != 0){
addr[off+x+1] = col;
}
if((ch & 0x80) != 0){
addr[off+x+0] = col;
}
}
}
void draw_char(){
unsigned char *ascii = ascii_array;
int x, y = 0;
for(int i=0x20; i<=0x7f; i++){
//字符水平间隔为4 竖直间隔为8
x = (i - 0x20) % 32 * 10;
y = (i - 0x20) / 32 * 20;
showChar((char *)VGA_ADDR, x, y, COL8_FFFFFF, ascii+(i-0x20)*16, SCREEN_WIDTH);
}
}
void init_mouse_cursor(char *vram, int x, int y, char bc){
//16*16 Mouse
//鼠标指针点阵
static char cursor[16][16] = {
"*...............",
"**..............",
"*O*.............",
"*OO*............",
"*OOO*...........",
"*OOOO*..........",
"*OOOOO*.........",
"*OOOOOO*........",
"*OOOOOOO*.......",
"*OOOO*****......",
"*OO*O*..........",
"*O*.*O*.........",
"**..*O*.........",
"*....*O*........",
".....*O*........",
"......*........."
};
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
int off = (i+y)*SCREEN_WIDTH+x+j;
if (cursor[i][j] == '*') {
vram[off] = COL8_000000;
}
if (cursor[i][j] == 'O') {
vram[off] = COL8_FFFFFF;
}
if (cursor[i][j] == '.') {
vram[off] = bc;
}
}
}
}
void showString(char *str, int x, int y, char col){
unsigned char *ascii = ascii_array;
for(; *str!=0x00; str++){
showChar((char *)VGA_ADDR, x, y, col, ascii+((char)*str-0x20)*16, SCREEN_WIDTH);
x += 8;
}
}
void int_8259A(char *index){
showString("Interrupt", 100, 100, COL8_FFFFFF);
}
中断处理函数 当点击键盘按键时,在屏幕输出字符串。
在kernel_main中初始化调色板功能initPallet时,开始关中断 初始化之后记得开中断,在这个地方调试了好长时间
void initPallet(){
//定义调色板
static char table_rgb[16*3] = {
0x00, 0x00, 0x00,
0xff, 0x00, 0x00,
0x00, 0xff, 0x00,
0xff, 0xff, 0x00,
0x00, 0x00, 0xff,
0xff, 0x00, 0xff,
0x00, 0xff, 0xff,
0xff, 0xff, 0xff,
0xc6, 0xc6, 0xc6,
0x84, 0x00, 0x00,
0x00, 0x84, 0x00,
0x84, 0x84, 0x00,
0x00, 0x00, 0x84,
0x84, 0x00, 0x84,
0x00, 0x84, 0x84,
0x84, 0x84, 0x84,
};
// 注意此指针变量的声明
unsigned char *p = table_rgb;
//读取eflags寄存器值
int flag = io_readFlag();
//关中断
io_cli();
//写入调色板编号
io_out8(0x03c8, 0);
//调色板颜色与坐标索引对应值
for(int i=0; i<16; i++){
io_out8(0x03c9, p[0]/4);
io_out8(0x03c9, p[1]/4);
io_out8(0x03c9, p[2]/4);
p += 3;
}
//将eflags寄存器重新赋值
io_writeFlag(flag);
// 开中断
// 此处注意开中断
// 在写调色板信息时关闭中断 这里要及时打开中断 否则后面中断无法响应
io_seti();
}
键盘中断响应优化
当键盘上的一个按键按下时,键盘会发送一个中断信号给CPU,与此同时,键盘会在指定端口(0x60) 输出一个数值,这个数值对应按键的扫描码(make code)叫通码,当按键弹起时,键盘又给端口输出一个数值,这个数值叫断码(break code).我们以按键按键’A’为例,当按键’A’按下时,键盘给端口0x60发出的扫描码是0X1E, 当按键’A’弹起时,键盘会给端口0x60发送断码0x9E。
断码 = 通码 + 0x80 键盘扫描码如下所示:
这样便实现键盘中断的简单响应,但是这种方式当持续点击键盘时,系统会变得卡顿,因为cpu一直被键盘中断占据,不能响应其他事件,因此可以对其进行优化,利用缓冲区来保存点击的键盘事件,当发生键盘中断时,对应的中断处理函数仅仅将键盘数据保存到缓冲区,在kernel_main主函数中循环检测缓冲区,当缓冲区非空时,才来响应数据。修改如下:
write_wram.c
定义键盘缓冲区数据结构以及初始化
// 键盘缓冲数据区
struct _KeyBuf {
// 缓冲区数据长度
unsigned char buf[KEYBUF_LEN];
// 下一数据读/写索引
int next_r, next_w;
// 有效数据长度
int len;
}KeyBuf;
struct _KeyBuf keybuf = {{0},0,0,0};
中断响应函数如下所示:
void int_keyboard(char *index){
io_out8(0x20, 0x21);
unsigned char data = io_in8(PORT_KEYDATA);
if (keybuf.len < KEYBUF_LEN) {
keybuf.buf[keybuf.next_w] = data;
keybuf.len++;
keybuf.next_w++;
} else {
keybuf.len = 0;
keybuf.next_w = 0;
}
// static 关键字 _stack_chk_fail
/*static char keyval[4] = {'0','x'};
char2HexStr(data, keyval);
static int x = 0;
showString(keyval, x, 100, COL8_FFFFFF);
x += 32;*/
//showString("12345", 100, 100, COL8_FFFFFF);
}
在kernel_main主函数中循环检测缓冲区数据,如果非空,则进行相应处理
//C程序入口
void kernel_main(){
initPallet();
//draw_simple();
//draw_rectangle();
draw_desktop();
//draw_char();
init_mouse_cursor((char *)VGA_ADDR, 100, 100, COL8_008484);
//showString("Interrupt", 100, 100, COL8_FFFFFF);
//init_mouse();
for(;;){
if (keybuf.len == 0) {
io_hlt();
} else {
io_cli();
static char keyval[4] = {'0','x'};
for(int i=0; i<keybuf.len; i++){
static int x = 0;
char2HexStr(keybuf.buf[i], keyval);
showString(keyval, x%SCREEN_WIDTH, x/SCREEN_WIDTH*20, COL8_FFFFFF);
x += 32;
}
keybuf.len = 0;
keybuf.next_w = 0;
io_seti();
}
}
}
https://www.cnblogs.com/qq378829867/p/5978334.html
参考地址: