GDT即Global Descriptor Table(全局描述符表),之所以要用到该表,是和IA-32平台的内存管理机制密切相关的...

    v0.0.3的项目地址:

    github.com地址:https://github.com/zenglong/zenglOX (只包含git提交上来的源代码)
   
    Dropbox地址:点此进入Dropbox网盘  (该版本位于zenglOX_v0.0.3的文件夹,该文件夹里的zip压缩包为源代码)

    sourceforge地址:https://sourceforge.net/projects/zenglox/files  (该版本位于zenglOX_v0.0.3的文件夹,该文件夹里的zip压缩包为源代码)

    另外再附加一个英特尔英文手册的共享链接地址:
    http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (里面有GDT和IDT的详细介绍,下面提到的一些页数可以在该PDF电子档顶部,分页输入框里直接输入查看)

    源代码编译调试方法请参考 v0.0.1的文章。

    GDT即Global Descriptor Table(全局描述符表),之所以要用到该表,是和IA-32平台的内存管理机制密切相关的,在上面英特尔英文手册的1922页有内存管理机制的详细介绍。

    IA-32平台的内存管理机制分为两部分:段和分页,如下图所示(该图位于英特尔手册的第1923页):


图1

    上图里的Logical Address是逻辑地址,例如ljmp $0x08,$offset长跳转指令里的$0x08,$offset部分就是逻辑地址,其中第一个操作数0x08就是图中的Segment Selector即段选择符,它用于表示一段内存,offset则是该段内存里的偏移值,Segment Selector的结构如下图所示(该图位于英特尔手册的第1928页):
 

图2

    位0到位1表示RPL请求的权限级别,位2为0时表示该Segment Selector段选择符里的Index部分是GDT表里的索引,否则就是LDT表里的索引,位3到位15表示GDT或LDT表里的索引值,上例中0x08的段选择符对应二进制格式为001000,低三位都是0,说明RPL是0级别的权限,对应的表为GDT,在GDT里的索引值为1(GDT里的索引值是从0开始),有关RPL权限级别方面的内容可以参考英特尔英文手册的第1990页。

    根据Segment Selector段选择符里的Index索引值就可以找到GDT全局描述符表里对应的描述符项目,在该描述符项目里存放有该段的线性基址,还有该段的范围信息,由该段的基址加上offset偏移值就可以得到逻辑地址对应的线性地址了,在没有设置分页机制的情况下,例如v0.0.3还没有设置Paging分页机制,所以该版本里的线性地址就等于物理地址。

    可以看到Segment Selector段选择符定位段信息时需要用到GDT表,下面就介绍下GDT表的结构,有关GDT表的完整介绍请参考英特尔英文手册的第1930页。

    GDT表是由Segment Descriptor即段描述符组成,操作系统内核可以在内存里根据段描述符的结构设置一个数组,例如在zlox_descriptor_tables.c的开头有如下定义:

ZLOX_GDT_ENTRY gdt_entries[5];

    上面定义了一个gdt_entries的数组,该数组就是一个GDT表,该数组里的每一项都是一个ZLOX_GDT_ENTRY的结构体,该结构体就是GDT表里的Segment Descriptor段描述符,段描述符的结构如下图所示:


图3

    上图位于英特尔手册的1931页,里面有所有字段的详细介绍,根据上图的结构,ZLOX_GDT_ENTRY结构体的定义如下(位于zlox_descriptor_tables.h头文件):

// This structure contains the value of one GDT entry.
// We use the attribute 'packed' to tell GCC not to change
// any of the alignment in the structure.

struct _ZLOX_GDT_ENTRY
{
    ZLOX_UINT16 limit_low;    // The lower 16 bits of the limit.
    ZLOX_UINT16 base_low;    // The lower 16 bits of the base.
    ZLOX_UINT8 base_middle;    // The next 8 bits of the base.
    ZLOX_UINT8 access;        // Access flags, determine what ring this segment can be used in.
    ZLOX_UINT8 granularity;
    ZLOX_UINT8 base_high;    // The last 8 bits of the base.
}__attribute__((packed));

    有了段描述符的结构定义,我们就可以通过zlox_gdt_set_gate函数(位于zlox_descriptor_tables.c文件)来设置段描述符里的各个字段:

