6、分割编译与中断处理

分割源文件
bootpack.c

#include "bootpack.h"
#include "dsctbl.c"
#include "graphic.c"
#include <stdio.h>

void HariMain(void)  //程序从此处开始运行,函数名不能改
{
    struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
    char s[40], mcursor[256];
    int mx, my;

    init_gdtidt();     //GDT和IDT的初始化
    init_palette();    //设置调色板
    init_screen(binfo->vram, binfo->scrnx, binfo->scrny);  //设置背景

    //显示鼠标
    mx = (binfo->scrnx - 16) / 2; 
    my = (binfo->scrny - 28 - 16) / 2;
    init_mouse_cursor8(mcursor, COL8_008484);
    putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
    sprintf(s, "(%d, %d)", mx, my);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);


    for (;;) 
    {
        io_hlt();
    }
}

bootpack.h

/* asmhead.nas */
struct BOOTINFO { /* 0x0ff0-0x0fff */
    char cyls; /* 启动读硬盘读到何处为止 */
    char leds; /* 启动时键盘LED的状态 */
    char vmode; /* 显卡模式为多少位颜色 */
    char reserve;
    short scrnx, scrny; /* 画面分辨率 */
    char *vram;
};
#define ADR_BOOTINFO    0x00000ff0

/* naskfunc.nas */
void io_hlt(void);
void io_cli(void);
void io_out8(int port, int data);
int io_load_eflags(void);
void io_store_eflags(int eflags);
void load_gdtr(int limit, int addr);
void load_idtr(int limit, int addr);

/* graphic.c */
void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
void init_screen8(char *vram, int x, int y);
void putfont8(char *vram, int xsize, int x, int y, char c, char *font);
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s);
void init_mouse_cursor8(char *mouse, char bc);
void putblock8_8(char *vram, int vxsize, int pxsize,
    int pysize, int px0, int py0, char *buf, int bxsize);

//声明常数,表示哪种色号对应哪种颜色
#define COL8_000000     0
#define COL8_FF0000     1
#define COL8_00FF00     2
#define COL8_FFFF00     3
#define COL8_0000FF     4
#define COL8_FF00FF     5
#define COL8_00FFFF     6
#define COL8_FFFFFF     7
#define COL8_C6C6C6     8
#define COL8_840000     9
#define COL8_008400     10
#define COL8_848400     11
#define COL8_000084     12
#define COL8_840084     13
#define COL8_008484     14
#define COL8_848484     15

/* dsctbl.c */
struct SEGMENT_DESCRIPTOR {   //8个字节
    short limit_low, base_low;
    char base_mid, access_right;
    char limit_high, base_high;
};
struct GATE_DESCRIPTOR {      //8个字节
    short offset_low, selector;
    char dw_count, access_right;
    short offset_high;
};
void init_gdtidt(void);
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar);
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar);
#define ADR_IDT         0x0026f800
#define LIMIT_IDT       0x000007ff
#define ADR_GDT         0x00270000
#define LIMIT_GDT       0x0000ffff
#define ADR_BOTPAK      0x00280000
#define LIMIT_BOTPAK            0x0007ffff
#define AR_DATA32_RW        0x4092
#define AR_CODE32_ER        0x409a

graphic.c

//设置调色板
void init_palette(void)
{
    /*声明了一个常数 table_rgb,相当于汇编中
         table_rgb:
              RESB 48

     static char 相当于汇编中的DB
        */
    static unsigned char table_rgb[16 * 3] = {
        0x00, 0x00, 0x00,   /*  0:黑 */
        0xff, 0x00, 0x00,   /*  1: 亮红 */
        0x00, 0xff, 0x00,   /*  2: 亮绿 */
        0xff, 0xff, 0x00,   /*  3: 亮黄 */
        0x00, 0x00, 0xff,   /*  4: 亮蓝 */
        0xff, 0x00, 0xff,   /*  5: 亮紫 */
        0x00, 0xff, 0xff,   /*  6: 浅亮蓝 */
        0xff, 0xff, 0xff,   /*  7: 白 */
        0xc6, 0xc6, 0xc6,   /*  8: 亮灰*/
        0x84, 0x00, 0x00,   /*  9: 暗红 */
        0x00, 0x84, 0x00,   /* 10: 暗绿*/
        0x84, 0x84, 0x00,   /* 11: 暗黄 */
        0x00, 0x00, 0x84,   /* 12: 暗青 */
        0x84, 0x00, 0x84,   /* 13: 暗紫 */
        0x00, 0x84, 0x84,   /* 14: 浅暗蓝 */
        0x84, 0x84, 0x84    /* 15: 暗灰 */
    };
    set_palette(0, 15, table_rgb);
    return;
}

