1缺页异常发生在内核态
2缺页异常发生在用户态
3源代码分析(选自2.6.10内核)
[cpp] view plain copy 3.1 do_page_fault() fastcall 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; int write; siginfo_t info; //将引发缺页异常的线性地址保存在address变量里面 __asm__("movl %%cr2,%0":"=r" (address)); if (notify_die(DIE_PAGE_FAULT, "page fault", regs, error_code, 14, SIGSEGV) == NOTIFY_STOP) return; tsk = current; //info是内核发送信号使用的信息结构体 info.si_code = SEGV_MAPERR; //该分支表明发生缺页时是发生在访问内核空间时 if (unlikely(address >= TASK_SIZE)) { //该分支表示发生缺页异常时,代码是在内核态访问内核态不存在 //的地址,转到vmalloc_fault处理分支,可能是访问了不连续的内核页面 if (!(error_code & 5)) goto vmalloc_fault; //做相应出错处理 goto bad_area_nosemaphore; } mm = tsk->mm; //在中断或者软中断中访问用户态空间,发生问题,是不可以的,因为中断或者 //软中断不代表任何的进程,mm为NULL代表着该进程是内核线程,内核线程 //继承了上一个普通进程页表,不能对其进行修改 if (in_atomic() || !mm) goto bad_area_nosemaphore; //尝试获取到读锁,若获得读锁失败时 if (!down_read_trylock(&mm->mmap_sem)) { //在内核态访问用户态的地址,这种情况发生在在 //进程的系统调用中去访问用户态的地址,在访问 //地址前,内核是不会去写对应的读锁的,所以可能是 //别的进程写了,相应的锁,所以需要等待,其它情况 //属于错误情况 if ((error_code & 4) == 0 && !search_exception_tables(regs->eip)) goto bad_area_nosemaphore; down_read(&mm->mmap_sem); } //下面这几句话是来判断出错地址是否在进程的线性区内 vma = find_vma(mm, address); //不在线性区内,地址错误 if (!vma) goto bad_area; //在线性区内,跳到正常处理部分 if (vma->vm_start <= address) goto good_area; //下面这些代码属于扩展进程栈的相关处理,该地址可能由push或者pusha指令引起 //向低地址扩展的栈其线性区的标志位会置上VM_GROWSDOWN if (!(vma->vm_flags & VM_GROWSDOWN)) goto bad_area; if (error_code & 4) {//异常发生在用户态 //对于栈操作,发生错误的内存地址不应该比esp小太多,不该小32 //个字节以上 if (address + 32 < regs->esp) goto bad_area; } //扩展进程的用户态堆栈 if (expand_stack(vma, address)) goto bad_area; good_area: info.si_code = SEGV_ACCERR; write = 0; switch (error_code & 3) { default://写,存在该页框,写时复制的情况 case 2: //写但不存在该页框 //该线性区不让写,发生错误 if (!(vma->vm_flags & VM_WRITE)) goto bad_area; write++; break; case 1: //读,存在该页框 goto bad_area; case 0: //读但是不存在该页框,缺页,需要进行调页 if (!(vma->vm_flags & (VM_READ | VM_EXEC))) goto bad_area; } survive: //在handle_mm_fault()函数里面处理缺页的情况 switch (handle_mm_fault(mm, vma, address, write)) { case VM_FAULT_MINOR: //在没有阻塞的情况下,完成了调页 tsk->min_flt++; break; case VM_FAULT_MAJOR: //在阻塞的情况下,完成了调页操作 tsk->maj_flt++; break; case VM_FAULT_SIGBUS: //发生其他错误 goto do_sigbus; case VM_FAULT_OOM: //内存不足 goto out_of_memory; default: BUG(); } /* * 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_read(&mm->mmap_sem); return; /* * Something tried to access memory that isn't in our memory map.. * Fix it, but check if it's kernel or user first.. */ bad_area: up_read(&mm->mmap_sem); bad_area_nosemaphore: //该错误发生在用户态代码访问时 if (error_code & 4) { if (is_prefetch(regs, address, error_code)) return; tsk->thread.cr2 = address; tsk->thread.error_code = error_code | (address >= TASK_SIZE); tsk->thread.trap_no = 14; info.si_signo = SIGSEGV; info.si_errno = 0; info.si_addr = (void __user *)address; //发送sigsegv信号给当前的进程 force_sig_info(SIGSEGV, &info, tsk); return; } //剩下的错误,发生在内核态 no_context: //是否有动态修正代码,该异常通常发生在将用户态线性地址 //作为参数传递给了系统调用,该错误发生在内核态访问一个 //用户态地址,但用户态地址不属于进程的地址空间 if (fixup_exception(regs)) return; if (is_prefetch(regs, address, error_code)) return; bust_spinlocks(1); //发生了真正的内核错误,往输出上打印相关错误信息 if (address < PAGE_SIZE) printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference"); else printk(KERN_ALERT "Unable to handle kernel paging request"); printk(" at virtual address lx\n",address); printk(KERN_ALERT " printing eip:\n"); printk("lx\n", regs->eip); asm("movl %%cr3,%0":"=r" (page)); page = ((unsigned long *) __va(page))[address >> 22]; printk(KERN_ALERT "*pde = lx\n", page); #ifndef CONFIG_HIGHPTE if (page & 1) { page &= PAGE_MASK; address &= 0x003ff000; page = ((unsigned long *) __va(page))[address >> PAGE_SHIFT]; printk(KERN_ALERT "*pte = lx\n", page); } #endif //产生Oops的消息 die("Oops", regs, error_code); bust_spinlocks(0); //退出相关进程 do_exit(SIGKILL); out_of_memory: //内存不足,删除当前进程 up_read(&mm->mmap_sem); if (tsk->pid == 1) { yield(); down_read(&mm->mmap_sem); goto survive; } printk("VM: killing process %s\n", tsk->comm); if (error_code & 4)//用户态进程,杀死用户态进程 do_exit(SIGKILL); goto no_context; do_sigbus: //发送SIGBUS信号给当前进程 up_read(&mm->mmap_sem); //内核态进程,生成oops等 if (!(error_code & 4)) goto no_context; if (is_prefetch(regs, address, error_code)) return; //用户态进程的话,发送SIGBUS给当前用户态进程 tsk->thread.cr2 = address; tsk->thread.error_code = error_code; tsk->thread.trap_no = 14; info.si_signo = SIGBUS; info.si_errno = 0; info.si_code = BUS_ADRERR; info.si_addr = (void __user *)address; force_sig_info(SIGBUS, &info, tsk); return; vmalloc_fault: { //在内核态访问内核空间内存,访问非连续性内存 int index = pgd_index(address); unsigned long pgd_paddr; pgd_t *pgd, *pgd_k; pmd_t *pmd, *pmd_k; pte_t *pte_k; asm("movl %%cr3,%0":"=r" (pgd_paddr)); pgd = index + (pgd_t *)__va(pgd_paddr); pgd_k = init_mm.pgd + index; if (!pgd_present(*pgd_k)) goto no_context; pmd = pmd_offset(pgd, address); pmd_k = pmd_offset(pgd_k, address); if (!pmd_present(*pmd_k)) goto no_context; //主要操作就是把主内核页表上对应的表项复制到当前进程的页表中 set_pmd(pmd, *pmd_k); pte_k = pte_offset_kernel(pmd_k, address); if (!pte_present(*pte_k)) goto no_context; return; } } 3.2 handle_mm_fault() int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long address, int write_access) { pgd_t *pgd; pmd_t *pmd; __set_current_state(TASK_RUNNING); pgd = pgd_offset(mm, address); inc_page_state(pgfault); if (is_vm_hugetlb_page(vma)) return VM_FAULT_SIGBUS; spin_lock(&mm->page_table_lock); //找到相应的pmd表的地址,没有的话,分配一个 pmd = pmd_alloc(mm, pgd, address); if (pmd) { //找到对应的pte表的地址,即页表的地址,找不到 //的话,分配一个 pte_t * pte = pte_alloc_map(mm, pmd, address); //进行相应的缺页处理: //1请求调页,2写时复制 if (pte) return handle_pte_fault(mm, vma, address, write_access, pte, pmd); } spin_unlock(&mm->page_table_lock); return VM_FAULT_OOM; } 3.3 handle_pte_fault() static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long address, int write_access, pte_t *pte, pmd_t *pmd) { pte_t entry; entry = *pte; if (!pte_present(entry)) { //页面从未被访问过,需要申请页面进行调页,匿名映射 //或者是磁盘文件映射都有可能 if (pte_none(entry)) return do_no_page(mm, vma, address, write_access, pte, pmd); //非线性磁盘文件映射 if (pte_file(entry)) return do_file_page(mm, vma, address, write_access, pte, pmd); //相关页框被作为交换页写到了磁盘上 return do_swap_page(mm, vma, address, pte, pmd, entry, write_access); } //写时复制 if (write_access) { if (!pte_write(entry)) return do_wp_page(mm, vma, address, pte, pmd, entry); entry = pte_mkdirty(entry); } entry = pte_mkyoung(entry); ptep_set_access_flags(vma, address, pte, entry, write_access); update_mmu_cache(vma, address, entry); pte_unmap(pte); spin_unlock(&mm->page_table_lock); return VM_FAULT_MINOR; } <script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script> 阅读(100) | 评论(0) | 转发(0) | 0
上一篇:经典排序算法之归并排序
下一篇:进程的页表创建详解
相关热门文章 linux 常见服务端口xmanager 2.0 for linux配置【ROOTFS搭建】busybox的httpd...openwrt中luci学习笔记Linux里如何查找文件内容... 给主人留下些什么吧!~~ 评论热议