标签:
转自:http://blog.csdn.net/bullbat/article/details/7108402
linux缺页异常程序必须能够区分由编程引起的异常以及由引用属于进程地址空间但还尚未分配物理页框的页所引起的异常。在x86-ia32体系上由do_page_fault函数处理,每个版本有所差异,现分析的版本为2.6.32
-
- dotraplinkage void __kprobes
- do_page_fault(struct pt_regs *regs, unsigned long error_code)
- {
- struct vm_area_struct *vma;
- struct task_struct *tsk;
- unsigned long address;
- struct mm_struct *mm;
- int write;
- int fault;
-
- tsk = current;
- mm = tsk->mm;
-
-
-
- address = read_cr2();
-
-
- if (kmemcheck_active(regs))
- kmemcheck_hide(regs);
- prefetchw(&mm->mmap_sem);
-
- if (unlikely(kmmio_fault(regs, address)))
- return;
-
-
-
- if (unlikely(fault_in_kernel_space(address))) {
-
- if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {
-
- if (vmalloc_fault(address) >= 0)
- return;
-
- if (kmemcheck_fault(regs, address, error_code))
- return;
- }
-
-
-
- if (spurious_fault(error_code, address))
- return;
-
-
- if (notify_page_fault(regs))
- return;
-
-
- bad_area_nosemaphore(regs, error_code, address);
-
- return;
- }
-
-
- if (unlikely(notify_page_fault(regs)))
- return;
-
- if (user_mode_vm(regs)) {
- local_irq_enable();
- error_code |= PF_USER;
- } else {
- if (regs->flags & X86_EFLAGS_IF)
- local_irq_enable();
- }
-
- if (unlikely(error_code & PF_RSVD))
-
- pgtable_bad(regs, error_code, address);
-
- perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, address);
-
-
-
- if (unlikely(in_atomic() || !mm)) {
- bad_area_nosemaphore(regs, error_code, address);
- return;
- }
-
-
-
- if (unlikely(!down_read_trylock(&mm->mmap_sem))) {
-
- if ((error_code & PF_USER) == 0 &&
- !search_exception_tables(regs->ip)) {
- bad_area_nosemaphore(regs, error_code, address);
- return;
- }
- down_read(&mm->mmap_sem);
- } else {
-
- might_sleep();
- }
-
- vma = find_vma(mm, address);
-
- if (unlikely(!vma)) {
- bad_area(regs, error_code, address);
- return;
- }
-
-
- if (likely(vma->vm_start <= address))
- goto good_area;
-
- if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
- bad_area(regs, error_code, address);
- return;
- }
-
- if (error_code & PF_USER) {
-
-
- if (unlikely(address + 65536 + 32 * sizeof(unsigned long) < regs->sp)) {
- bad_area(regs, error_code, address);
- return;
- }
- }
- if (unlikely(expand_stack(vma, address))) {
- bad_area(regs, error_code, address);
- return;
- }
-
-
- good_area:
- write = error_code & PF_WRITE;
-
- if (unlikely(access_error(error_code, write, vma))) {
- bad_area_access_error(regs, error_code, address);
- return;
- }
-
-
-
- fault = handle_mm_fault(mm, vma, address, write ? FAULT_FLAG_WRITE : 0);
-
- if (unlikely(fault & VM_FAULT_ERROR)) {
- mm_fault_error(regs, error_code, address, fault);
- return;
- }
-
- if (fault & VM_FAULT_MAJOR) {
- tsk->maj_flt++;
- perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, 0,
- regs, address);
- } else {
- tsk->min_flt++;
- perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, 0,
- regs, address);
- }
-
- check_v8086_mode(regs, address, tsk);
-
- up_read(&mm->mmap_sem);
- }
大致流程中分为:
地址为内核空间:
1,当地址为内核地址空间并且在内核中访问时,如果是非连续内存地址,将init_mm中对应的项复制到本进程对应的页表项做修正;
2,地址为内核空间时,检查页表的访问权限;
3,如果1,2没搞定,跳到非法访问处理(在后面详细分析这个);
地址为用户空间:
4,如果使用了保留位,打印信息,杀死当前进程;
5,如果在中断上下文中火临界区中时,直接跳到非法访问;
6,如果出错在内核空间中,查看异常表,进行相应的处理;
7,查找地址对应的vma,如果找不到,直接跳到非法访问处,如果找到正常,跳到good_area;
8,如果vma->start_address>address,可能是栈太小,对齐进行扩展;
9,good_area处,再次检查权限;
10,权限正确后分配新页框,页表等;
对于缺页中断的非法访问由函数bad_area执行,该函数的执行情况分为:
1,如果在用户空间访问,直接发送SEGSEGV信号;
2,如果在内核空间访问分为两种情况:
1)地址是一个错误的系统调用参数,修正码(典型是发送SIGSEGV信号);
2)反之,杀死进程并显示内核的OOPS信息;
- static void
- __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,
- unsigned long address, int si_code)
- {
- struct task_struct *tsk = current;
-
-
-
- if (error_code & PF_USER) {
-
- local_irq_enable();
-
-
- if (is_prefetch(regs, error_code, address))
- return;
-
- if (is_errata100(regs, address))
- return;
-
- if (unlikely(show_unhandled_signals))
- show_signal_msg(regs, error_code, address, tsk);
-
-
- tsk->thread.cr2 = address;
- tsk->thread.error_code = error_code | (address >= TASK_SIZE);
- tsk->thread.trap_no = 14;
-
- force_sig_info_fault(SIGSEGV, si_code, address, tsk);
-
- return;
- }
-
- if (is_f00f_bug(regs, address))
- return;
-
- no_context(regs, error_code, address);
- }
内核访问时
- static noinline void
- no_context(struct pt_regs *regs, unsigned long error_code,
- unsigned long address)
- {
- struct task_struct *tsk = current;
- unsigned long *stackend;
- unsigned long flags;
- int sig;
-
-
-
- if (fixup_exception(regs))
- return;
-
-
- if (is_prefetch(regs, error_code, address))
- return;
-
- if (is_errata93(regs, address))
- return;
-
-
-
- flags = oops_begin();
-
- show_fault_oops(regs, error_code, address);
-
- stackend = end_of_stack(tsk);
- if (*stackend != STACK_END_MAGIC)
- printk(KERN_ALERT "Thread overran stack, or stack corrupted\n");
-
- tsk->thread.cr2 = address;
- tsk->thread.trap_no = 14;
- tsk->thread.error_code = error_code;
-
- sig = SIGKILL;
- if (__die("Oops", regs, error_code))
- sig = 0;
-
-
- printk(KERN_EMERG "CR2: %016lx\n", address);
-
- oops_end(flags, regs, sig);
- }
linux内核分析之缺页中断【转】
标签:
原文地址:http://www.cnblogs.com/sky-heaven/p/5657801.html