动手写操作系统8----内核中断机制实现

本节实现内核的中断机制,即实现操作系统的中断响应机制,来实现基本的键盘 鼠标响应事件。

中断基本概念

有关中断的基本概念可以参考: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。对端口发送数据时顺序:

  1. 往端口20h(主片)或A0h(从片)发送ICW1
  2. 往端口21h(主片)或A1h(从片)发送ICW2
  3. 往端口21h(主片)或A1h(从片)发送ICW3
  4. 往端口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();
		}   
    }
}

__stack_chk_fail栈检查失败

https://www.cnblogs.com/qq378829867/p/5978334.html

参考地址:

https://www.jianshu.com/p/73a4cef4ac76

https://blog.csdn.net/Zllvincent/article/details/83995182

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值