一、异常初始化
中断向量表的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