《30天自制操作系统》06_day_学习笔记

harib03a:
  内容没有变化 ;P109 从这里开始,代码开始工程化了.
  将原本300多行的bootpack.c分割成了三部分:
        graphic.c      : 用来处理界面图像
        dsctbl.c        : 用来处理中断和段表(GDT,IDT)等
        bootpack.c    : 和后面的bootpack.h文件一起用来封装函数
  修改了Makefile中的文件生成步骤(当然后面需要bootpack.h 头文件):
    修改前:bootpack.c-->bootpack.bim
    修改后:graphic.c  -->graphic.obj
        dsctbl.c    -->dsctbl.obj
        bootpack.c-->bootpack.obj
        graphic.obj+dsctbl.obj+bootpack.obj+其他接口信息-->bootpack.bim
harib03b:
  整理Makefile;简化了Makefile中的一些内容;
  感觉原理和DBMS中的LIKE字符串匹配神似,有木有。
  看看这个书本上的这个例子:归纳成一般规则:

bootpack.gas : bootpack.c Makefile
  $(CC1) -o bootpack.gas bootpack.c
graphic.gas : graphic.c Makefile
  $(CC1) -o graphic.gas graphic.c
dsctbl.gas : dsctbl.c Makefile
  $(CC1) -o dsctbl.gas dsctbl.c
//------------------------------------
%.gas : %.c Makefile
  $(CC1) -o $*.gas $*.c

harib03c:
  P111 整理头文件;这里实际上还是在做代码优化工作;为后续准备
  我们发现bootpack.c被分割之后,经常需要调用bootpack.c中的函数
  因此,来了一个头文件bootpack.h;把bootpack.c中实现的函数封装起来
  这样每次需要调用函数时只要#include一个bootpack.h 就 OK了!!

/* bootpack.h */
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);
/* Cgraphic.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);

  接下来我们来看下LGDT和LIDT是怎么个情况:

  LGDT和LIDT都是分别是给48为的段寄存器GDTR和48位的中断寄存器IDTR赋值,他们的原理基本相同;下面以LGDT为例详细说明一下。

  1、段信息结构体struct SEGMENT_DESCRIPTOR (如下图所示)

    • 包括段的大小;
    • 段的起始地址;
    • 段的管理属性;

    

  按照CPU的结构要求,将段信息归结成8个字节写入内存中;之后再由指令LGDT装入段寄存器GDTR中;

  注意:寄存器GDTR长度为6个字节。写入段寄存器中的信息只是短信息(8个字节)中的高位的6个字节,后面两个字节存储的是段的管理属性-access_right ;

//请对照上面图片看;
//表示段信息的结构体(一共48位) //段地址 32位:base_low(2字节)+base_mid(1字节)+base_high(1字节) //剩余4个字节||||我们知道段上限为4GB;一个32位的数值 //如果剩余这4个字节全部用来表示段上限那么就没有空间表示段信息了; //HOW TO DO?? //段上限 20位:段属性中的标志位Gbit为1时,limit的单位不解释成字节,而翻译成页(1页=4KB) // limit_low 和limit_high //注 意:limit_high中的上4位写的是段属性 struct SEGMENT_DESCRIPTOR { //段信息结构体定义8字节 short limit_low, base_low; //2字节 char base_mid, access_right; //1字节 char limit_high, base_high; //1字节 }; struct GATE_DESCRIPTOR { //中断信息结构体定义8字节 short offset_low, selector; char dw_count, access_right; short offset_high; };
/*将段信息的8个字节写入内存;便于段寄存器读取;
;struct SEGMENT_DESCRIPTOR  在头文件中;
;段大小;;段起始地址;;段的管理属性(写入,执行,系统专用等) */
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;
}

   2、LGDT和LIDT的具体实现

    