void set_palette(int start, int end, unsigned char *rgb)
{
    int i, eflags;
    eflags = io_load_eflags();  /* 记录中断许可标志的值 */
    io_cli();               /* 将中断许可标志置为0,禁止中断。访问调色板,首先屏蔽中断,如CLI */
    io_out8(0x03c8, start);         //将想要设定的调色板号码写入0x03c8
    for (i = start; i <= end; i++) {      //再将RGB顺序写入0x03c9
        io_out8(0x03c9, rgb[0] / 4);
        io_out8(0x03c9, rgb[1] / 4);
        io_out8(0x03c9, rgb[2] / 4);
        rgb += 3;
    }
    io_store_eflags(eflags);    /* 复原中断许可标志。如果最初执行了CLI,最后还要执行STI */
    return;
}

//画面上有320*200个像素,左上为(0,0),右下为(319,319)
//像素坐标(x, y)对应的VREAM地址为 0xa0000 + x + y * 320
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
    int x, y;
    for (y = y0; y <= y1; y++) {
        for (x = x0; x <= x1; x++)
            vram[y * xsize + x] = c;  
    }
    return;
}

void init_screen(char *vram, int x, int y)
{
    boxfill8(vram, x, COL8_008484,  0,     0,      x -  1, y - 29);
    boxfill8(vram, x, COL8_C6C6C6,  0,     y - 28, x -  1, y - 28);
    boxfill8(vram, x, COL8_FFFFFF,  0,     y - 27, x -  1, y - 27);
    boxfill8(vram, x, COL8_C6C6C6,  0,     y - 26, x -  1, y -  1);

    boxfill8(vram, x, COL8_FFFFFF,  3,     y - 24, 59,     y - 24);
    boxfill8(vram, x, COL8_FFFFFF,  2,     y - 24,  2,     y -  4);
    boxfill8(vram, x, COL8_848484,  3,     y -  4, 59,     y -  4);
    boxfill8(vram, x, COL8_848484, 59,     y - 23, 59,     y -  5);
    boxfill8(vram, x, COL8_000000,  2,     y -  3, 59,     y -  3);
    boxfill8(vram, x, COL8_000000, 60,     y - 24, 60,     y -  3);

    boxfill8(vram, x, COL8_848484, x - 47, y - 24, x -  4, y - 24);
    boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y -  4);
    boxfill8(vram, x, COL8_FFFFFF, x - 47, y -  3, x -  4, y -  3);
    boxfill8(vram, x, COL8_FFFFFF, x -  3, y - 24, x -  3, y -  3);
    return;
}


void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
    int i;
    char *p, d;
    for (i = 0; i < 16; i++)  //字符的存储:每行8位,共16行,所以一个字符用16个字节
    {
        p = vram + (y + i) * xsize + x;
        d = font[i];
        if ((d & 0x80) != 0) { p[0] = c; }
        if ((d & 0x40) != 0) { p[1] = c; }
        if ((d & 0x20) != 0) { p[2] = c; }
        if ((d & 0x10) != 0) { p[3] = c; }
        if ((d & 0x08) != 0) { p[4] = c; }
        if ((d & 0x04) != 0) { p[5] = c; }
        if ((d & 0x02) != 0) { p[6] = c; }
        if ((d & 0x01) != 0) { p[7] = c; }
    }
    return;
}

void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
    extern char hankaku[4096];  
    for (; *s != 0x00; s++)    //字符串都是以0x00结尾的
    {
        putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
        x += 8;
    }
    return;
}

