1.我们绘制了鼠标指针,但是都是固定在特定位置,不能移动。为了能响应键盘和鼠标事件,我们需要了解中断控制器、建立中断机制!
2.8259A芯片是一个中断管理芯片。中断的来源除了来自于硬件自身的NMI中断和来自于软件的INT n指令造成的软件中断之外,还有来自于外部硬件设备的中断,这些中断是可屏蔽的。这些中断也都通过可编程中断控制器PIC(Programmable Interrupt Controller)进行控制,并传递给CPU。
一个8259A芯片的可以接最多8个中断源,但由于可以将2个或多个8259A芯片级连(cascade),并且最多可以级连到9个,所以最多可以接64个中断源。如今绝大多数的PC都拥有两个8259A,这样 最多可以接收15个中断源。
在一个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。
3.8259A工作原理
当一个中断请求从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为止。
4.8259As的口令
程序员可以向8259A写两种命令字:
Initialization Command Word (ICW);这种命令字被用作对8259A芯片的初始化。
Operation Command Word (OCW):这种命令被用来向8259A发布命令,以对其进行控制。OCW可以在8259A被初始化之后的任何时候被使用。
任何时候,只要向某一个8259A的第一个端口(0x20 for Master,and 0xA0 for Slave)写入的命令的bit-4(从0算起)为1,那么这个8259A就认为这是一个ICW1;而一旦一个8259A收到一个ICW1,它就认为一个初始化序列开始了。
我们需要对指定端口发送1字节的数据,这个一字节(8 bit)的数据,我们称之为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[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
1.实现键盘中断事件,修改kernel.s 文件如下:
;全局描述符结构 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
dw %1 & 0xffff
db (%1>>16) & 0xff
dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff)
db (%1>>24) & 0xff
%endmacro
DA_32 EQU 4000h ; 32 位段
DA_C EQU 98h ; 存在的只执行代码段属性值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
;中断描述符表
;Gate selecotor, 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 8Eh ; 中断调用门
org 0x9000
jmp entry
[SECTION .gdt]
;定义全局描述符 段基址 段偏移上限 段属性
LABEL_GDT: GDescriptor 0, 0, 0
LABEL_DESC_CODE: GDescriptor 0, SegCodeLen-1, DA_C+DA_32
LABEL_DESC_VIDEO: GDescriptor 0xb8000, 0xffff, DA_DRW
LABEL_DESC_STACK: GDescriptor 0, STACK_TOP-1, DA_DRWA+DA_32
LABEL_DESC_VRAM: GDescriptor 0, 0xffffffff, DA_DRW
;gdt 表大小
GdtLen equ $-LABEL_GDT
;gdt表偏移上限和基地址
GdtPtr dw GdtLen-1
dd 0
;cpu开机进入实模式时使用的段寄存器 cs,ds,es,ss 和偏移地址组成内存地址,内存地址=段寄存器 * 16 + 偏移地址
;保护模式中段寄存器保存的是gdt 描述表中各个描述符的偏移,也叫选择子
SelectorCode32 EQU LABEL_DESC_CODE-LABEL_GDT
SelectorVideo EQU LABEL_DESC_VIDEO-LABEL_GDT
SelectorStack EQU LABEL_DESC_STACK-LABEL_GDT
SelectorVRAM EQU LABEL_DESC_VRAM-LABEL_GDT
;中断描述符表
LABEL_IDT:
%rep 255
Gate SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep
IdtLen equ $ - LABEL_IDT
IdtPtr dw IdtLen - 1
dd 0
[SECTION .s16]
[BITS 16]
entry:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0x100
;设置屏幕色彩模式
mov al,0x13
mov ah,0
int 0x10
;设置LABEL_DESC_CODE描述符段基址
mov eax,0
mov ax,cs
shl eax,4
add eax,SEG_CODE32
mov word [LABEL_DESC_CODE+2],ax
shr eax,16
mov [LABEL_DESC_CODE+4],al
mov [LABEL_DESC_CODE+7],ah
;设置栈空间
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_STACK
mov word [LABEL_DESC_STACK+2],ax
shr eax,16
mov byte [LABEL_DESC_STACK+4],al
mov byte [LABEL_DESC_STACK+7],ah
mov eax,0
mov ax,ds
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr+2],eax
;设置GDTR 寄存器
lgdt [GdtPtr]
cli ;关闭可可屏蔽中断,如键盘中断
in al,0x92
or al,0x02
out 0x92,al
mov eax,cr0
or eax,1
mov cr0,eax
call init8259A
;加载中断描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_IDT
mov dword [IdtPtr + 2], eax
lidt [IdtPtr]
jmp dword SelectorCode32:0
;初始化8259A中断控制器
init8259A:
mov al, 011h ;向主8259A发送ICW1
out 20h, al
call io_delay
out 0A0h, al ;向从8259A发送ICW1
call io_delay
;20h 分解成ICW2 是, ICW2[0,1,2] = 0, 这是强制要求的,
;也就是ICW2的值不能是0x21,0x22之类,只要前三位是0就行
;整个ICW2 = 0x20,这样的话,当主8259A对应的IRQ0管线向CPU发送信号时,
;CPU根据0x20这个值去查找要执行的代码,IRQ1管线向CPU发送信号时,
;CPU根据0x21这个值去查找要执行的代码,依次类推
mov al, 020h ;向主8259A发送ICW2
out 021h, al ;
;
call io_delay
mov al, 028h ;向从8259A发送ICW2
out 0A1h, al
call io_delay
;04h 分解成ICW3 相当于ICW[2] = 1,
;这表示从8259A通过主IRQ2管线连接到主8259A控制器
mov al, 004h ; 向主8259A发送ICW3
out 021h, al
call io_delay
mov al, 002h ;向从8259A 发送 ICW3
out 0A1h, al
call io_delay
mov al, 002h
out 021h, al
call io_delay
out 0A1h, al
call io_delay
;还需要再向两个芯片分别发送一个字节,叫OCW(operation control word),
;一个OCW是一字节数据, 也就是8bit,每一bit设置作用是,当OCW[i] = 1 时,
;屏蔽对应的IRQ(i)管线的信号,例如OCW[0]=1, 那么IRQ0管线的信号将不会被CPU接收,以此类推
;
mov al, 11111101b ;CPU只接收主8259A, IRQ1管线发送的信号,其他管线发送信号一概忽略
out 021h, al ;IRQ1对应的是键盘产生的中断
call io_delay
mov al, 11111111b ;CPU忽略所有来自从8259A芯片的信号
out 0A1h, al ;鼠标是通过从8259A的IRQ4管线向CPU发送信号
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
;恢复中断
sti
call init_main
fin:
hlt
jmp fin
LABEL_8259A:
SpuriousHandler equ LABEL_8259A - $$
call int_8259A
iretd
;导入io操作函数模块
%include "io.s"
;导入C语言编写的功能模块
%include "os.s"
;32位模式代码长度
SegCodeLen EQU $-SEG_CODE32
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 1024 db 0
STACK_TOP EQU $ - LABEL_STACK
2.修改os.c 文件如下
// !compile method
// clang -m32 -c os.c -o os.o
// objconv -fnasm os.o -o os.s
//
#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
void initPallet();
/**
*绘制矩形
*x 矩形左上角x坐标
*y 矩形左上角y坐标
*width 宽度
*height 高度
*colIndex pallet_color 类型调色板颜色索引,即矩形颜色
*/
void fillRect(int x,int y,int width,int height,char colIndex);
//绘制桌面背景
void drawBackground();
/**
*绘制字体
*@param addr 绘制的起始显存地址
*@param x 绘制的x坐标
*@param y 绘制的y坐标
*@param col 绘制颜色
*@param pch 绘制的字符数组8*16,每一行共8位,共16行
*@param screenWidth 屏幕宽度
*/
void putChar(char *addr,int x,int y,char col,unsigned char *ch,int screenWidth);
/*
*初始化鼠标指针
*@param vram 绘制的起始显存地址
*@param x 绘制鼠标指针最左上角x坐标
*@param y 绘制鼠标指针最左上角y坐标
*@param bc 绘制的矩形填充颜色,和背景色一样将能看到鼠标指针
*/
void init_mouse_cursor(char *vram,int x,int y,char bc);
//操作系统C语言入口函数--可以指定为其他
void init_main() {
initPallet();
drawBackground();
init_mouse_cursor((char *)0xa0000,100,100,COL8_008484);
for(; ;){
io_hlt();
}
}
void initPallet(){
//定义调色板
static unsigned char table_rgb[16*3] = {
0x00, 0x00, 0x00, /* 0:黑色*/
0xff, 0x00, 0x00, /* 1:亮红*/
0x00, 0xff, 0x00, /* 2:亮绿*/
0xff, 0xff, 0x00, /* 3:亮黄*/
0x00, 0x00, 0xff, /* 4:亮蓝*/
0xff, 0x00, 0xff, /* 5:亮紫*/
0x00, 0xff, 0xff, /* 6:浅亮蓝*/
0xff, 0xff, 0xff, /* 7:白色*/
0xc6, 0xc6, 0xc6, /* 8:亮灰*/
0x84, 0x00, 0x00, /* 9:暗红*/
0x00, 0x84, 0x00, /* 10:暗绿*/
0x84, 0x84, 0x00, /* 11:暗黄*/
0x00, 0x00, 0x84, /* 12:暗青*/
0x84, 0x00, 0x84, /* 13:暗紫*/
0x00, 0x84, 0x84, /* 14:浅灰蓝*/
0x84, 0x84, 0x84, /* 15:暗灰*/
};
unsigned char *rgb = table_rgb;
int flag = io_readFlag();
io_cli();
io_out8(0x03c8, 0);
for(int i=0;i<16;i++){
io_out8(0x03c9,rgb[0] / 4);
io_out8(0x03c9,rgb[1] / 4);
io_out8(0x03c9,rgb[2] / 4);
rgb += 3;
}
io_writeFlag(flag);
}
void fillRect(int x,int y,int width,int height,char colIndex){
char *vram = (char *)0xa0000;
for(int i=y;i<=y+height;i++){
for(int j=x;j<=x+width;j++){
vram[i*SCREEN_WIDTH+j] = colIndex;
}
}
}
void drawBackground(){
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 putChar(char *addr,int x,int y,char col,unsigned char *pch,int screenWidth){
for(int i=0;i<16;i++){
char ch = pch[i];
int off = (y+i)*screenWidth;
//显示的字形最左边的是低地址,右侧的是高地址。例如:0x80,则高地址部分显示在内存的低地址,
//最低位的应该偏移7
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 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;
}
}
}
}
/*
*8259A中断调用
*
*/
void int_8259A(char *index){
unsigned char *ascii = ascii_array;
for(int i=0x20;i<=0x7f;i++){
int x = (i-0x20)%32*10;
int y = (i-0x20)/32*20;
putChar((char *)0xa0000,x,y,COL8_FFFFFF,ascii+(i-0x20)*16,SCREEN_WIDTH);
}
}