// Set the value of one GDT entry.
static ZLOX_VOID zlox_gdt_set_gate(ZLOX_SINT32 num, ZLOX_UINT32 base, ZLOX_UINT32 limit,
                                    ZLOX_UINT8 access, ZLOX_UINT8 gran)
{
    gdt_entries[num].base_low    = (base & 0xFFFF);
    gdt_entries[num].base_middle = (base >> 16) & 0xFF;
    gdt_entries[num].base_high   = (base >> 24) & 0xFF;

    gdt_entries[num].limit_low   = (limit & 0xFFFF);
    gdt_entries[num].granularity = (limit >> 16) & 0x0F;
    
    gdt_entries[num].granularity |= gran & 0xF0;
    gdt_entries[num].access      = access;
}

    上面的zlox_gdt_set_gate函数里,第一个参数num表示要设置的GDT里段描述符的索引值,base参数表示该段在内存里的起始地址,limit参数表示该段的有效上限,access参数表示段描述符第二个4字节里位8到位15的值,gran参数表示段描述符第二个4字节里位16到位23的值。

    有了zlox_gdt_set_gate段描述符的设置函数,就可以在zlox_init_gdt函数里对GDT里的5个段描述符进行初始化:

// Initialises the GDT (global descriptor table)
static ZLOX_VOID zlox_init_gdt()
{
    gdt_ptr.limit = (sizeof(ZLOX_GDT_ENTRY) * 5) - 1;
    gdt_ptr.base  = (ZLOX_UINT32)gdt_entries;
    
    zlox_gdt_set_gate(0, 0, 0, 0, 0);                    // Null segment
    zlox_gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);    // Code segment
    zlox_gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);    // Data segment
    zlox_gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);    // User mode code segment
    zlox_gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);    // User mode data segment

    _zlox_gdt_flush((ZLOX_UINT32)&gdt_ptr);
}

    上面代码用到了一个gdt_ptr的全局变量,该变量的定义如下(位于zlox_descriptor_tables.c文件的开头):

ZLOX_GDT_PTR gdt_ptr;

    ZLOX_GDT_PTR是一个结构体,该结构体的定义如下(位于zlox_descriptor_tables.h头文件):

// This struct describes a GDT pointer. It points to the start of
// our array of GDT entries, and is in the format required by the
// lgdt instruction.

struct _ZLOX_GDT_PTR
{
    ZLOX_UINT16 limit;        // The upper 16 bits of all selector limits.
    ZLOX_UINT32 base;        // The address of the first ZLOX_GDT_ENTRY struct.
}__attribute__((packed));

    要了解该结构体的含义,首先要了解一个GDTR寄存器,它是一个48位的寄存器(32位模式下),通过特殊的汇编指令,我们可以将ZLOX_GDT_PTR结构体的内容加载到该寄存器,该寄存器里就包含了GDT的内存基址和GDT的limit尺寸信息,GDTR寄存器与GDT的关系如下图所示:


图4

    上图来自英特尔英文手册的第1936页,该图显示了,当Segment Selector段选择符里的TI标志位为0时,表示从GDT里进行索引,然后处理器就会根据GDTR寄存器里的Base Address基址信息找到GDT表的第一个段描述符的起始位置,另外,处理器是根据GDTR里的limit值加上Base Address的值来确定GDT里最后一个有效字节的内存位置的,所以limit的值等于GDT表的总字节数减一。所以在前面的zlox_init_gdt函数里,开头就将gdt_ptr.limit设为(sizeof(ZLOX_GDT_ENTRY) * 5) - 1即GDT总字节数减一,同时将gdt_ptr.base字段设为gdt_entries即GDT的内存基址。

    在设置了gdt_ptr.base字段后,接下来的zlox_gdt_set_gate(0, 0, 0, 0, 0)函数将GDT的第一项设为0,因为GDT的第一项是不被处理器使用的,所以这个段描述符也叫"null descriptor"(空描述符),当空描述符被加载到DS,ES之类的数据段寄存器时,并不会产生异常,只有当使用该含有空描述符的数据段寄存器访问内存时才会产生异常,所以可以使用空描述符来初始化那些不被使用的段寄存器,这样当无意中访问到这些段寄存器时,就可以抛出异常。

    再接下来的zlox_gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF)函数用于将GDT的第二项设为内核代码段,第二个参数为0,表示该代码段的基址为0,第三个参数为0xFFFFFFFF,表示将段描述符的limit的20位都设为1,至于limit具体能表示的范围,还需要根据描述符里第2个4字节里的位23即G字段来判断,G是granularity粒度的缩写,当G所在二进制位被设置为0时,则limit的基本单位就是一个字节,limit能表示的范围为0到0xFFFFF即220-1也就是1M的段地址空间,当G所在二进制位被设置为1时,则limit的基本单位就是4K字节,即当limit为0时,limit能表示的范围为0到4095(212-1),当limit为1时,limit能表示的范围为0到8191(213-1),当limit的20位全被1填充时,能表示的范围为0到4G - 1,由于本例zlox_gdt_set_gate函数的最后一个参数0xCF的最高位为1,所以G为1,所以本例的代码段和数据段能表示的范围都是0到4G - 1 。

    当zlox_gdt_set_gate函数的第三个参数为0x9A时,低4位为段描述符的Type字段,A对应的二进制值为1010,该二进制对应的Type类型需要在下表中查询:

图5

    上表位于英特尔手册的第1933页,从上表可以看出,1010对应的类型为可读和可执行的Code代码段。0x9A里的高4位9对应的二进制为1001,所以该段描述符的P字段为1说明该段存在,中间两个00表示DPL字段为0,即该段描述符对应的权限级别为0,属于内核代码段,有关DPL,RPL,CPL这些和权限级别有关的内容请参考英文手册的第1990页,1001的最后一个二进制1是该段描述符的S字段,表示该段描述符是代码或数据段。

    有关段描述符其他字段的含义请参考英特尔手册的第1931页。

    zlox_gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);函数用于将GDT的第三项设为内核数据段,因为0x92的低4位对应二进制为0010,从图5的表格里可以看到该段是一个可读可写的Data数据段。

    zlox_gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);函数用于将GDT的第四项设为用户态的代码段,因为0xFA里的高4位的中间两位为11,即DPL字段为11对应十进制值3,所以该段描述符的权限级别为3,属于用户程序的权限级别。

    zlox_gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);函数则用于将GDT的第五项设为用户态的数据段。

    上面设置的代码段和数据段都是以0为基址,段的内存范围都为0到4G - 1,这种分段模式属于Basic Flat Model基本平坦模型,详情参考英特尔手册的第1924页。

    在zlox_init_gdt初始化GDT函数的最后,通过_zlox_gdt_flush汇编函数将GDT的基址等信息设置到GDTR寄存器,同时为DS,ES,CS之类的段寄存器设置对应的段选择符(_zlox_gdt_flush汇编函数位于zlox_gdt.s汇编文件里):

.global _zlox_gdt_flush    # Allows the C code to call _zlox_gdt_flush().
_zlox_gdt_flush:
    movl 4(%esp),%eax    # Get the pointer to the GDT, passed as a parameter.
    lgdt (%eax)            # Load the new GDT pointer
    
    movw $0x10,%ax        # 0x10 is the offset in the GDT to our data segment
    movw %ax,%ds        # Load all data segment selectors
    movw %ax,%es
    movw %ax,%fs
    movw %ax,%gs
    movw %ax,%ss
    ljmp $0x08,$_gdt_ljmp_flush    # 0x08 is the offset to our code segment: Far jump!
