第6天:分割编译与中断处理
封装打包程序
- 为了缩短源程序,对bootpack.c进行分类,增加源文件的数目,减少代码行数,但是用到的函数还是要在主函数前面声明,整合到一起的时候也需要加上不同的头文件名字。大致是下面的图示过程:
- 分割之后,Makefile变长,三个文件需要做同样的过程才能生成.gas和.nas文件,生成一个规则(一般规则)来编译,实际就是把这一段:
graphic.gas : graphic.c Makefile
$(CC1) -o graphic.gas graphic.c
graphic.nas : graphic.gas Makefile
$(GAS2NASK) graphic.gas graphic.nas
graphic.obj : graphic.nas Makefile
$(NASK) graphic.nas graphic.obj graphic.lst
dsctbl.gas : dsctbl.c Makefile
$(CC1) -o dsctbl.gas dsctbl.c
dsctbl.nas : dsctbl.gas Makefile
$(GAS2NASK) dsctbl.gas dsctbl.nas
dsctbl.obj : dsctbl.nas Makefile
$(NASK) dsctbl.nas dsctbl.obj dsctbl.lst
变成:
%.gas : %.c Makefile
$(CC1) -o $*.gas $*.c
%.nas : %.gas Makefile
$(GAS2NASK) $*.gas $*.nas
%.obj : %.nas Makefile
$(NASK) $*.nas $*.obj $*.lst
最后再整合成bootpack.h:包含了结构体、颜色还有全部函数的声明。只需要在调用的时候在前面加上#include”bootpack.h”这句就可以了。最终精简版结果:
昨天的问题
_load_gdtr: ; void load_gdtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LGDT [ESP+6]
RET
- 这函数是用来把段上限和地址赋值到GDTR的48位寄存器中,不能用mov指令,只能从地址中读取6个字节,可以用AX寄存器辅助。寄存器的低16位是段上限,等于“GDT的有效字节数-1”,剩下的高32位代表GDT的开始地址。
最初状态: DWORD[ESP+4](段上限):0x0000ffff
DWORD[ESP+8](地址):0x00270000
先把上面esp+4的两个字节放入AX寄存器中,在把寄存器的值放入esp+6中。
Set_segmdesc函数:
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; //高2字节
return;
}
结构体SEGMENT_DESCRIPTOR的八个字节:
struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
- 包含了段大小,起始地址,管理属性。地址用32位表示,用base表示,其中low占2个字节,high占1个字节,mid一个字节,所以段上限(limit)只能用20位,如果32位就满了,这样一来设置了一个标志位G表示Gbit,标志位为1解释成页,相当于4KB。段上限20位最大为1MB,4KB×1MB=4GB。Limit_low和limit_high里面保存段上限其中limit_high的高四位写入段属性。最后剩下的12位是剩下的段属性-“段的访问权属性”用access_right或ar表示,合并高四位中的4位就变成16位:xxxx0000xxxxxxxx,高四位称为“扩展访问权”到386时代以后才能使用,一般是“GD00”,G是上面说到的G,D为1表示32位,为0表示16位,除了运行80286程序之外都用D=1的模式。
- 第八位的主要模式:
这段设定是有关CPU的系统模式还是应用模式,如果在应用模式执行LGDT是被阻止的,如果应用程序段是0x9a则为系统模式,0xfa则为应用模式。
初始化PIC
- PIC是将8个中断信号集合成一个中断信号的装置,起到一个监视的功能,接受到中断信号之后再传给CPU,为了能处理更多的中断增加了一个从PIC。CPU只能接受主PIC传送的信号,只有主PIC向CPU传送从PIC数据的时候,从PIC的中断信号才有用。
硬件设定:
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内部有8个寄存器对应8路IRQ信号,如果某一位值为1,则该位对应的IRQ信号被屏蔽。ICW是“初始化控制数据”,有四个(1-4)ICW1和ICW4与PIC主板配线方式,中断信号的电气特性有关,一般都是固定值。ICW3是有关主-从的连接设定,硬件上设定IRQ2与从PIC相连,所以设定为0000010。最后的ICW2才是操作系统能独立设定的,决定IRQ以哪一号通知CPU,中断信号IRQ 0-15对应INT 0x20-0x2f,INT 0x00-0x1f被CPU的系统保护占用。
中断处理程序和注册中断IDT
- 鼠标是IRQ12键盘是IRQ1,用到的中断就是INT 0x2c和INT 0x21。
键盘的中断处理:
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();
}
}
中断处理之后不能执行“return”只能通过执行IRETD指令,用汇编语言修改naskfunc.nas
_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
- CALL _inthandler21就是键盘的中断,调用这个函数,前面都是做中断前的“现场保护”工作,把寄存器的值压入栈中保存,三个段寄存器相等(DS/ES/SS)。中断处理之后再把栈中的值放出来,做“恢复现场”的工作。最后就是将这个函数_inthandler21注册到IDT中,在dsctbl.c的init_gdtidt里面加入:
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
- _inthandler21注册到idt的第0x21号,2 * 8表示 asm_inthandler21属于哪一个段,段号是2乘以8是因为低三位必须是0(段寄存器第三位不用)。
- 关于号码为2的段:set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);这个段正好涵盖了整个bootpack.hrb,最后的AR_CODE32_ER将IDT属性设定为0x008e,表示用于中断处理的有效设定。在HariMain的最后,修改了PIC的IMR,以便接受来自键盘和鼠标的中断。
- 补充指令“io_sti();”仅仅是执行STI指令,是CLI的逆指令,执行STI指令之后,IF(中断许可标志位)变为1,CPU接受外部中断。下面是按下键盘之后的状态