Advertisement

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

阅读量:

分割编译与中断处理

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文件中包含"bootpack.h"是为了指示编译器替换特定内容后进行编译。

圆括号(" ")用于表示同一存放位置的头文件名;尖括号(<>)则用于表示由编译器指定的位置。

4. 意犹未尽

6-4-1

此程序用于将指定段落上限参数(limit)和内存地址分配给名为GDTR的48位寄存器。唯一确定GDTR的方法是设定一个内存地址。从指定位置提取6个字节长度的数据块,并将其设置到GDTR寄存器中。负责执行这一操作的是LGDT。

该寄存器用于指定程序运行时的内存起始段地址。该寄存器由系统自动初始化,并被用来确定程序运行所需的最低有效16位区域。这个区域的值等于"有效容量减一"(GDT的有效容量减去一个单位)。此外,在某些特定情况下会用到"上限"这个词来描述类似的概念,它们都指的是某个量的具体数值减去一个单位。在处理高位数据时(即剩下的4个字节),我们关注的是GDT开始地址这一概念。

WORD [ESP +4] 中存储段上限值(例如:FFFF) WORD [ESP +8] 中存储内存起始地址(例如:1234567)
若以字节形式表示,则会显示为 [FFFF FFFF FF27 FFFF] (其中低位位于内存较小字节的位置)

6-4-2

在实施LGDT的过程中,在程序运行时将这些数据初始化为十六进制值FF FF 0xxD(即十六进制值FF FF FF FF xx18)。具体操作步骤如下:首先利用MOV指令将AX寄存器的内容加载到目标地址ESP+4处以便获取初始四位十六进制全1值;随后通过STO指令将该值存储于 ESP+6的位置上从而完成数据的初始化工作。经过上述操作后,在内存中的状态变为六位十六进制全1值接着两位零接着两位零x18(即六位全1后面跟零x18)。这样做的好处在于,在后续操作中可以从 ESP+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位编码来表示段地址,并被称为基址寄存器。具体来说,在内存管理电路中将段地址划分为三个字段:低字节(low)、中间字节(mid)以及高字节(high)。这种划分方式主要是为了与80286系列处理器的兼容性需求而设计的。

段的最大容量最多可达4GB, 但受限于20位, 用户需将指定到1MB为止。为了增加指定段的容量, 系统提供了标记位Gbit的功能选项。当该标记位置为1时, limit参数将不再按字节(Byte)计算, 而是以页(page)为单位进行表示(每页有4KB)。按照此计算方式, 每页4KB乘以1MB等于4GB。而位于20位制限下的两个极限值则分别存储在limit_low和limit_high两个变量中。

段访问权限控制属性(Segment Access Control Attribute),通常用变量名access_right或ar来进行标识。其中12位字段的最高4位被分配到limit_high的相应高位位置上,在程序处理时将ar视为一个包含16位的数据字段来进行运算:
xxxx0000xxxxxxxx(其中x代表二进制中的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的状态取决于正在运行的应用程序所处的内存权限位。

5. 初始化PIC

需使用鼠标时需使用中断,并需准确无误地初始化GDT和IDT。“还需初始化PIC(programmable interrupt controller),“可编程中断控制器”。PIC负责将8个中断信号集合成一个中断信号。”

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指令与外设进行交互以完成数据传输任务.在本系统中,我们采用的是MIC与ACC两种控制器模式,其中一个是主控制器(MIC),另一个则是辅助控制器(ACC).

PIC的所有寄存器均为8位寄存器;IMR是"interrupt mask register"的缩写, 其作用是"中断屏蔽寄存器". 每个IMR配置对应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. 中断处理程序的制作

鼠标设备属于 IRQ 12 装置,键盘设备属于 IRQ 1 装置。因此,在该系统中使用了 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指针的值,在此过程中该函数并未被利用。尽管在中断处理完成之后无法直接返回主程序(UseReturn),但必须执行IRCSTD指令。这一指令仍需以NASC的方式进行编码。

复制代码
    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, -4
    MOV [SS:ESP], EAX
    POP EAX指令相当于:
    MOV EAX, [SS:ESP]
    ADD ESP,4

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

复制代码
    PUSH EAX
    PUSH ECX
    PUSH EDX
    PUSH EBX
    PUSH ESP
    PUSH EBP
    PUSH ESI
    PUSH 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"实际上是基于二进制运算符"<<"来进行位移操作的结果(即乘以二的三次方)。因此,“'2×8'这种表示方式也可以等价地用'bitwise shift operation '的形式来表达(即等于十六进制数值16)。

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

该语句表明这一段彻底覆盖了整个bootpack.hrb文件。最终的AR_INTGATE32字段用于将IDT属性赋值为0x008e,这表明该配置适用于中断处理。

全部评论 (0)

还没有任何评论哟~