标签:
harib03a:
内容没有变化 ;P109 从这里开始,代码开始工程化了.
将原本300多行的bootpack.c分割成了三部分:
graphic.c : 用来处理界面图像
dsctbl.c : 用来处理中断和段表(GDT,IDT)等
bootpack.c : 和后面的bootpack.h文件一起用来封装函数
修改了Makefile中的文件生成步骤(当然后面需要bootpack.h 头文件):
修改前:bootpack.c-->bootpack.bim
修改后:graphic.c -->graphic.obj
dsctbl.c -->dsctbl.obj
bootpack.c-->bootpack.obj
graphic.obj+dsctbl.obj+bootpack.obj+其他接口信息-->bootpack.bim
harib03b:
整理Makefile;简化了Makefile中的一些内容;
感觉原理和DBMS中的LIKE字符串匹配神似,有木有。
看看这个书本上的这个例子:归纳成一般规则:
bootpack.gas : bootpack.c Makefile
$(CC1) -o bootpack.gas bootpack.c
graphic.gas : graphic.c Makefile
$(CC1) -o graphic.gas graphic.c
dsctbl.gas : dsctbl.c Makefile
$(CC1) -o dsctbl.gas dsctbl.c
//------------------------------------
%.gas : %.c Makefile
$(CC1) -o $*.gas $*.c
harib03c:
P111 整理头文件;这里实际上还是在做代码优化工作;为后续准备
我们发现bootpack.c被分割之后,经常需要调用bootpack.c中的函数
因此,来了一个头文件bootpack.h;把bootpack.c中实现的函数封装起来
这样每次需要调用函数时只要#include一个bootpack.h 就 OK了!!
/* bootpack.h */ struct BOOTINFO { /* 0x0ff0-0x0fff */ char cyls; /* 启动区读硬盘停止的位置 */ char leds; /* 启动时,键盘LED的状态 */ char vmode; /* 显卡的色彩模式 */ char reserve; short scrnx, scrny; /* 画面分辨率(像素) */ char *vram; }; #define ADR_BOOTINFO 0x00000ff0 //都是一些函数的声明,具体实现方法在源文件中。 /* 汇编naskfunc.nas函数申明 */ void io_hlt(void); void io_cli(void); void io_out8(int port, int data); int io_load_eflags(void); void io_store_eflags(int eflags); void load_gdtr(int limit, int addr); void load_idtr(int limit, int addr); /* Cgraphic.c 函数声明*/ void init_palette(void); void set_palette(int start, int end, unsigned char *rgb); void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1); void init_screen8(char *vram, int x, int y); void putfont8(char *vram, int xsize, int x, int y, char c, char *font); void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s); void init_mouse_cursor8(char *mouse, char bc);
接下来我们来看下LGDT和LIDT是怎么个情况:
LGDT和LIDT都是分别是给48为的段寄存器GDTR和48位的中断寄存器IDTR赋值,他们的原理基本相同;下面以LGDT为例详细说明一下。
1、段信息结构体struct SEGMENT_DESCRIPTOR (如下图所示)
按照CPU的结构要求,将段信息归结成8个字节写入内存中;之后再由指令LGDT装入段寄存器GDTR中;
注意:寄存器GDTR长度为6个字节。写入段寄存器中的信息只是短信息(8个字节)中的高位的6个字节,后面两个字节存储的是段的管理属性-access_right ;
//请对照上面图片看;
//表示段信息的结构体(一共48位) //段地址 32位:base_low(2字节)+base_mid(1字节)+base_high(1字节) //剩余4个字节||||我们知道段上限为4GB;一个32位的数值 //如果剩余这4个字节全部用来表示段上限那么就没有空间表示段信息了; //HOW TO DO?? //段上限 20位:段属性中的标志位Gbit为1时,limit的单位不解释成字节,而翻译成页(1页=4KB) // limit_low 和limit_high //注 意:limit_high中的上4位写的是段属性 struct SEGMENT_DESCRIPTOR { //段信息结构体定义8字节 short limit_low, base_low; //2字节 char base_mid, access_right; //1字节 char limit_high, base_high; //1字节 }; struct GATE_DESCRIPTOR { //中断信息结构体定义8字节 short offset_low, selector; char dw_count, access_right; short offset_high; };
/*将段信息的8个字节写入内存;便于段寄存器读取; ;struct SEGMENT_DESCRIPTOR 在头文件中; ;段大小;;段起始地址;;段的管理属性(写入,执行,系统专用等) */ void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar) { if (limit > 0xfffff) { ar |= 0x8000; /* G_bit = 1 */ limit /= 0x1000; } sd->limit_low = limit & 0xffff; sd->base_low = base & 0xffff; sd->base_mid = (base >> 16) & 0xff; sd->access_right = ar & 0xff; sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); sd->base_high = (base >> 24) & 0xff; return; } void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar) { gd->offset_low = offset & 0xffff; gd->selector = selector; gd->dw_count = (ar >> 8) & 0xff; gd->access_right = ar & 0xff; gd->offset_high = (offset >> 16) & 0xffff; return; }
2、LGDT和LIDT的具体实现
;LGDT给48位寄存器GDTR赋值; ;方法:从指定地址的高6个字节; ;底16位为段上限;高32位为GDT表开始的地址 _load_gdtr: ; void load_gdtr(int limit, int addr); MOV AX,[ESP+4] ; limit MOV [ESP+6],AX LGDT [ESP+6] RET ;LIDT赋值,原理和上面基本相同 _load_idtr: ; void load_idtr(int limit, int addr); MOV AX,[ESP+4] ; limit MOV [ESP+6],AX LIDT [ESP+6] RET
3、段管理属性ar(limit_high的高四位+access_right)
harib03d:
请对照书P117的图看 初始化PIC(programmable interrupt controller-可编程中断控制器),顾名思义:控制中断的东西
CPU单独只能处理一个中断,因此需要辅助芯片来帮助CPU处理不同的中断信号;
主PIC(PIC0)和CPU相连;从PIC( PIC1 )和主PIC的IRQ2相连;扩展了16个中断口
PIC芯片的主要寄存器:
IMR:中断屏蔽寄存器;8位,对应8路IRQ信号;信号1屏蔽该路中段;
ICW:初始化控制数据;一共有4个寄存器
/* int.c */ #include "bootpack.h" void init_pic(void) /* PIC的初始化 */ { io_out8(PIC0_IMR, 0xff ); /* 禁止所有中断 */ io_out8(PIC1_IMR, 0xff ); /* 禁止所有中断 */ io_out8(PIC0_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode) */ io_out8(PIC0_ICW2, 0x20 ); /* IRQ0-7由INT20-27接受 */ io_out8(PIC0_ICW3, 1 << 2); /* PIC1由IRQ2连接 */ io_out8(PIC0_ICW4, 0x01 ); /* 无缓冲区模式 */ io_out8(PIC1_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode) */ io_out8(PIC1_ICW2, 0x28 ); /* IRQ8-15由INT28-2f接收 */ io_out8(PIC1_ICW3, 2 ); /* PIC1由IRQ2连接 */ io_out8(PIC1_ICW4, 0x01 ); /* 无缓冲区模式 */ io_out8(PIC0_IMR, 0xfb ); /* 11111011 PIC1以外的全部禁止 */ io_out8(PIC1_IMR, 0xff ); /* 11111111 禁止所有中断 */ return; }
harib03e:
设置键盘和鼠标中断;笔者将键盘中断设在了PIC的IRQ12,将鼠标设在了IRQ1;
调用的终端号分别为0x2c(0010 1100) 和 0x21(0010 0001) 可以对照P117的PIC芯片图看。
void inthandler21(int *esp) /* PS/2的键盘中断 */ { struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; //加载BIOS boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15); //获取键盘中断之后,显示这个字符串INT 21 (IRQ-1) : PS/2 keyboard putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard"); for (;;) { //之后 让CPU等着 !! io_hlt(); } }
下面介绍了键盘中断函数的具体实现的汇编代码;
笔者在这里介绍了堆栈的一些知识,这段汇编代码就比较简单了:
_asm_inthandler21: PUSH ES ;关键寄存器压栈,函数必做 PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS ;将DS和ES调整到与SS相等 MOV DS,AX MOV ES,AX CALL _inthandler21 ;调用函数_asm_inthandler21 POP EAX ;返回后,将寄存器回复原来 POPAD POP DS POP ES IRETD ;最后执行IRETD
我们已经编写了键盘的响应函void inthandler21(int *esp);也理解了该函数的汇编具体在内存中怎么做的_asm_inthandler21:
接下来就要把键盘中断号写到中断表中,这样当执行INT时,就可以调用相应的中断程序了(在这里是函数void inthandler21(int *esp))
/* IDT设定; */ set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
差不多了,make run 以后,只要敲了键盘,就会相应0x21中断,接着CPU会在中断表中找到这个中断程序(在这里是函数void inthandler21(int *esp))的入口了。于是就会执行这个中断程序,而这个程序的内容就是输出字符串
INT 21 (IRQ-1) : PS/2 keyboard
标签:
原文地址:http://www.cnblogs.com/pengfeiz/p/5785263.html