//制作要显示的字符
void init_mouse_cursor8(char *mouse, char bc)
{
    static char cursor[16][16] = 
    {
        "**************..",
        "*OOOOOOOOOOO*...",
        "*OOOOOOOOOO*....",
        "*OOOOOOOOO*.....",
        "*OOOOOOOO*......",
        "*OOOOOOO*.......",
        "*OOOOOOO*.......",
        "*OOOOOOOO*......",
        "*OOOO**OOO*.....",
        "*OOO*..*OOO*....",
        "*OO*....*OOO*...",
        "*O*......*OOO*..",
        "**........*OOO*.",
        "*..........*OOO*",
        "............*OO*",
        ".............***"
    };
    int x, y;

    for (y = 0; y < 16; y++) 
    {
        for (x = 0; x < 16; x++) 
        {
            if (cursor[y][x] == '*') 
                mouse[y * 16 + x] = COL8_000000;  //黑色

            if (cursor[y][x] == 'O') 
                mouse[y * 16 + x] = COL8_FFFFFF;  //白色

            if (cursor[y][x] == '.') 
                mouse[y * 16 + x] = bc;           //背景色
        }
    }
    return;
}

//进行显示,只需将buf中的数据复制到vram中
//vxsize与vysize与vram有关,vysize=0xa0000, vxsize=320
//pxsize与pysize是想要显示图像的大小,因为鼠标指针大小为16*16,所以两个值都为16
//buf指定图形存放地址,bxsize指定每一行含有的像素值,与pxsize大体相同
//px0与py0为要显示字符的起始像素点
void putblock8_8(char *vram, int vxsize, int pxsize,
    int pysize, int px0, int py0, char *buf, int bxsize)
{
    int x, y;
    for (y = 0; y < pysize; y++) 
    {
        for (x = 0; x < pxsize; x++) 
            vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x];

    }
    return;
}

dsctbl.c

void init_gdtidt(void)
{
    //0x270000到0x27ffff设为GDT,因为这一块内存没有特殊用途
    struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;

    //0x26f800到0x26ffff设为IDT  
    struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) 0x0026f800;  
    int i;

    /* GDT的初始化*/
    for (i = 0; i < 8192; i++) {   //完成对8192个段的设定
        set_segmdesc(gdt + i, 0, 0, 0);   //将每个段的上限、基址、访问权限都设为0
    }

    //将1号段上限设为4G,地址为0,表示CPU所能管理的全部内存本身
    set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);

    //将2号段上限设为512KB,地址为0x280000,正好是bootpack.hrb可以执行的地方
    set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);  
    load_gdtr(0xffff, 0x00270000); //因为C语言不能给GDTR赋值,所以写成汇编

    /* IDT的初始化 */
    for (i = 0; i < 256; i++) 
    {
        set_gatedesc(idt + i, 0, 0, 0);
    }
    load_idtr(0x7ff, 0x0026f800);  //给IDTR赋值

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

Makefile

OBJS_BOOTPACK = bootpack.obj naskfunc.obj hankaku.obj graphic.obj dsctbl.obj

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

MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.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.nas Makefile
    $(NASK) ipl.nas ipl.bin ipl.lst

# 承接启动区,调用C语言
asmhead.bin : asmhead.nas Makefile
    $(NASK) asmhead.nas asmhead.bin asmhead.lst

#使用makefont.exe编译hankaku.txt字库
hankaku.bin : hankaku.txt Makefile
    $(MAKEFONT) hankaku.txt hankaku.bin

#然后连接所必须的接口信息,生成目标文件hankaku.obj,这样就可以与bootpack.obj连接了
hankaku.obj : hankaku.bin Makefile
    $(BIN2OBJ) hankaku.bin hankaku.obj _hankaku

#使用obj2bim.exe将bootpack.obj生成二进制映像文件bootpack.bim,
#这一步是因为C语言不能编写所有的程序,有一部分用汇编来写,然后链接到C语言程序上 
bootpack.bim : bootpack.obj naskfunc.obj hankaku.obj Makefile
    $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
        bootpack.obj naskfunc.obj hankaku.obj
# 3MB+64KB=3136KB

#使用bim2hrb.exe将bootpack.obj生成bootpack.hrb,这是因为映像文件只是将各部分全都链接在一起,
#做成了完整的机器语言文件,为了实际使用,还需要加工,如加上识别的文件头,或者压缩等。
bootpack.hrb : bootpack.bim Makefile
    $(BIM2HRB) bootpack.bim bootpack.hrb 0

