13.实现鼠标中断处理

简介

上节实现了对键盘中断服务子程序的处理和修改优化了中断程序,但只是简单的在中断服务子程序中记录断码或通码,缓冲区使用效率不高。

目标

实现鼠标中断处理、优化中断缓存。pc中8259A中断控制器连接模型如下:
13.实现鼠标中断处理_第1张图片

1.鼠标发送中断信号的数据线在从8259A芯片的IRQ4信号线,为了接收鼠标中断信号,我们在初始化中断控制芯片时,必须启用该信号线,同时,从8259A芯片是通过主8259A的IRQ2信号线连接在一起的,所以,也必须同时启动主8259A芯片的IRQ2信号线。故init8259A:初始化中断控制器中如下代码修改如下:

mov  al, 11111001b	;CPU只接收主8259A, IRQ1管线发送的信号,其他管线发送信号一概忽略
out  0x21, al		;IRQ1对应的是键盘产生的中断
call io_delay

mov  al, 11101111b	;IRQ4 允许鼠标中断
out  0xa1, al		;鼠标是通过从8259A的IRQ4管线向CPU发送信号
call io_delay

2.鼠标电路对应的一个端口是 0x64, 通过读取这个端口的数据来检测鼠标电路的状态,内核会从这个端口读入一个字节的数据,如果该字节的第二个比特位为0,那表明鼠标电路可以接受来自内核的命令,因此,在给鼠标电路发送数据前,内核需要反复从0x64端口读取数据,并检测读到数据的第二个比特位,直到该比特位为0时,才发送控制信息。

3.修改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 & 0xffff)
		dw  %1
		dw  (%3 & 0x1f) | ((%4 << 8) & 0xff00)
		dw  ((%2>>16) & 0xffff)
	%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  0x21
		Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
	%endrep
	
	;键盘中断向量(8259A 键盘中断向量0x20,IRQ1 是键盘中断请求,0x20 + IRQ[n] = 0x21
	.0x21:
		Gate  SelectorCode32, KeyboardHandler,0, DA_386IGate
	
	%rep  10
		Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
	%endrep
	
	;从中断控制器8259A 中断向量0x28,IRQ4 是鼠标中断请求,0x28 + IRQ[n] = 0x2c
	.0x2c:
		Gate  SelectorCode32, MouseHandler,0, DA_386IGate


	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]

	
	sti		;恢复中断
    jmp dword SelectorCode32:0



;初始化8259A中断控制器
init8259A:
	mov  al, 0x11		;向主8259A发送ICW1
	out  0x20, al
	call io_delay

	out 0xa0, 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, 0x20		;向主8259A发送ICW2
	out 0x21, al		;
						;
	call io_delay

	mov  al, 0x28		;向从8259A发送ICW2
	out  0xa1, al
	call io_delay

	;04h 分解成ICW3 相当于ICW[2] = 1, 
	;这表示从8259A通过主IRQ2管线连接到主8259A控制器
	mov  al, 0x04		; 向主8259A发送ICW3 
	out  0x21, al
	call io_delay

	mov  al, 0x02		;向从8259A 发送 ICW3 
	out  0xa1, al
	call io_delay

	mov  al, 0x02
	out  0x21, al
	call io_delay

	out  0xa1, al
	call io_delay


	;还需要再向两个芯片分别发送一个字节,叫OCW(operation control word), 
	;一个OCW是一字节数据,	也就是8bit,每一bit设置作用是,当OCW[i] = 1 时,
	;屏蔽对应的IRQ(i)管线的信号,例如OCW[0]=1, 那么IRQ0管线的信号将不会被CPU接收,以此类推
	;
	mov  al, 11111001b	;CPU只接收主8259A, IRQ1管线发送的信号,其他管线发送信号一概忽略
	out  0x21, al		;IRQ1对应的是键盘产生的中断
	call io_delay

	mov  al, 11101111b	;IRQ4 允许鼠标中断
	out  0xa1, 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 
   
    call init_main 

fin:    
    hlt
    jmp fin

;8259A中断控制器
LABEL_8259A:
	SpuriousHandler  equ LABEL_8259A - $$
	iretd

;键盘中断程序
LabelKeyboardHandler:
	KeyboardHandler equ LabelKeyboardHandler - $$
	push es
    push ds
    pushad
    mov  eax, esp
    push eax
	
	call int_keyboard

	pop  eax
    mov  esp, eax
    popad
    pop  ds
    pop  es
	iretd

