(今天先将bootpack.c文件分成三个文件,然后又对makefile使用一般规则替代普通生成规则,后又将重复的内容提炼到bootpack.h头文件中,
引入头文件声明函数和宏定义,“”表示文件在同一个文件夹里,<>表示头文件位于i按一起所提供的文件夹里。
makefile中引入一般规则,类似有.*导入python包。
1.GDTR和IDTR详细讲解
汇编指令LGDT:从指定地址中读取6字节数据到GDTR寄存器里。GDTR高32位代表GDT开始地址,低16位的代表段上限。
关于为什么MOV AX,[ESP+4]?
开始DWORD[ESP+4]里存放的是段上限(即0x0000ffff),DWORD[ESP+8]存放的是地址(即0x00270000)。按照字节写出来是[FF FF 00 00 00 00 27 00](注意低位放在内存地址小的字节里)。MOV AX,[ESP+4]读取最初的2字节0xffff,MOV [ESP+6],AX将其0xffff写入[ESP+6]里,最后变成[FF FF FF FF 00 00 27 00]。
_load_gdtr: ; void load_gdtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LGDT [ESP+6] //将[ESP+6]的内容加载到GDTR
RET
_load_idtr: ; void load_idtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LIDT [ESP+6]
RET
段的8字节信息分别是什么:a、段的大小,由limit_low(2字节)和limit_high(低4位)共20位表示。b、段的起始地址。由基址low_base(2字节)、mid_base(1字节)、high_base(1字节)表示。c、段的管理属性。由access_right(1字节)和limit_high(高4位)共12位。这些信息都存在结构体sd中。具体赋值过程见void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)。
段属性里有Gbit位,当Gbit为1时,段上限表示的数据要×4KB(1页)。
;dsctbl.c
struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
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;
}
12位段数性:12位段数高4位放在limit_high里,低8位放在acess_right里。
其中高四位到386才能使用,由“GD00”构成,G指之前说的Gbit;D指段的模式(1指32位模式,0指16位模式)。低八位具有固定搭配,比如(0x00表示未使用)、(0x92表示系统专用,可读写的段,不可直行)。
2.初始化PIC
PIC(可编程中断控制器programmable interrupt controller)是将8个中断信号集合成一个中断信号的装置。
初始化PIC:
;int.c
void init_pic(void)
/* PIC初始化 */
{
io_out8(PIC0_IMR, 0xff ); /* 禁止所有中断 */
io_out8(PIC1_IMR, 0xff ); /* 禁止所有中断 */
io_out8(PIC0_ICW1, 0x11 ); /* 边沿触发模式 */
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 ); /* 边沿触发模式 */
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;
}
PIC相对CPU是外部设备,CPU使用OUT指令进行操作。PIC0、PIC1分别指主从PIC。其中端口号位于bootpack.h文件中,ICW1、ICW2、ICW3、ICW4端口号相同,但是有一套输出规则——ICW1之后必须紧跟ICW2可以区分。
PIC的寄存器:IMR(中断屏蔽寄存器,interrupt mask register),8位分别对应8路IRQ信号。如 果某一位为1,对应IRQ信号被屏蔽,PIC忽略该路信号。
ICW(初始化控制数据,initial control word),ICW1于主板配线方式有关、ICW3 与主从连接相关(00000100和2)、ICW4与电气特性有关,是固定值。 ICW2决定IRQ以哪一号中断通知CPU,即CPU之后会调用的显卡BIOS函数 号码。
(CPU允许中断处理后,命令PIC发送2字节数据,PIC发送的数据为:0xcd+0x20(翻译成汇编就是INT 0x20),然后CPU就会调用BIOS的0x20号函数。)
3.编写键盘和鼠标中断调用程序
1.初始化IDT、GDT时,将IDT设定(idt + 0x21——IDT对应的bios函数号码,IDT内存位置;
(int) asm_inthandler21——函数位置;2 * 8——asm_inthandler21属于哪一个段:AR_INTGATE32——设定IDT属性)写入内存中:
;bootpack.c
void init_gdtidt(void)
{
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) ADR_IDT;
int i;
/* GDT偺弶婜壔 */
for (i = 0; i <= LIMIT_GDT / 8; i++) {
set_segmdesc(gdt + i, 0, 0, 0);
}
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW);
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
load_gdtr(LIMIT_GDT, ADR_GDT);
/* IDT偺弶婜壔 */
for (i = 0; i <= LIMIT_IDT / 8; i++) {
set_gatedesc(idt + i, 0, 0, 0);
}
load_idtr(LIMIT_IDT, ADR_IDT);
/* 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);
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.功能是啥?
;naskfun.nas
EXTERN _inthandler21, _inthandler27, _inthandler2c
_asm_inthandler21:
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler21
POP EAX
POPAD
POP DS
POP ES
IRETD
3.编写用于INT ox21的中断处理程序:
;int.c
void inthandler21(int *esp)
/* PS/2僉乕儃乕僪偐傜偺妱傝崬傒 */
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard");
for (;;) {
io_hlt();
}
}