_gdt_ljmp_flush:
    ret

    第一条代码通过movl 4(%esp),%eax将第一个参数里的ZLOX_GDT_PTR结构体指针赋值给EAX寄存器,这样,lgdt (%eax)指令就可以根据指针将ZLOX_GDT_PTR结构体的内容设置到GDTR寄存器。从而完成GDT的加载工作。

    0x10为内核数据段的选择符,通过AX寄存器和MOV指令将DS,ES,FS,GS,SS都设置为相同的数据段。

    修改CS的代码段选择符的方式和DS数据段寄存器的方式不一样,我们必须使用ljmp指令做长跳转,才能将0x08的段选择符隐式的设置到CS里。

    以上就是GDT的相关代码,下面再看下IDT的内容。

    IDT就是Interrupt Descriptor Table中断描述符表,要了解IDT中断描述符表,首先要了解中断和异常。

    有关中断和异常的完整详细的介绍请参考英特尔英文手册的第2018页,即第三卷的第6章,下面只做些简单的介绍。

    中断就是一种信号,当处理器收到这类信号时,就会将当前的执行过程挂起,同时转向对应的中断处理程序去处理收到的信号,当信号处理完毕时,就可以通过特殊的指令返回到挂起的程序继续执行,典型的例子就是计时器发出的信号和键盘按下时发出的信号等,这些计时器,键盘之类的都属于外部硬件产生的中断,还有一种中断是通过Int汇编指令以软件的方式产生的中断。

    除了中断会让处理器将执行流程挂起,转向对应的处理程序外,异常也可以产生相同的效果,例如DIV指令执行时如果发生除零的异常的话,也会中断当前的执行流程,转向对应的处理程式,在该处理程式里对除零之类的异常进行处理。

    为了让处理器在收到中断或异常时,能调用我们自己定义的处理程序,就需要用到IDT中断描述符表,IDT和GDT类似,IDT里的每一项也有8个字节,IDT里可以容纳如下三种描述符:
  • Task-gate descriptor 任务门描述符
  • Interrupt-gate descriptor 中断门描述符
  • Trap-gate descriptor 陷阱门描述符
    v0.0.3版本里只用到了Interrupt-gate descriptor中断门描述符,下面是中断门描述符的结构图:

图6

    上图位于英特尔手册的第2028页。中断门描述符里的Segment Selector为:当发生中断时,对应的中断处理例程所在段的段选择符。Offset为该段里的中段处理例程的入口位置的内存偏移值。这样当发生中断时,就可以根据Segment Selector和Offset定位到对应的处理程式了。

    另外,中断描述符里第2个4字节的位11即D字段表示对应的中断处理例程是32位的操作数尺寸还是16位的,DPL为该中断描述符的权限级别,P字段用于判断该段是否存在。

    由上图的结构,我们就可以在zlox_descriptor_tables.h头文件里写出中断描述符的结构体定义:

struct _ZLOX_IDT_ENTRY
{
    ZLOX_UINT16 base_lo;    // The lower 16 bits of the address to jump to when this interrupt fires.
    ZLOX_UINT16 sel;        // Kernel segment selector.
    ZLOX_UINT8 always0;        // This must always be zero.
    ZLOX_UINT8 flags;        // More flags.
    ZLOX_UINT16 base_hi;    // The upper 16 bits of the address to jump to.
}__attribute__((packed));

typedef struct _ZLOX_IDT_ENTRY ZLOX_IDT_ENTRY;

    有了ZLOX_IDT_ENTRY结构体,就可以像定义GDT那样,定义出IDT(位于zlox_descriptor_tables.c文件的开头):

