自制操作系统 (六) 分割编译与中断处理

2016-05-08 

参考书籍:《30天自制操作系统》、《自己动手写操作系统》

 

分割编译主要围绕这两张图:

然后是Makefile的内容:

OBJS = c_main.obj assemblyFunc.obj hankaku.obj graphic.obj dsctbl.obj int.obj

TOOLPATH = ../z_tools/
INCPATH  = ../z_tools/haribote/

MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.exe
NASM     = $(TOOLPATH)nasm.exe
CC1      = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM  = $(TOOLPATH)obj2bim.exe
MAKEFONT = $(TOOLPATH)makefont.exe
BIN2OBJ  = $(TOOLPATH)bin2obj.exe
BIM2HRB  = $(TOOLPATH)bim2hrb.exe
RULEFILE = $(TOOLPATH)haribote/haribote.rul
EDIMG    = $(TOOLPATH)edimg.exe
IMGTOL   = $(TOOLPATH)imgtol.com
COPY     = copy
DEL      = del

default :
    $(MAKE) img

ipl.bin : ipl.asm Makefile
    $(NASM) ipl.asm -o ipl.bin

main.bin : main.nas Makefile
    $(NASK) main.nas main.bin main.lst

hankaku.bin : hankaku.txt Makefile
    $(MAKEFONT) hankaku.txt hankaku.bin

hankaku.obj : hankaku.bin Makefile
    $(BIN2OBJ) hankaku.bin hankaku.obj _hankaku

c_main.bim : $(OBJS) Makefile
    $(OBJ2BIM) @$(RULEFILE) out:c_main.bim stack:3136k map:c_main.map \
        $(OBJS)
# 3MB+64KB=3136KB

c_main.hrb : c_main.bim Makefile
    $(BIM2HRB) c_main.bim c_main.hrb 0

haribote.sys : main.bin c_main.hrb Makefile
    copy /B main.bin+c_main.hrb haribote.sys

haribote.img : ipl.bin haribote.sys Makefile
    $(EDIMG)   imgin:../z_tools/fdimg0at.tek \
        wbinimg src:ipl.bin len:512 from:0 to:0 \
        copy from:haribote.sys to:@: \
        imgout:haribote.img

%.gas : %.c Makefile
    $(CC1) -o $*.gas $*.c

%.nas : %.gas Makefile
    $(GAS2NASK) $*.gas $*.nas

%.obj : %.nas Makefile
    $(NASK) $*.nas $*.obj $*.lst

img :
    $(MAKE) haribote.img

run :
    $(MAKE) img
    $(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
    $(MAKE) -C ../z_tools/qemu

install :
    $(MAKE) img
    $(IMGTOL) w a: haribote.img

clean :
    -$(DEL) *.bin
    -$(DEL) *.lst
    -$(DEL) *.gas
    -$(DEL) *.obj
    -$(DEL) c_main.nas
    -$(DEL) c_main.map
    -$(DEL) c_main.bim
    -$(DEL) c_main.hrb
    -$(DEL) haribote.sys

src_only :
    $(MAKE) clean
    -$(DEL) haribote.img

dsctbl.c:

#include "c_head.h"

/**
 *author:      无    名
 *date:        2016.06.08
 *description: init GDT IDT
 **/


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_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;
}

其中作为参数传入的构造函数在c_head.h:

/* dsctbl.c */
struct SEGMENT_DESCRIPTOR {
    short limit_low, base_low;
    char base_mid, access_right;
    char limit_high, base_high;
};
struct GATE_DESCRIPTOR {
    short offset_low, selector;
    char dw_count, access_right;
    short offset_high;
};

SEGMENT_DESCRIPTOR里,段地址为base,分为low(二字节)、mid(一字节)、high(一字节)三部分,共32位(4GB)。

分为3段是为了与80286时代的程序兼容。

段上限表示一个段可以有多少字节。段上限最大是4GB,也就是一个32位的数值,如果直接放进去,这个数值本身就要占用4字节,再加上基址,一共要8字节,会把结构体占满。所以段上限只能20位。即1MB。

因此在段的属性里设了一个标志位,叫做Gbit。这个标志位是1的时候,limit的单位不解释成字节(byte),而解释成页(page)。1页为4KB。4KB*1M=4GB。

这20位的段上限分别写到limit_low和limit_high里。看起来它们好像是总共有3字节,即24位,但实际上我们接着要把段属性写入limit_high的高4位里,所以最后段上限还是只有20位。

最后12位的段属性。段属性又称为“段的访问权属性”,在程序中用变量名access_right或ar来表示。因为12位段属性中的高4位放在limit_high的高4位里,所以程序有意把ar当作如下的16位构成来处理:

xxxx0000xxxxxxxx

ar的高4位为“扩展访问权”。为什么这么说呢?因为这高四位的访问属性在80286的时代还不存在,到386以后才可以使用。这4位是“GD00”构成的,其中G是指刚才所说的G bit,D是指段的模式,1是指32位模式,0是指16位模式。这里出现的16位模式主要只运用于运行80286的程序,不能用于调用BIOS。所以除了运行80286程序以外,通常都用D=1模式。

