首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

缺页异常详解(5)

缺页异常详解(5)

   /*为引发缺页的进程分配一个物理页框,它先确定与引发缺页的线性地址对应的各级页目录项是否存在,如不存在则分进行分配。具体如何分配这个页框是通过调用handle_pte_fault完成的*/         fault = handle_mm_fault(mm, vma, addr & PAGE_MASK, (fsr & FSR_WRITE) ? FAULT_FLAG_WRITE : 0);
         if (unlikely(fault & VM_FAULT_ERROR))
                   return fault;
         if (fault & VM_FAULT_MAJOR)
                   tsk->maj_flt++;
         else
                   tsk->min_flt++;
         return fault;
check_stack:
    /*addr后面的vmavm_flags含有VM_GROWSDOWN标志,这说明这个vma是属于栈的vma,所以addr是在栈中,有可能是栈空间不够时再进栈导致的访问错误,同时查看栈是否还能扩展,如果不能扩展(expand_stack返回非0)则确认确实是栈溢出导致,即addr确实是栈中地址,不是非法地址,应该进入缺页中的请求调页*/
         if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))
                   goto good_area;
out:
         return fault;
}
l  首先,查看缺页异常的这个虚拟地址addr,找它后面最近的vma,如果真的没有找到,那么说明访问的地址是真的错误了,因为它根本不在所分配的任何一个vma线性区;这是一种严重错误,将返回错误码(fault)VM_FAULT_BADMAP,内核会杀掉这个进程;
l  如果addr后面有vma,但addr并未落在这个vma的区间内,这存在一种可能,要知道栈的增长方向和堆是相反的即栈是向下增长,所以也许addr实际上是栈的一个地址,它后面的vma实际上是栈的vma,栈已无法扩展,即访问addr时,这个addr并没有落在vma中所以更无二级页表映射,导致缺页异常,所以查看addr后面的vma是否是向下增长并且栈是否无法扩展,以此界定addr是不是栈地址,如果是则进入缺页异常处理流程,否则同样返回错误码(fault)VM_FAULT_BADMAP,内核会杀掉这个进程;
l  权限错误也就返回,比如缺页报错(fsr)报的是不可写,但vma本身就不可写,那么就直接返回,因为问题根本不是缺页,而是vma就已经有问题;返回错误码(fault) VM_FAULT_BADACCESS,这也是一种严重错误,内核会杀掉这个进程;s
l  最后是对确实缺页异常的情况进行处理,调用函数handle_mm_fault,正常情况下将返回VM_FAULT_MAJORVM_FAULT_MINOR,返回错误码fault并加一taskmaj_fltmin_flt成员
函数handle_mm_fault,就是为引发缺页的进程分配一个物理页框,它先确定与引发缺页的线性地址对应的各级页目录项是否存在,如不存在则分进行分配。具体如何分配这个页框是通过调用handle_pte_fault()完成的,注意最后一个参数flag,它来源于fsr,标识写异常和非写异常,这是为了达到进一步推后分配物理内存的一个铺垫;源码如下
int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,
                   unsigned long address, unsigned int flags)
{
         pgd_t *pgd;
         pud_t *pud;
         pmd_t *pmd;
         pte_t *pte;
         __set_current_state(TASK_RUNNING);
         count_vm_event(PGFAULT);
         if (unlikely(is_vm_hugetlb_page(vma)))
                   return hugetlb_fault(mm, vma, address, flags);
    /*返回addr对应的一级页表条目*/
         pgd = pgd_offset(mm, address);
    /*对于armpud就是pgd*/
         pud = pud_alloc(mm, pgd, address);
         if (!pud)
                   return VM_FAULT_OOM;
    /*对于armpmd就是pud就是pgd*/
         pmd = pmd_alloc(mm, pud, address);
         if (!pmd)
                   return VM_FAULT_OOM;
    /*返回addr对应的二级页表条目*/
         pte = pte_alloc_map(mm, pmd, address);
         if (!pte)
                   return VM_FAULT_OOM;
    /*该函数根据页表项pte所描述的物理页框是否在物理内存中,分为两大类:

请求调页:被访问的页框不在主存中,那么此时必须分配一个页框,分为线性(匿名/文件)映射、非线性映射、swap情况下映射


写时复制:被访问的页存在,但是该页是只读的,内核需要对该页进行写操作,


此时内核将这个已存在的只读页中的数据复制到一个新的页框中*/

         return handle_pte_fault(mm, vma, address, pte, pmd, flags);
}
继承事业,薪火相传
返回列表