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);
}
对于中断,根据特定中断号做相应的处理,后面再描述。
对于系统调用,后面再描述。