babyos2(6)——IDT,interrupt,exception

babyos2内核开始执行后,会重新设置GDT到一个更安全的位置。同时,为了能处理中断和异常,需要设置IDT。
IDT也是一个表,叫中断描述符表。每个项表示一个中断描述符。
这里写图片描述
Task Gate,Intel是想用于任务调度的,在此先不描述。
中断门和陷阱门的区别在于通过中断门进入中断服务程序时CPU自动关中断,即将CPU的EFLAGS中IF位清0,防止中断嵌套。而通过陷阱门进入则IF标志位不变。
这里写图片描述

IDTR和IDT关系如上图所示。
每个IDT对应了一个中断服务程序。

void cpu_t::init_gdt()
{
    m_gdt[SEG_NULL]  = 0x0000000000000000ULL;
    m_gdt[SEG_KCODE] = 0x00cf9a000000ffffULL;
    m_gdt[SEG_KDATA] = 0x00cf92000000ffffULL;
    m_gdt[SEG_UCODE] = 0x00cffa000000ffffULL;
    m_gdt[SEG_UDATA] = 0x00cff2000000ffffULL;

    lgdt(m_gdt, sizeof(uint64) * (GDT_LEN));
}

void cpu_t::init_idt()
{
    init_isrs();
    lidt(m_idt, sizeof(m_idt));

    /*
     * Delete NT
     */
    __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
}

这两个函数用于设置GDT和IDT。


void cpu_t::set_gate(uint32 index, uint32 addr, uint64 flag)
{
    uint64 idt_item;

    idt_item = flag;                                            /* gate type */
    idt_item |= (uint64)((SEG_KCODE << 3) << 16);               /* kernel code segment selector */
    idt_item |= ((uint64)addr << 32) & 0xffff000000000000ULL;   /* high 16 bits of address */
    idt_item |= ((uint64)addr) & 0xffff;                        /* low 16 bits of address */

    m_idt[index] = idt_item;
}

/* set idt by trap gate */
void cpu_t::set_trap_gate(uint32 index, uint32 addr)
{
    set_gate(index, addr, TRAP_GATE_FLAG);
}

/* set idt by interrupt gate */
void cpu_t::set_intr_gate(uint32 index, uint32 addr)
{
    set_gate(index, addr, INTERRUPT_GATE_FLAG);
}

void cpu_t::set_system_gate(uint32 index, uint32 addr)
{
    set_gate(index, addr, SYSTEM_GATE_FLAG);
}

void cpu_t::init_isrs()
{
    // exceptions
    for (uint32 i = 0; i < IRQ_0; i++) {
        set_trap_gate(i, (uint32)isr_vector[i]);
    }

    // interrupts
    for (uint32 i = IRQ_0; i < 256; i++) {
        set_intr_gate(i, (uint32)isr_vector[i]);
    }

    // syscall
    set_system_gate(IRQ_SYSCALL, (uint32)isr_vector[IRQ_SYSCALL]);
}

这里写图片描述
这里写图片描述

保护模式下的中断和异常如上图所示。
其中0~31为Intel定义的异常,babyos2使用陷阱门。32~255为用户自定义中断,babyos2使用中断门。然后babyos2使用0x80作为系统调用,单独做了处理,这里的system_gate实际是个陷阱门,只是DPL为3,即系统调用过程中是允许中断的。
如上面代码所示,每个IDT设置的中断服务程序指向了isr_vector[i],这个表定义在:

isr_vector:
.long   isr_0x00, isr_0x01, isr_0x02, isr_0x03
.long   isr_0x04, isr_0x05, isr_0x06, isr_0x07
.long   isr_0x08, isr_0x09, isr_0x0a, isr_0x0b
.long   isr_0x0c, isr_0x0d, isr_0x0e, isr_0x0f

.long   isr_0x10, isr_0x11, isr_0x12, isr_0x13
.long   isr_0x14, isr_0x15, isr_0x16, isr_0x17
.long   isr_0x18, isr_0x19, isr_0x1a, isr_0x1b
.long   isr_0x1c, isr_0x1d, isr_0x1e, isr_0x1f

...

而isr_0x00, isr_0x01..等的定义由一个宏实现:

#define MAKE_ISR(index) \
isr_##index:                \
    pushl   $0x00;         \
    pushl   $index;            \
    jmp     common_isr

#define MAKE_ISR_WITH_ERROR(index) \
isr_##index:                \
    pushl   $index;            \
    jmp     common_isr