ZLOX_IDT_ENTRY idt_entries[256];

    上面的idt_entries数组就是IDT,该数组有256个元素,每个元素都是ZLOX_IDT_ENTRY的类型。

    从英特尔手册的第2018页有关中断和异常的介绍里,可以看到,为了能有效的处理这些中断和异常,每个需要处理的中断和异常都被分配了一个唯一的标识号,这个标识号被称作vector number即中断向量号,处理器在收到一个中断或异常时,就会使用它们的中断向量号作为IDT里的索引值,找到对应的中断门描述符,然后就可以根据中断门描述符里的段选择符和偏移值定位到对应的处理程式了,由于vector number中断向量号的允许的有效范围是0到255,所以上面的idt_entries数组的大小就定义成256 。

    此外,中断向量号里0号到31号是Intel 64和IA-32平台里预定义的异常和中断,32号到255号则是用户自定义的中断,这些用户自定义的中断多用于外部I/O设备产生的中断。

    当前v0.0.3版本里主要是对0号到31号平台预定义的中断和异常进行了设置,32号到255号的中断在该版本里还没有被设置。

    下表显示了一部分中断向量号对应的中断和异常信息(完整的表格请查看英特尔手册的第2019和2020页):

图7

    所以我们只需要将IDT里的中断门描述符通过函数设置好即可,zlox_descriptor_tables.c里的zlox_idt_set_gate函数就是用于设置中断门描述符的,该函数的定义如下:

static ZLOX_VOID zlox_idt_set_gate(ZLOX_UINT8 num, ZLOX_UINT32 base, ZLOX_UINT16 sel,
                                    ZLOX_UINT8 flags)
{
    idt_entries[num].base_lo = base & 0xFFFF;
    idt_entries[num].base_hi = (base >> 16) & 0xFFFF;

    idt_entries[num].sel     = sel;
    idt_entries[num].always0 = 0;
    // We must uncomment the OR below when we get to using user-mode.
    // It sets the interrupt gate's privilege level to 3.

    idt_entries[num].flags   = flags /* | 0x60 */;
}

    上面函数的第一个参数num为IDT里的索引值(也可以看作是中断或异常的向量号),第二个参数base为该中断门描述符里的基址,第三个参数sel为描述符里的段选择符,flags参数为DPL权限级别之类的标志位的值。

    有了zlox_idt_set_gate函数,我们就可以在zlox_init_idt初始化IDT的函数里,对所需处理的中断和异常进行设置,zlox_init_idt函数的定义如下(该函数也位于zlox_descriptor_tables.c文件里):