;鼠标中断程序
LabelMouseHandler:
	MouseHandler equ LabelMouseHandler - $$
	push es
    push ds
    pushad
    mov  eax, esp
    push eax
	
	call int_mouse

	pop  eax
    mov  esp, eax
    popad
    pop  ds
    pop  es
	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

4.修改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


//定义键盘缓冲数据区
#define KEYBUF_LEN 32
struct _KeyBuf{
	//缓冲区数据长度
    unsigned char buf[KEYBUF_LEN];
	//下一数据读/写索引
    int next_r, next_w;
	//有效数据长度	
	int len;
}KeyBuf;

struct _KeyBuf keybuf = {{0},0,0,0};


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);



/*
 *char 类型数据转换为16进制字符数据
 *@param	val		待转化为16进制的数值
 *@param	arr		保存16进制字符串数据的数组
 */
void char2HexStr(unsigned char val,char *arr);

//初始化鼠标硬件
void init_mouse();

int num = 0;


//操作系统C语言入口函数--可以指定为其他
void init_main() {
	io_sti();
    initPallet();
	drawBackground();

	init_mouse_cursor((char *)0xa0000,100,100,COL8_008484);
	
	init_mouse();
	

    for(; ;){
		if(keybuf.len==0){
       		io_hlt();
		}
		else{
			io_cli();

			static char arr[4] = {'0','x'};
			unsigned char *ascii = ascii_array;
			for(int t=0;t> 4;
	if(tmp>=10){
		arr[2] = 'a'+tmp-10;
	}
	else{
		arr[2] = '0'+tmp;
	}

	tmp = val & 0x0f;
	if(tmp>=10){
		arr[3] = 'a'+tmp-10;
	}
	else{
		arr[3] = '0'+tmp;
	}
}

/*
 *8259A 键盘中断调用
 *
 */
void int_keyboard(char *index){
	//0x20是8259A控制端口
	//0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,
	//CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号
	io_out8(0x20,0x21);

	//读取8259A  0x60端口键盘扫描码
	char data = io_in8(0x60);

	if(keybuf.len

鼠标电路被激活后将给CPU发送中断信号显示mouse图形,加载运行floppy.img 效果如下:
13.实现鼠标中断处理_第2张图片

优化缓存机制

1.随着功能的丰富,os.c 文件也越来越大会导致修改的难度增加,我们先对os.c 文件分离,考虑到我们OS 是一个简单小巧,文件不会太多。故把os.c 文件中相关申明、类型定义部分提取到os.h 文件中。修改后os.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


//定义缓冲区
typedef struct _FIFO8{
	//指向缓冲区
    char* buf;
	//r:读索引,w:写索引
	//len:存储数据长度
    int r, w, size, len, flag;
}FIFO8;


//*******************************函数声明*************************

//初始化调色板
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);



/*
 *char 类型数据转换为16进制字符数据
 *@param	val		待转化为16进制的数值
 *@param	arr		保存16进制字符串数据的数组
 */
void char2HexStr(unsigned char val,char *arr);

//初始化鼠标硬件
void init_mouse();

//缓存初始化
void fifo8_init(FIFO8 *fifo,int size,char *buf);
//缓冲区存放数据
int fifo8_put(FIFO8 *fifo,char data);
//缓冲区读取数据
int fifo8_get(FIFO8 *fifo);

这样我们只需要在os.c 文件中 #include "os.h",os.c 文件中写相关实现逻辑和屏蔽不需要暴露的功能!

2.os.c文件如下:

// !compile method
// clang -m32 -c os.c -o os.o
// objconv -fnasm os.o -o os.s
//

#include "os.h"
#include "io.h"
#include "ascii_font.h"


 
static char keybuf[32];
static char mousebuf[128];

static FIFO8 keybufInfo;
static FIFO8 mousebufInfo;


static int num = 0;


//操作系统C语言入口函数--可以指定为其他
void init_main() {
	io_sti();
    initPallet();
	drawBackground();

	fifo8_init(&keybufInfo,32,keybuf);
	fifo8_init(&mousebufInfo,128,mousebuf);

	init_mouse_cursor((char *)0xa0000,100,100,COL8_008484);
	
	init_mouse();
	

    for(; ;){
		if(keybufInfo.len>0){
			io_cli();

			static char arr[4] = {'0','x'};
			unsigned char *ascii = ascii_array;
			for(int t=0;t0){
			io_cli();

			static char arr[4] = {'0','x'};
			unsigned char *ascii = ascii_array;
			for(int t=0;t> 4;
	if(tmp>=10){
		arr[2] = 'a'+tmp-10;
	}
	else{
		arr[2] = '0'+tmp;
	}

	tmp = val & 0x0f;
	if(tmp>=10){
		arr[3] = 'a'+tmp-10;
	}
	else{
		arr[3] = '0'+tmp;
	}
}

/*
 *8259A 键盘中断调用
 *
 */
void int_keyboard(char *index){
	//0x20是8259A控制端口
	//0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,
	//CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号
	io_out8(0x20,0x21);

	//读取8259A  0x60端口键盘扫描码
	char data = io_in8(0x60);

	fifo8_put(&keybufInfo,data);
}


#define  PORT_KEYDAT  0x60
#define  PORT_KEYSTA  0x64
#define  PORT_KEYCMD  0x64
#define  KEYCMD_WRITE_MODE  0x60
#define  KBC_MODE     0x47

//鼠标电路对应的一个端口是 0x64, 通过读取这个端口的数据来检测鼠标电路的状态,
//内核会从这个端口读入一个字节的数据,如果该字节的第二个比特位为0,那表明鼠标电路可以
//接受来自内核的命令,因此,在给鼠标电路发送数据前,内核需要反复从0x64端口读取数据,
//并检测读到数据的第二个比特位,直到该比特位为0时,才发送控制信息
void waitKBCReady(){
	for( ; ;){
		if((io_in8(PORT_KEYSTA) & 0x02)==0){
			break;
		}
	}
}


#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4

//初始化键盘控制电路,鼠标控制电路是连接在键盘控制电路上,通过键盘电路实现初始化
void init_mouse(){
	waitKBCReady();

	//0x60让键盘电路进入数据接受状态
	io_out8(PORT_KEYCMD,KEYCMD_WRITE_MODE);
	waitKBCReady();

	//数据0x47要求键盘电路启动鼠标模式,这样鼠标硬件所产生的数据信息,通过键盘电路端口0x60就可读到
	io_out8(PORT_KEYDAT,KBC_MODE);	
	
	waitKBCReady();
	io_out8(PORT_KEYCMD,KEYCMD_SENDTO_MOUSE);
	waitKBCReady();
	//0xf4数据激活鼠标电路,激活后将会给CPU发送中断信号
	io_out8(PORT_KEYDAT,MOUSECMD_ENABLE);
}



/*
 *8259A 鼠标中断调用
 *
 */
void int_mouse(char *index){
	//当中断处理后,要想再次接收中断信号,就必须向中断控制器发送一个字节的数据
	io_out8(0x20,0x20);
	io_out8(0xa0,0x20);
	
	//读取鼠标数据
	char data = io_in8(0x60);
	fifo8_put(&mousebufInfo,data);
}


void fifo8_init(FIFO8 *fifo, int size,char *buf){
	fifo->buf = buf;
	fifo->r = 0;
	fifo->w = 0;
	fifo->size = size;
	fifo->len = 0;
	fifo->flag = 0;
}


int fifo8_put(FIFO8 *fifo,char data){
    if (fifo->len == fifo->size) {
        return -1;
    }

    fifo->buf[fifo->w] = data;
    fifo->w++;
    if (fifo->w == fifo->size) {
        fifo->w = 0;
    }

    fifo->len++;
    return 0;
}


int fifo8_get(FIFO8 *fifo) {
    
    if (fifo->len == 0) {
        return -1;
    }

   	int data = fifo->buf[fifo->r];
    fifo->r++;
    if (fifo->r == fifo->size) {
        fifo->r = 0;
    }

    fifo->len--;
    return data;
}

中断处理后,要想再次接收中断信号,必须向中断控制器发送一个字节的数据,这个字节数据叫OCW2, OCW2[0-2] 用来表示中断的优先级,OCW2[3-4]这两位必须设置为0,OCW[5]这一位称之为End of Interrupt, 这一位设置为1,表示当前中断处理结束,控制器可以继续调用中断函数处理到来的中断信号,要想下一次继续处理中断信号,这一位必须设置为1,OCW2[6-7]设置为0即可,我们代码中发送OCW2时的数值是0x20,也就是仅仅把OCW[5]设置为1。

3.加载运行floppy.img效果如下:
13.实现鼠标中断处理_第3张图片

运行内核文件后屏幕上会显示0xfa,是鼠标被激活时传送过来的。

点击键盘a 字符,界面会出现0x1e、0x9e,再把鼠标放入虚拟机后移动鼠标屏幕上回出现一串信息,效果如下:
13.实现鼠标中断处理_第4张图片

至此我们的键盘鼠标中断处理完成,最后鼠标发送的数据,需要连续三个字节一起解读。

你可能感兴趣的:(自制操作系统)