用户空间的缺页异常可以分为两种情况--

  1、触发异常的线性地址处于用户空间的vma中,但还未分配物理页,如果访问权限OK的话内核给进程分配相应的物理页了

  2、触发异常的线性地址不处于用户空间的vma中,这种情况得判断是不是因为用户进程的栈空间消耗完而触发的缺页异常,如果是的话则在用户空间对栈区域进行扩展,并且分配相应的物理页,如果不是则作为一次非法地址访问来处理,内核将终结进程

  下面来看do_page_fault()函数对用户空间缺页异常的处理

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;  //获取当前进程的地址空间

 /* Get the faulting address: */
 address = read_cr2(); //读取CR2寄存器获取触发异常的访问地址

 ...
         ...
         ...
         ...

 vma = find_vma(mm, address);//试图寻找到一个离address近的vma,vma包含address或在address之后

 /*没有找到这样的vma则说明address之后没有虚拟内存区域,因此该address肯定是无效的,
   通过bad_area()路径来处理,bad_area()的主体是__bad_area()-->bad_area_nosemaphore()*/
 if (unlikely(!vma)) {
  bad_area(regs, error_code, address);
  return;
 }
 /*如果该地址包含在vma之中,则跳转到good_area处进行处理*/
 if (likely(vma->vm_start <= address))
  goto good_area;

 /*不是前面两种情况的话,则判断是不是由于用户堆栈所占的页框已经使用完,而一个PUSH指令
   引用了一个尚未和页框绑定的虚拟内存区域导致的一个异常,属于堆栈的虚拟内存区,其VM_GROWSDOWN位
   被置位*/
 if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
  bad_area(regs, error_code, address);//不是堆栈区域,则用bad_area()来处理
  return;
 }
 if (error_code & PF_USER) {//必须处于用户空间
  /*
   * Accessing the stack below %sp is always a bug.
   * The large cushion allows instructions like enter
   * and pusha to work. ("enter $65535, $31" pushes
   * 32 pointers and then decrements %sp by 65535.)
   */
   /*这里检查address,只有该地址足够高(和堆栈指针的差不大于65536+32*sizeof(unsigned long)),
     才能允许用户进程扩展它的堆栈地址空间,否则bad_area()处理*/
  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()处理
  bad_area(regs, error_code, address);
  return;
 }