《30天自制操作系统》 day6 小结

来源:互联网 发布:留美幼童 知乎 编辑:程序博客网 时间:2024/06/07 13:06

分割编译与中断处理

1. 分割源文件

分割文件的优劣:

优 劣 (1)按照处理内容进行分类,如果分的好的话,将来进行修改时,容易找到地方。 (5)源文件数量增加。 (2)如果Makefile写的好,只需要编译修改过的文件,就可以提高make的速度。 (6)分类分得不好的话,修改时不容易找到地方。 (3)单个源文件都不长,多个小文件比大文件好处理。 (4)看起来很酷。

Bootpack.c的分割:

6-1-1

Makefile根据bootpack.c的分割后应增加的功能:

6-1-2

2/3. 整理Makefile/头文件

make.exe会首先寻找普通的生成规则如果没有找到,就尝试用一般规则。普通生成规则要优于一般规则。
将c文件里重复的声明添加到.h头文件中,然后在其余.c文件中增加#include “bootpack.h”表示告诉编译器,这里要替换成指定的内容,然后进行编译。

头文件名用(”“)表示该头文件与源文件位于同一个文件夹里,而尖括号(<>)则表示该头文件位于编译器所提供的文件夹里。

4. 意犹未尽

6-4-1
这个函数用来将指定的段上限(limit)和地址赋值给名为GDTR的48位寄存器。给GDTR赋值唯一的办法是指定一个内存地址,从指定的地址读取6个字节(48位),然后赋值给GDTR寄存器,完成这一任务的就是LGDT。

该寄存器的低16位(即内存的最初2个字节)是段上限,它等于“GDT的有效字节数-1”。今后我们还会偶尔用到上限这个词,意思都是表示量的大小,一般为“字节数-1”剩下的高32位(即剩余的4个字节),代表GDT的开始地址。

DWORD[ESP+4]里存放段上限(0x0000ffff),DWORD[ESP+8]里存放地址(0x00270000)。若是按字节写出来就成了FF FF 00 00 00 27 00 00。
6-4-2

为了执行LGDT,作者希望把它们排列成[FF FF 00 27 00 00]的样子。所以就先”MOV AX, [ESP+4]”读取最初的0xffff,然后再写到[ESP+6]里。结果就成了[FF FF FF FF 00 27 00 00],如果从[ESP+6]开启读6个字节的话正好是想要的结果,即如下图:

6-4-3

naskfunc.nas的_load_idtr对IDTR的设置与GDTR类似。

另外GDT,LDT,GDTR,LDTR 的详解可以在这里看到:
http://www.techbulo.com/708.html

6-4-4

这个函数是按照CPU的规格要求,将段的信息归结成8个字节写入内存。填入内容在前一章已经提到过。

首先是段的地址。地址用32位表示,称为段的基址(base)。在这里分为low(2字节),mid(1字节)和high(1字节)3段,合起来32位。是为了与80286时代的程序兼容才分为三段。

段上限最大4GB,但只能使用20位,指定到1MB为止。为了扩大指定段的大小,段属性里设置了一个标志位Gbit。当这个标志位位1时,limit的单位不解释成字节(BYTE),而解释成页(page,有4KB)。这样4KBx1M=4GB。20位的段上限分别写到limit_low和limit_high里。

段属性,又称为“段的访问控制权属性”,在程序中用变量名access_right或ar来表示。因为12位段属性中的高4位放在limit_high的高4位里,所以程序里有意把ar当做如下的16位构成来处理:
xxxx0000xxxxxxxx(其中x是1或1)

ar的高四位被称为“扩展访问权”

G D 0 0 Gbit 指段模式 1指32位模式 0指16位模式 除运行80286程序外,通常都使用D=1

ar的低8位:

00000000(0x00) 未使用的记录表(description table) 10010010(0x92) 系统专用,可读写的段。不可执行。 Ring0系统模式 10011010(0x9a) 系统专用,可执行的段。可读不可写。 11110010(0xf2) 应用程序专用,可读写的段。不可执行。 Ring3应用模式 11111010(0xfa) 应用程序专用,可执行的段。可读不可写。