MAKE_ISR(0x00)
MAKE_ISR(0x01)
MAKE_ISR(0x02)
MAKE_ISR(0x03)
MAKE_ISR(0x04)
MAKE_ISR(0x05)
MAKE_ISR(0x06)
MAKE_ISR(0x07)
MAKE_ISR_WITH_ERROR(0x08)
MAKE_ISR(0x09)
MAKE_ISR_WITH_ERROR(0x0a)
MAKE_ISR_WITH_ERROR(0x0b)
MAKE_ISR_WITH_ERROR(0x0c)
MAKE_ISR_WITH_ERROR(0x0d)
MAKE_ISR_WITH_ERROR(0x0e)
MAKE_ISR(0x0f)
MAKE_ISR_WITH_ERROR(0x10)
MAKE_ISR(0x11)
MAKE_ISR(0x12)
MAKE_ISR(0x13)
MAKE_ISR(0x14)
MAKE_ISR(0x15)
MAKE_ISR(0x16)
MAKE_ISR(0x17)
MAKE_ISR(0x18)
MAKE_ISR(0x19)
MAKE_ISR(0x1a)
MAKE_ISR(0x1b)
MAKE_ISR(0x1c)
MAKE_ISR(0x1d)
MAKE_ISR(0x1e)
MAKE_ISR(0x1f)
MAKE_ISR(0x20)
...

babyos2定义了两个宏用于产生isr_0x??,分别用于处理有错误码和无错误码两种情况。对于有错误码的中断或异常,发送中断时自动将错误码压栈,而没有错误码的,babyos2压入0作为错误码,以使各种中断栈中内容统一。然后统一调用common_isr做处理。

/*
 * guzhoudiaoke@126.com
 * 2017-10-27
 */

#include "kernel.h"

# ----------------------------------------------------------------------------------------------------------------------
# 向量号  助记符 说明                        类型        错误号    产生源
# --------------------------------------------------------------------------------------------------------------------
# 0       #DE    除出错                      故障        无        DIV或IDIV指令
# 1       #DB    调试                        故障/陷阱   无        任何代码或数据引用,或是INT 1指令
# 2        --    NMI中断                     中断        无        非屏蔽外部中断
# 3       #BP    断点                        陷阱        无        INT 3指令
# 4       #OF    溢出                        陷阱        无        INTO指令
# 5       #BR    边界范围超出                故障        无        BOUND指令
# 6       #UD    无效操作码(未定义操作码)  故障        无        UD2指令或保留的操作码。(Pentium Pro中加入的新指令)
# 7       #NM    设备不存在(无数学协处理器)故障        无        浮点或WAIT/FWAIT指令
# 8       #DF    双重错误                    异常终止    有(0)   任何可产生异常、NMI或INTR的指令
# 9        --    协处理器段超越(保留)      故障        无        浮点指令(386以后的CPU不产生该异常)
# 10      #TS    无效的任务状态段TSS         故障        有        任务交换或访问TSS
# 11      #NP    段不存在                    故障        有        加载段寄存器或访问系统段
# 12      #SS    堆栈段错误                  故障        有        堆栈操作和SS寄存器加载
# 13      #GP    一般保护错误                故障        有        任何内存引用和其他保护检查
# 14      #PF    页面错误                    故障        有        任何内存引用
# 15       --    (Intel保留,请勿使用)                 无 
# 16      #MF    x87 FPU浮点错误(数学错误) 故障        无        x87 FPU浮点或WAIT/FWAIT指令
# 17      #AC    对齐检查                    故障        有(0)   对内存中任何数据的引用
# 18      #MC    机器检查                    异常终止    无        错误码(若有)和产生源与CPU类型有关(奔腾处理器引进)
# 19      #XF    SIMD浮点异常                故障        无        SSE和SSE2浮点指令(PIII处理器引进)
# 20-31    --    (Intel保留,请勿使用)
# 32-255   --    用户定义(非保留)中断      中断                  外部中断或者INT n指令
# ---------------------------------------------------------------------------------------------------------------------

.global isr_vector
.global ret_from_isr


#define SAVE_ALL \
    cld; \
    pushl %es; \
    pushl %ds; \
    push  %fs; \
    push  %gs; \
    pushl %eax; \
    pushl %ebp; \
    pushl %edi; \
    pushl %esi; \
    pushl %edx; \
    pushl %ecx; \   
    pushl %ebx; \

#define RESTORE_ALL \
    popl %ebx;  \
    popl %ecx;  \
    popl %edx;  \
    popl %esi;  \
    popl %edi;  \
    popl %ebp;  \
    popl %eax;  \
    popl %gs;   \
    popl %fs;   \
    popl %ds;   \
    popl %es;   \

#define GET_CURRENT(reg) \
    movl $-8192, reg; \
    andl %esp, reg

need_resched = 0

reschedule:
    call schedule
    jmp  restore_all

common_isr:
    SAVE_ALL
    GET_CURRENT(%ebx)
    movl    $(SEG_KDATA<<3),%edx
    movw    %dx,            %ds
    movw    %dx,            %es
    movw    %dx,            %fs
    movw    %dx,            %gs

    #movl    48(%esp),       %eax
    pushl   %esp
    call    do_common_isr
    addl    $4,             %esp