// Initialise the interrupt descriptor table.
static ZLOX_VOID zlox_init_idt()
{
    idt_ptr.limit = sizeof(ZLOX_IDT_ENTRY) * 256 - 1;
    idt_ptr.base  = (ZLOX_UINT32)idt_entries;

    zlox_memset((ZLOX_UINT8 *)idt_entries, 0, sizeof(ZLOX_IDT_ENTRY)*256);
    
    zlox_idt_set_gate( 0, (ZLOX_UINT32)_zlox_isr_0 , 0x08, 0x8E);
    zlox_idt_set_gate( 1, (ZLOX_UINT32)_zlox_isr_1 , 0x08, 0x8E);
    zlox_idt_set_gate( 2, (ZLOX_UINT32)_zlox_isr_2 , 0x08, 0x8E);
    zlox_idt_set_gate( 3, (ZLOX_UINT32)_zlox_isr_3 , 0x08, 0x8E);
    zlox_idt_set_gate( 4, (ZLOX_UINT32)_zlox_isr_4 , 0x08, 0x8E);
    zlox_idt_set_gate( 5, (ZLOX_UINT32)_zlox_isr_5 , 0x08, 0x8E);
    zlox_idt_set_gate( 6, (ZLOX_UINT32)_zlox_isr_6 , 0x08, 0x8E);
    zlox_idt_set_gate( 7, (ZLOX_UINT32)_zlox_isr_7 , 0x08, 0x8E);
    zlox_idt_set_gate( 8, (ZLOX_UINT32)_zlox_isr_8 , 0x08, 0x8E);
    zlox_idt_set_gate( 9, (ZLOX_UINT32)_zlox_isr_9 , 0x08, 0x8E);
    zlox_idt_set_gate(10, (ZLOX_UINT32)_zlox_isr_10, 0x08, 0x8E);
    zlox_idt_set_gate(11, (ZLOX_UINT32)_zlox_isr_11, 0x08, 0x8E);
    zlox_idt_set_gate(12, (ZLOX_UINT32)_zlox_isr_12, 0x08, 0x8E);
    zlox_idt_set_gate(13, (ZLOX_UINT32)_zlox_isr_13, 0x08, 0x8E);
    zlox_idt_set_gate(14, (ZLOX_UINT32)_zlox_isr_14, 0x08, 0x8E);
    zlox_idt_set_gate(15, (ZLOX_UINT32)_zlox_isr_15, 0x08, 0x8E);
    zlox_idt_set_gate(16, (ZLOX_UINT32)_zlox_isr_16, 0x08, 0x8E);
    zlox_idt_set_gate(17, (ZLOX_UINT32)_zlox_isr_17, 0x08, 0x8E);
    zlox_idt_set_gate(18, (ZLOX_UINT32)_zlox_isr_18, 0x08, 0x8E);
    zlox_idt_set_gate(19, (ZLOX_UINT32)_zlox_isr_19, 0x08, 0x8E);
    zlox_idt_set_gate(20, (ZLOX_UINT32)_zlox_isr_20, 0x08, 0x8E);
    zlox_idt_set_gate(21, (ZLOX_UINT32)_zlox_isr_21, 0x08, 0x8E);
    zlox_idt_set_gate(22, (ZLOX_UINT32)_zlox_isr_22, 0x08, 0x8E);
    zlox_idt_set_gate(23, (ZLOX_UINT32)_zlox_isr_23, 0x08, 0x8E);
    zlox_idt_set_gate(24, (ZLOX_UINT32)_zlox_isr_24, 0x08, 0x8E);
    zlox_idt_set_gate(25, (ZLOX_UINT32)_zlox_isr_25, 0x08, 0x8E);
    zlox_idt_set_gate(26, (ZLOX_UINT32)_zlox_isr_26, 0x08, 0x8E);
    zlox_idt_set_gate(27, (ZLOX_UINT32)_zlox_isr_27, 0x08, 0x8E);
    zlox_idt_set_gate(28, (ZLOX_UINT32)_zlox_isr_28, 0x08, 0x8E);
    zlox_idt_set_gate(29, (ZLOX_UINT32)_zlox_isr_29, 0x08, 0x8E);
    zlox_idt_set_gate(30, (ZLOX_UINT32)_zlox_isr_30, 0x08, 0x8E);
    zlox_idt_set_gate(31, (ZLOX_UINT32)_zlox_isr_31, 0x08, 0x8E);

    _zlox_idt_flush((ZLOX_UINT32)&idt_ptr);
}

    上面的zlox_init_idt函数在开头也碰到一个和gdt_ptr类似的变量:idt_ptr全局变量,该变量的定义如下:

ZLOX_IDT_PTR idt_ptr;

    ZLOX_IDT_PTR是IDTR寄存器对应的结构体定义,IDTR与IDT的关系类似于之前介绍的GDTR与GDT的关系,如下图所示:


图8

    上图位于英特尔手册的第2027页,IDTR也是一个48位的寄存器(32位模式下),IDTR里包含IDT的基址和IDT的limit尺寸上限信息。

    根据IDTR的结构图,ZLOX_IDT_PTR的结构体定义如下(位于zlox_descriptor_tables.h头文件):

struct _ZLOX_IDT_PTR
{
    ZLOX_UINT16 limit;
    ZLOX_UINT32 base;        // The address of the first element in our ZLOX_IDT_ENTRY array.
}__attribute__((packed));

typedef struct _ZLOX_IDT_PTR ZLOX_IDT_PTR;

    idt_ptr.limit和idt_ptr.base的设置过程和gdt_ptr的设置过程类似。

    接着,先用zlox_memset函数将idt_entries即IDT里的所有中断门描述符清零,再用zlox_idt_set_gate函数对需要处理的中断和异常设置对应的处理例程。

    这里的_zlox_isr_0到_zlox_isr_31是zlox_interrupt.s文件里定义的汇编函数。

    由于_zlox_isr_0到_zlox_isr_31函数比较多,一个个写太麻烦,目前版本里的中断处理过程都是一样的,所以可以用宏来批量定义:

# This macro creates a stub for an ISR which does NOT pass it's own
# error code (adds a dummy errcode byte).

.macro _ZLOX_INT_ISR_NOERRCODE argNum
.global _zlox_isr_\argNum
_zlox_isr_\argNum:
    cli                # Disable interrupts firstly.
    pushl $0        # Push a dummy error code.
    pushl $\argNum    # Push the interrupt number.
    jmp _zlox_isr_common_stub
.endm

# This macro creates a stub for an ISR which passes it's own error code.
.macro _ZLOX_INT_ISR_ERRCODE argNum
.global _zlox_isr_\argNum
_zlox_isr_\argNum:
    cli                # Disable interrupts firstly.
    pushl $\argNum    # Push the interrupt number.
    jmp _zlox_isr_common_stub
.endm

_ZLOX_INT_ISR_NOERRCODE    0
_ZLOX_INT_ISR_NOERRCODE    1
_ZLOX_INT_ISR_NOERRCODE    2
_ZLOX_INT_ISR_NOERRCODE    3
_ZLOX_INT_ISR_NOERRCODE    4
_ZLOX_INT_ISR_NOERRCODE    5
_ZLOX_INT_ISR_NOERRCODE    6
_ZLOX_INT_ISR_NOERRCODE    7
.............................. //省略N行代码
.............................. //省略N行代码

_ZLOX_INT_ISR_NOERRCODE    31

_zlox_isr_common_stub:
    pusha            # Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax

    movw %ds,%ax    # Lower 16-bits of eax = ds.
    pushl %eax        # save the data segment descriptor

    movw $0x10,%ax    # load the kernel data segment descriptor
    movw %ax,%ds
    movw %ax,%es
    movw %ax,%fs
    movw %ax,%gs

    call zlox_isr_handler    # in zlox_isr.c

    pop %ebx        # reload the original data segment descriptor
    movw %bx,%ds
    movw %bx,%es
    movw %bx,%fs
    movw %bx,%gs

    popa            # Pops edi,esi,ebp...
    addl $8,%esp    # Cleans up the pushed error code and pushed ISR number
    sti
    iret            # pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP

    有关GNU汇编里.macro宏定义的语法,可以参考 http://tigcc.ticalc.org/doc/gnuasm.html#SEC109 里的语法说明。

    上面_ZLOX_INT_ISR_NOERRCODE宏用于定义没有Error Code的处理例程,_ZLOX_INT_ISR_ERRCODE宏用于定义有Error Code的处理例程。这些处理例程最终都会jmp _zlox_isr_common_stub跳转到_zlox_isr_common_stub标签处,最后通过call zlox_isr_handler指令进入zlox_isr.c文件的zlox_isr_handler函数里:

// This gets called from our ASM interrupt handler stub.
ZLOX_VOID zlox_isr_handler(ZLOX_ISR_REGISTERS regs)
{
    zlox_monitor_write("zenglox recieved interrupt: ");
    zlox_monitor_write_dec(regs.int_no);
    zlox_monitor_put('\n');
}

    上面的zlox_isr_handler函数会将中断或异常的向量号通过zlox_monitor_write_dec函数显示输出到屏幕上。

    该版本在bochs或virtualBox里的运行情况如下:

图9

    OK,就到这里,休息,休息一下 o(∩_∩)o~~
 
上下篇

下一篇: zenglOX v0.0.4 IRQ(中断请求)与PIT(可编程间隔定时器)

上一篇: zenglOX v0.0.2 VGA输出显示字符串

相关文章

zenglOX v0.0.9 User Mode(用户模式)

zenglOX v3.2.0 USB v1.1, UHCI, USB KeyBoard, USB Mouse

zenglOX v1.2.0 ISO 9660文件系统

zenglOX v3.0.0与v3.0.1 GUI窗口界面

zenglOX v3.0.2 PNG(Portable Network Graphics) and BUG Fix

zenglOX v1.4.0与v1.4.1 通过ATA驱动读写硬盘里的数据, BUG修复, VirtualBox与VMware的调试功能