;LGDT给48位寄存器GDTR赋值;
;方法:从指定地址的高6个字节;
;底16位为段上限;高32位为GDT表开始的地址
_load_gdtr:        ; void load_gdtr(int limit, int addr);
        MOV        AX,[ESP+4]        ; limit
        MOV        [ESP+6],AX
        LGDT       [ESP+6]
        RET
;LIDT赋值,原理和上面基本相同
_load_idtr:        ; void load_idtr(int limit, int addr);
        MOV        AX,[ESP+4]        ; limit
        MOV        [ESP+6],AX
        LIDT       [ESP+6]
        RET

   3、段管理属性ar(limit_high的高四位+access_right)

    •   在段信息的结构体中,我们可以看到access_right定义类型为char,一个字节(8位);
    •   段管理属性是有两部分组成的:段上限(大小)的高四位(也就是limit_high的高四位)+access_right(8位) = 12位
    •   ar的高四位是“拓展访问权限”
    •   ar的底8位是决定系统模式和应用模式的(有用户进程和系统进程的感觉,有木有)

 harib03d:

   请对照书P117的图看  初始化PIC(programmable interrupt controller-可编程中断控制器),顾名思义:控制中断的东西

  CPU单独只能处理一个中断,因此需要辅助芯片来帮助CPU处理不同的中断信号;

  主PIC(PIC0)和CPU相连;从PIC( PIC1 )和主PIC的IRQ2相连;扩展了16个中断口

  PIC芯片的主要寄存器:

    IMR:中断屏蔽寄存器;8位,对应8路IRQ信号;信号1屏蔽该路中段;

    ICW:初始化控制数据;一共有4个寄存器

    •  ICW1:定值,由主板配线方式决定;
    •  ICW4:定值,由主板配线方式决定;
    •  ICW3:定值,主-从CPI的连接设定;(以上三个都由芯片本身的属性决定)
    •  ICW2:16位,对应从IRQ0-IRQ15;决定PIC芯片发送哪一号中断通知CPU。
/* 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  ); /* 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;
}

 harib03e:

   设置键盘和鼠标中断;笔者将键盘中断设在了PIC的IRQ12,将鼠标设在了IRQ1;

   调用的终端号分别为0x2c(0010 1100) 和 0x21(0010 0001)   可以对照P117的PIC芯片图看。

void inthandler21(int *esp)
/* PS/2的键盘中断 */
{
    struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;  //加载BIOS
    boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);   
    //获取键盘中断之后,显示这个字符串INT 21 (IRQ-1) : PS/2 keyboard
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard");  
    for (;;) {   //之后 让CPU等着 !!
        io_hlt();
    }
}

  下面介绍了键盘中断函数的具体实现的汇编代码;

  笔者在这里介绍了堆栈的一些知识,这段汇编代码就比较简单了:

_asm_inthandler21:                      
        PUSH    ES             ;关键寄存器压栈,函数必做
        PUSH    DS
        PUSHAD
        MOV        EAX,ESP
        PUSH    EAX
        MOV        AX,SS       ;将DS和ES调整到与SS相等
        MOV        DS,AX
        MOV        ES,AX
        CALL    _inthandler21  ;调用函数_asm_inthandler21
        POP        EAX         ;返回后,将寄存器回复原来
        POPAD
        POP        DS
        POP        ES
        IRETD                  ;最后执行IRETD

  我们已经编写了键盘的响应函void inthandler21(int *esp);也理解了该函数的汇编具体在内存中怎么做的_asm_inthandler21: 

  接下来就要把键盘中断号写到中断表中,这样当执行INT时,就可以调用相应的中断程序了(在这里是函数void inthandler21(int *esp))

    /* 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);

  差不多了,make run 以后,只要敲了键盘,就会相应0x21中断,接着CPU会在中断表中找到这个中断程序(在这里是函数void inthandler21(int *esp))的入口了。于是就会执行这个中断程序,而这个程序的内容就是输出字符串

    INT 21 (IRQ-1) : PS/2 keyboard
原文地址:https://www.cnblogs.com/pengfeiz/p/5785263.html