6.5.2页错误的定位

  页错误的定位既包含虚拟地址的定位,也包含被调入页在交换文件(swapfile)或在可执行映象中的定位。

 具体地说,在一个进程访问一个无效页表项时,处理器产生一个陷入并报告一个页错误,它描述了页错误发生的虚地址和访问类型,这些类型通过页的错误码error_code中的前三位来判别 ,具体如下:

 

 * bit 0 == 0 means no page found, 1 means protection fault

 * bit 1 == 0 means read, 1 means write

 * bit 2 == 0 means kernel, 1 means user-mode

 

也就是说,如果第0位为0,则错误是由访问一个不存在的页引起的(页表的表项中present标志为0);否则,如果第0位为1,则错误是由无效的访问权所引起的。如果第1位为0,则错误是由读访问或执行访问所引起;如果为1,则错误是由写访问所引起的。如果第2位为0,则错误发生在处理器处于内核态时,否则,错误发生在处理器处于用户态时。

页错误的线性地址被存于CR2 寄存器,操作系统必须在vm_area_struct中找到页错误发生时页的虚拟地址(通过红黑树或旧版本中的AVL树),下面通过do_page_fault()中的一部分源代码来说明这个问题:

 

 /* CR2中包含有最新的页错误发生时的虚拟地址*/

   __asm__("movl %%cr2,%0":"=r" (address));

         vma = find_vma(current, address);

 

   如果没找到,则说明访问了非法虚地址,Linux会发信号终止进程(如果必要)。否则,检查页错误类型,如果是非法类型(越界错误,段权限错误等)同样会发信号终止进程,部分源代码如下:

 

vma = find_vma(current, 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) {  /*如是用户态进程*/

       /* 不可访问堆栈空间*/

        if (address + 32 < regs->esp)

           goto bad_area;

   }

   if (expand_stack(vma, address))

       goto bad_area;

bad_area:                 /* 用户态的访问*/

{  

    if (error_code & 4)

       current->tss.cr2 = address;

       current->tss.error_code = error_code;

       current->tss.trap_no = 14;

       force_sig(SIGSEGV, current);    /* 给当前进程发杀死信号*/

       return;

        ……

   die_if_kernel("Oops", regs, error_code);        /*报告内核 */

    do_exit(SIGKILL);                         /*强行杀死进程*/