CPU到底是处于系统模式还是应用模式取决于执行中的应用程序是位于访问权位0x9a的段还是位于访问权位0xfa的段。

5. 初始化PIC

要使用鼠标必须使用中断,将GDT和IDT正确无误地初始化。同时还要初始化PIC(programmable interrupt controller),“可编程中断控制器”。PIC是将8个中断信号集合成一个中断信号的装置。与CPU直接相连的PIC称为主PIC(master PIC),与主PIC相连的PIC称为从PIC(slave PIC)。主PIC负责处理第0到第7号中断信号,从PIC负责8到15.

6-5-1

PIC的初始化:
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  ); /* IRQ0-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;}

从CPU角度来看,PIC是外部设备,CPU使用OUT指令进行操作。程序中的PIC0和PIC1,分别指主PIC和从PIC。

PIC的寄存器都是8位寄存器,IMR是“interrupt mask register”的缩写,意思是“中断屏蔽寄存器”。8位分别对应8路IRQ信号。

ICW是“initial control word”的缩写,意为“初始化控制数据”。注:只有在电脑CPU里,word这个词才是16位的意思,在别的设备上有时指8位有时也指32位。ICW有4个,分别编号为1~4,共有4个字节的数据。ICW1和ICW4与IPC主板配线方式、中断信号的电气特性有关。ICW3是有关主-从连接的设定,对主PIC而言,第几号IRQ与从PIC相连,使用8位来设定的。如果把这些位全部设为1,那么主PIC就能驱动8个从PIC。因此不同的操作系统可以进行独特设定的就只有ICW2了、这个ICW2,决定了IRQ以哪一号中断通知CPU。

6. 中断处理程序的制作

鼠标是IRQ12,键盘是IRQ1,所以我们编写用了INT 0x2c和INT 0x21的中断处理程序,即中断发生时所要调用的程序。

void inthandler21(int *esp)/*来自PS/2键盘的中断*/{    struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;    boxfill8(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, *INT 21 (IRQ-1) : PS/2 kwyboard*);    for (;;)    {        io_hlt();    }}

Inthandle12接收了esp指针的值,但函数中没有使用。中断处理完成后不能执行“return”但必须执行IRSTD指令,这个指令还是得用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

接下来解释了栈(stack)的概念,栈属于缓冲区。

缓冲区有两种:一种是将信息从上面逐渐加入进来,需要时再从下面一个个取出。

6-6-1

最先加入的信息也最先取出,所以这种缓冲区是“先进先出”,简称FIFO。

还有一种缓冲区是信息从上加入,也从最上面开始取。所以这种缓冲区是“先进后出”,简称FILO

6-6-2

栈正是FILO型的缓冲区,PUSH将数据压入栈顶,POP将数据从栈顶取出。

PUSH EAX这个指令相当于:

ADD ESP, -4MOV [SS:ESP], EAXPOP EAX指令相当于:MOV EAX, [SS:ESP]ADD ESP,4

还有一个不常见的指令PUSHAD相当于:

PUSH EAXPUSH ECXPUSH EDXPUSH EBXPUSH ESPPUSH EBPPUSH ESIPUSH EDI

反过来POPAD指令同上相反顺序。

函数_asm_inthandler21的目的是保存中断现场的值,然后将该函数注册到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);

asm_inthandler注册在idt的第0x21号,2*8表示的是asm_inthandler的属于哪一个段,即段号是2,乘以8是因为低3位有着别的意思,所以低三位必须是0。所以“2*8”也可以写成“2<<3”或者16。

号码为2的段:set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);

这个语句说明这个段正好涵盖了整个bootpack.hrb。最后的AR_INTGATE32将IDT的属性设定为0x008e,表示这是用于中断处理的有效设定。

0 0
原创粉丝点击