zoukankan      html  css  js  c++  java
  • 自制操作系统 (六) 分割编译与中断处理

    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_toolsqemufdimage0.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。

  • 相关阅读:
    主键、外键
    框架学习八:Model查询
    框架学习七:自动验证、填充、字段映射
    框架学习六:ORM方式添加数据
    11.0 C++远征:对象指针
    10.0 C++远征:深拷贝与浅拷贝
    9.0 C++远征:对象成员
    8.0 C++远征:对象数组
    7.0 C++远征:封装小结
    2.0 C++远征:类内定义与内联函数
  • 原文地址:https://www.cnblogs.com/rixiang/p/5470600.html
Copyright © 2011-2022 走看看