页错误的定位既包含虚拟地址的定位,也包含被调入页在交换文件(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);
/*强行杀死进程*/