#核心OS二进制文件
haribote.sys : asmhead.bin bootpack.hrb Makefile
    copy /B asmhead.bin+bootpack.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

#生成 helloos.img

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) bootpack.nas
    -$(DEL) bootpack.map
    -$(DEL) bootpack.bim
    -$(DEL) bootpack.hrb
    -$(DEL) haribote.sys


#把源程序以外的文件全部删干净

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

初始化PIC
PIC programmable interrupt controller 可编程中断控制器
int.c

/* PIC的初始化 */
void init_pic(void)
{
    io_out8(PIC0_IMR,  0xff  ); /* 禁止所有中断。IMR interrupt mask register 中断屏蔽寄存器 */
    io_out8(PIC1_IMR,  0xff  ); /* 禁止所有中断 */

    //ICW 有4个,ICW1、ICW4与主板的配线方式、中断信号的电气特性有关,都为固定值,ICW3是与主-从连接的设置有关
    io_out8(PIC0_ICW1, 0x11  ); /* 边沿触发模式(edge trigger mode)。ICW initial control word 初始化控制数据 */
    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  ); /* 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;
}

中断程序的制作
鼠标是IRQ12,键盘是IRQ1,编写的相应中断处理程序为INT 0x2c和INT 0x21。
int.c节选

/* 来自 PS/2 键盘的中断 */
void inthandler21(int *esp) //只是显示一条信息,然后保持在待机状态
{
    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();
    }
}

/* 来自PS/2鼠标的中断  */
void inthandler2c(int *esp)
{
    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 2C (IRQ-12) : PS/2 mouse");
    for (;;) {
        io_hlt();
    }
}

中断处理完成后,不能return,而必须执行IRETD指令,只能用汇编写。
naskfunc.nas节选

GLOBAL  _asm_inthandler21, _asm_inthandler2c
EXTERN  _inthandler21, _inthandler2c  ;EXTERN表示在别的源文件中

_asm_inthandler21:
        PUSH        ES
        PUSH        DS
        PUSHAD              ; 相当于PUSH EAX ECX EDX EBX ESP EBP ESI EDI 
        MOV     EAX,ESP
        PUSH        EAX
        MOV     AX,SS
        MOV     DS,AX       ; C语言认为DS ES SS都指同一个段
        MOV     ES,AX
        CALL        _inthandler21   ; 调用别的文件中的函数
        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

将中断函数_asm_inthandler21注册到IDT中,在dsctbl.c中的init_gdtidt中添加

/* IDT的设定 */
    //asm_inthandle21注册在idt的第0x21号,2*8表示asm_inthandle21属于2号段,*8是因为低三位必须为0
    //AR_INTGATE32将IDT的属性设为0x008e,这是这是用于中断处理的有效设定
    set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); 
    set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);

bootpack.c节选

init_gdtidt();  //GDT和IDT的初始化
init_pic();     //PIC初始化
io_sti();    //仅执行STI命令,IF变为1,CPU接收来自外部设备的中断

//PIC属于外部设备,用IN OUT指令进行操作
io_out8(PIC0_IMR, 0xf9); /* PIC1(11111001) */
io_out8(PIC1_IMR, 0xef); /* (11101111) */

当按下键盘后,捕获了中断
这里写图片描述

总结一下中断的制作:
1、在asmhead.nas中添加一些设定,之前只是支持C语言导入,现在还要让它支持鼠标指针。
2、先初始化GDT、IDT,GDT为全局段号记录表,存放在GDTR寄存中,记录段的大小、段的起止位置,段的管理属性。IDT为中断记录表,记录了0~255号的中断号码与调用函数的对应关系。如果GDT还没有设置完就设置IDT会比较麻烦。
3、然后初始化PIC,PIC为可编程中断控制器,它将8个信号合成一个中断信号装置,监视着输入管脚的8个中断信号,只要有一个中断信号进来,就将唯一的输出管脚信号设成ON,并通知CPU。
4、编写中断处理程序,如inthandler21。
5、在naskfunc.nas中编写asm_inthandler,负责中断处理之前的处理,如对寄存的保存,调用中断处理程序,然后是中断之后的处理,返回。
6、将编写的中断处理程序注册到IDT中,如将asm_inthandler注册在IDT的第0x21号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值