Linux内核源代码情景分析-异常

来源:互联网 发布:小牛数据恢复破解版 编辑:程序博客网 时间:2024/04/29 11:06

    一、异常初始化

    中断向量表的IDT的初始化

void __init trap_init(void){#ifdef CONFIG_EISAif (isa_readl(0x0FFFD9) == 'E'+('I'<<8)+('S'<<16)+('A'<<24))EISA_bus = 1;#endifset_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);        ......}


    对从0到19的异常,设置异常处理程序,set_trap_gate如下:

static void __init set_trap_gate(unsigned int n, void *addr){_set_gate(idt_table+n,15,0,addr);}


    在IDT中设置了门描述符,如下图:



    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 %dspushl %eaxxorl %eax,%eaxpushl %ebppushl %edipushl %esipushl %edxdecl %eax//eax = -1pushl %ecxpushl %ebxcldmovl %es,%ecxmovl ORIG_EAX(%esp), %esi//获取错误码存放在esi中movl ES(%esp), %edi//获得异常处理函数存放在edi中movl %eax, ORIG_EAX(%esp)       //将-1存放在原来存放错误码的位置movl %ecx, ES(%esp)             //将es存放在原来存放异常函数的位置,这样就和中断一样了movl %esp,%edxpushl %esi//把错误码压入堆栈,做为异常处理函数的参数pushl %edx//把堆栈的指针也压入堆栈,做为异常处理函数的参数movl $(__KERNEL_DS),%edxmovl %edx,%dsmovl %edx,%esGET_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_AREAif (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_SMPGET_CURRENT(%ebx)movl processor(%ebx),%eaxshll $CONFIG_X86_L1_CACHE_SHIFT,%eaxmovl SYMBOL_NAME(irq_stat)(,%eax),%ecx# softirq_activetestl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx# softirq_mask#elsemovl SYMBOL_NAME(irq_stat),%ecx# softirq_activetestl SYMBOL_NAME(irq_stat)+4,%ecx# softirq_mask#endifjne   handle_softirq            //如果有软中断,先处理软中断ENTRY(ret_from_intr)                   //这个函数我们在,执行中断处理函数之后已经讲过了GET_CURRENT(%ebx)movl EFLAGS(%esp),%eax# mix EFLAGS and CSmovb CS(%esp),%altestl $(VM_MASK | 3),%eax# return to VM86 mode or non-supervisor?jne ret_with_reschedulejmp restore_allALIGNhandle_softirq:call SYMBOL_NAME(do_softirq)jmp ret_from_intr

0 0
原创粉丝点击