ret_from_isr:
    cli
    cmpl    $0,                need_resched(%ebx)
    jne     reschedule
restore_all:
    RESTORE_ALL
    addl    $8,             %esp
    iret

common_isr首先通过SAVE_ALL将所有寄存器压栈,之后设置ds,es..为内核数据段,然后将esp压栈,方便获取前面压栈的所有寄存器值(esp即do_common_isr的参数)。

/*
 * guzhoudiaoke@126.com
 * 2017-11-7
 */

#include "cpu.h"
#include "babyos.h"
#include "i8259a.h"
#include "x86.h"
#include "string.h"

extern uint32 isr_vector[];
extern uint8  kernel_stack[];
extern void ret_from_isr(void) __asm__("ret_from_isr");

static const char* exception_msg[] = {
    "int0  #DE divide error",
    "int1  #DB debug",
    "int2  --  NMI",
    "int3  #BP break point",
    "int4  #OF overflow",
    "int5  #BR bounds check",
    "int6  #UD invalid opcode",
    "int7  #NM device not available",
    "int8  #DF double fault",
    "int9  --  coprocessor seg overrun",
    "int10 #TS invalid TSS",
    "int11 #NP segment not present",
    "int12 #SS stack segment",
    "int13 #GP general protection",
    "int14 #PF page fault",
    "int15 -- (Intel reserved)",
    "int16 #MF x87 FPU coprocessor error",
    "int17 #AC align check",
};

extern "C" void do_common_isr(trap_frame_t* frame)
{
    os()->get_arch()->get_cpu()->do_common_isr(frame);
}

typedef struct trap_frame_s {
    uint32 ebx;
    uint32 ecx;
    uint32 edx;
    uint32 esi;
    uint32 edi;
    uint32 ebp;
    uint32 eax;

    uint16 gs, padding4;
    uint16 fs, padding3;
    uint16 ds, padding1;
    uint16 es, padding2;

    uint32 trapno;

    // pushed by x86 hardware
    uint32 err;
    uint32 eip;
    uint16 cs;
    uint16 padding5;
    uint32 eflags;

    // cross rings
    uint32 esp;
    uint16 ss, padding6;
} trap_frame_t;
void cpu_t::do_common_isr(trap_frame_t* frame)
{
    uint32 trapno = frame->trapno;
    if (trapno < IRQ_0) {
        do_exception(frame);
    }
    else if (trapno < IRQ_0 + IRQ_NUM) {
        do_interrupt(trapno);
    }
    else if (trapno == IRQ_SYSCALL) {
        do_syscall(frame);
    }
    else {
        console()->kprintf(RED, "Interrupt: %x, NOT KNOWN\n", frame->trapno);
    }
}

最终调用到cpu_t::do_common_isr(..)做统一处理。对前32号,调用do_exception处理异常,对32~48号中断,调用do_interrupt处理中断,系统调用单独处理,其他暂时不处理。

void cpu_t::do_exception(trap_frame_t* frame)
{
    uint32 trapno = frame->trapno;
    if (trapno <= 0x10) {
        console()->kprintf(RED, "Exception: %s\n", exception_msg[trapno]);
        console()->kprintf(RED, "current: %p, pid: %p\n", current, current->m_pid);
        console()->kprintf(RED, "errno: %x, eip: %x, cs: %x, esp: %x\n", frame->err, frame->eip, frame->cs, frame->esp);

        uint32 cr2 = 0xffffffff;
        __asm__ volatile("movl %%cr2, %%eax" : "=a" (cr2));
        console()->kprintf(RED, "cr2: %x\n", cr2);
    }
    else {
        console()->kprintf(RED, "Error Interrupt: %x, RESERVED!\n", trapno);
    }
    while (1) {
        halt();
    }
}

对于异常,只打印一些信息,然后halt。

void cpu_t::do_interrupt(uint32 trapno)
{
    int ch;
    switch (trapno) {
        case IRQ_0 + IRQ_KEYBOARD:
            os()->get_arch()->get_keyboard()->do_irq();
            ch = os()->get_arch()->get_keyboard()->read();
            if (ch != 0) {
                console()->kprintf(WHITE, "%c", ch);
            }
            break;
        case IRQ_0 + IRQ_TIMER:
            os()->update(os()->get_arch()->get_timer()->get_tick());
            os()->get_arch()->get_timer()->do_irq();
            break;
        case IRQ_0 + IRQ_HARDDISK:
            os()->get_ide()->do_irq();
            break;
        default:
            console()->kprintf(RED, "Interrupt: %x\n", trapno);
            break;
    }
}

void cpu_t::do_syscall(trap_frame_t* frame)
{
    m_syscall.do_syscall(frame);
}

对于中断,根据特定中断号做相应的处理,后面再描述。
对于系统调用,后面再描述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值