ar的低8位从80286时代就已经有了:

00000000(0x00):未使用的记录表

10010010(0x92):系统专用,可读写的段。不可执行。

10011010(0x9a):系统专用,可执行的段。可读不可写。

11110010(0xf2):应用程序用,可读写的段。不可执行。

11111010(0xfa):应用程序用,可执行的段。可读不可写。 

关于gdtr的加载的代码:

_load_gdtr:        ; void load_gdtr(int limit, int addr);
        MOV        AX,[ESP+4]        ; limit
        MOV        [ESP+6],AX
        LGDT    [ESP+6]
        RET

书中这样说:

“这个函数用来指定的段上限和地址值赋值给名为GDTR的48位寄存器。这是一个很特别的48位寄存器,并不能用我们常用的MOV指令来赋值。给它赋值的时候,唯一的方法就是指定一个内存地址,从指定的地址读取6个字节(也就是48位),然后赋值给GDTR寄存器。完成这一任务的指令,就是LGDT。

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

在最初执行这个函数的时候,DWORD[ESP+4]里存放的是段上限,DWORD[ESP+8]里存放的是地址。具体到实际的数值,就是0x0000ffff和0x00270000 。把它们按字节写出来的话,就成了[FF FF 00 00 00 00 27 00](要注意低位存放在内存地址小的字节里)。为了执行LGDT,笔者希望把它们排列成[FF FF 00 00 27 00]的样子,所以就先用"MOV AX,[ESP+4]"读取最初的0xfffff,然后再写到[ESP+6]里。这样,结果就成了[FF FF 00 00 27 00],如果从[ESP + 6]开始读6字节的话,正好是我们想要的结果。“

如果你不了解保护模式下的寻址原理的话,是读不懂这些文字的,http://www.techbulo.com/708.html读这篇文章后再对照这段文字,就可以理解了。

 

初始化PIC:

与CPU直接相连的PIC称为主PIC(0-7号中断),与主PIC相连的PIC称为从PIC(8-15号中断)。从PIC通过第二号IRQ与主PIC相连。

int.c:

#include "c_head.h"

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-7接收 */
    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;
}

void inthandler21(int *esp)
/* 来自PS/2键盘的中断 */
{
    struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
    draw_box(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();
    }
}

void inthandler2c(int *esp)
{
    struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
    draw_box(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 2C (IRQ-12) : PS/2 mouse");
    for (;;) {
        io_hlt();
    }
}

void inthandler27(int *esp)                                    
{
    io_out8(PIC0_OCW2, 0x67); 
    return;
}

PIC初始化部分配合c_head.h下面的声明:

/* int.c */
void init_pic(void);
void inthandler21(int *esp);
void inthandler27(int *esp);
void inthandler2c(int *esp);
#define PIC0_ICW1        0x0020
#define PIC0_OCW2        0x0020
#define PIC0_IMR        0x0021
#define PIC0_ICW2        0x0021
#define PIC0_ICW3        0x0021
#define PIC0_ICW4        0x0021
#define PIC1_ICW1        0x00a0
#define PIC1_OCW2        0x00a0
#define PIC1_IMR        0x00a1
#define PIC1_ICW2        0x00a1
#define PIC1_ICW3        0x00a1
#define PIC1_ICW4        0x00a1

可以看出是通过向内存指定位置中写入内容来作PIC初始化。

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

ICW是“initial control word”的缩写,意为“初始化控制数据”。

其中有这样一句

io_out8(PIC0_ICW2,0x20);表明IRQ7-14由INT20-27接收。

io_out8(PIC0_ICW2,0x28);表明IRQ8-15由INT28-2f接收。

而后具体的中断调用还需要汇编语句:

_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

_asm_inthandler27:
        PUSH    ES
        PUSH    DS
        PUSHAD
        MOV        EAX,ESP
        PUSH    EAX
        MOV        AX,SS
        MOV        DS,AX
        MOV        ES,AX
        CALL    _inthandler27
        POP        EAX
        POPAD
        POP        DS
        POP        ES
        IRETD

_asm_inthandler2c:
        PUSH    ES
        PUSH    DS
        PUSHAD
        MOV        EAX,ESP
        PUSH    EAX
        MOV        AX,SS
        MOV        DS,AX
        MOV        ES,AX
        CALL    _inthandler2c
        POP        EAX
        POPAD
        POP        DS
        POP        ES
        IRETD

通过汇编语句调用对应的C语言函数。

上面代码是中断函数,首先是汇编语言部分,汇编函数中调用C语言函数。而这个中断函数是怎样和之前初始化的PIC联系起来呢?

这便要通过IDT的设定。dsctbl.c:

    set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);

asm_inthandler21注册在idt的第0x21号,这样一来如果发生中断了。CPU就会调用asm_inthandler21。

转载于:https://www.cnblogs.com/rixiang/p/5470600.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值