一、异常初始化
中断向量表的IDT的初始化
void __init trap_init(void)
{
#ifdef CONFIG_EISA
if (isa_readl(0x0FFFD9) == 'E'+('I'<<8)+('S'<<16)+('A'<<24))
EISA_bus = 1;
#endif
set_trap_gate(0,÷_error);
set_trap_gate(1,&debug);
......
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&spurious_interrupt_bug);
set_trap_gate(16,&coprocessor_error);
set_trap_gate(17,&alignment_check);
set_trap_gate(18,&machine_check);
set_trap_gate(19,&simd_coprocessor_error);
......
}
static void __init set_trap_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,0,addr);
}
Selector为_KERNEL_CS。P为1;DPL为00;DT为0;TYPE为15,陷阱门。Offset就是异常处理函数的偏移。
二、异常响应
异常一般发生在用户态,在内核态能触发的唯一异常就是缺页异常。 我们以缺页异常为例。
1、执行异常处理函数之前
如果异常发生在用户态,则会形成如下图:
图 异常处理和中断处理系统堆栈对照图
(1)、CPU根据具体的中断向量(本例中为14),从中断向量表IDT中找到相应的表项,而该表项应该是一个陷阱门。 首先把用户态堆栈的SS,用户堆栈的ESP,EFLAGS,用户空间的CS,EIP存入到系统堆栈中(从TSS中获取)。如果所发生的异常产生出错代码的话,就把这个出错代码也压入堆栈。在中断处理中,堆栈的这个位置存放的中断请求号。
(2)、CPU根据陷阱门的设置到达了异常处理函数。
ENTRY(page_fault)
pushl $ SYMBOL_NAME(do_page_fault)
jmp error_code
把do_page_fault压入堆栈中。在中断处理中,
堆栈的这个位置存放的ES。然后跳到error_code。
error_code:
pushl %ds
pushl %eax
xorl %eax,%eax
pushl %ebp
pushl %edi
pushl %esi
pushl %edx
decl %eax //eax = -1
pushl %ecx
pushl %ebx
cld
movl %es,%ecx
movl ORIG_EAX(%esp), %esi //获取错误码存放在esi中
movl ES(%esp), %edi //获得异常处理函数存放在edi中
movl %eax, ORIG_EAX(%esp) //将-1存放在原来存放错误码的位置
movl %ecx, ES(%esp) //将es存放在原来存放异常函数的位置,这样就和中断一样了
movl %esp,%edx
pushl %esi //把错误码压入堆栈,做为异常处理函数的参数
pushl %edx //把堆栈的指针也压入堆栈,做为异常处理函数的参数
movl $(__KERNEL_DS),%edx
movl %edx,%ds
movl %edx,%es
GET_CURRENT(%ebx)
call *%edi //执行异常处理函数
addl $8,%esp //跳过刚刚压入堆栈做为参数的两个值
jmp ret_from_exception
经过了error_code,异常处理系统堆栈中,do_page_fault变成了es,出错代码变成了-1。异常处理和中断处理的堆栈基本一样。
2、执行异常处理函数,do_page_fault
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)//刚刚压入堆栈的参数
{
struct task_struct *tsk;
struct mm_struct *mm;
struct vm_area_struct * vma;
unsigned long address;
unsigned long page;
unsigned long fixup;
int write;
siginfo_t info;
/* get the address */
__asm__("movl %%cr2,%0":"=r" (address));
tsk = current;
/*
* We fault-in kernel-space virtual memory on-demand. The
* 'reference' page table is init_mm.pgd.
*
* NOTE! We MUST NOT take any locks for this case. We may
* be in an interrupt or a critical region, and should
* only copy the information from the master page table,
* nothing more.
*/
if (address >= TASK_SIZE)
goto vmalloc_fault;
mm = tsk->mm;
info.si_code = SEGV_MAPERR;
/*
* If we're in an interrupt or have no user
* context, we must not take the fault..
*/
if (in_interrupt() || !mm)
goto no_context;
down(&mm->mmap_sem);
vma = find_vma(mm, address);
if (!vma)
goto bad_area;
if (vma->vm_start <= address)
goto good_area;
if (!(vma->vm_flags & VM_GROWSDOWN))
goto bad_area;
if (error_code & 4) {
/*
* accessing the stack below %esp is always a bug.
* The "+ 32" is there due to some instructions (like
* pusha) doing post-decrement on the stack and that
* doesn't show up until later..
*/
if (address + 32 < regs->esp)
goto bad_area;
}
if (expand_stack(vma, address))
goto bad_area;
/*
* Ok, we have a good vm_area for this memory access, so
* we can handle it..
*/
good_area:
info.si_code = SEGV_ACCERR;
write = 0;
switch (error_code & 3) {
default: /* 3: write, present */
#ifdef TEST_VERIFY_AREA
if (regs->cs == KERNEL_CS)
printk("WP fault at %08lx\n", regs->eip);
#endif
/* fall through */
case 2: /* write, not present */
if (!(vma->vm_flags & VM_WRITE))
goto bad_area;
write++;
break;
case 1: /* read, present */
goto bad_area;
case 0: /* read, not present */
if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
goto bad_area;
}
/*
* If for any reason at all we couldn't handle the fault,
* make sure we exit gracefully rather than endlessly redo
* the fault.
*/
switch (handle_mm_fault(mm, vma, address, write)) {
case 1:
tsk->min_flt++;
break;
case 2:
tsk->maj_flt++;
break;
case 0:
goto do_sigbus;
default:
goto out_of_memory;
}
/*
* Did it hit the DOS screen memory VA from vm86 mode?
*/
if (regs->eflags & VM_MASK) {
unsigned long bit = (address - 0xA0000) >> PAGE_SHIFT;
if (bit < 32)
tsk->thread.screen_bitmap |= 1 << bit;
}
up(&mm->mmap_sem);
return;
.......
}
3、执行异常处理函数之后
error_code中最后jmp ret_from_exception。
ret_from_exception:
#ifdef CONFIG_SMP
GET_CURRENT(%ebx)
movl processor(%ebx),%eax
shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
movl SYMBOL_NAME(irq_stat)(,%eax),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx # softirq_mask
#else
movl SYMBOL_NAME(irq_stat),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4,%ecx # softirq_mask
#endif
jne handle_softirq //如果有软中断,先处理软中断
ENTRY(ret_from_intr) //这个函数我们在,执行中断处理函数之后已经讲过了
GET_CURRENT(%ebx)
movl EFLAGS(%esp),%eax # mix EFLAGS and CS
movb CS(%esp),%al
testl $(VM_MASK | 3),%eax # return to VM86 mode or non-supervisor?
jne ret_with_reschedule
jmp restore_all
ALIGN
handle_softirq:
call SYMBOL_NAME(do_softirq)
jmp ret_from_intr