在之前介绍malloc()和mmap()两个用户API函数的内核实现时,我们发现它们只建立了进程地址空间,在用户空间可以看到虚拟内存,但没有建立虚拟内存和物理内存之间的映射关系。当进程访问这些还没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常(也称为"缺页中断"),linux内核必须处理此异常。缺页异常是内存管理当中最复杂和重要的一部分,需要考虑很多的细节,包括匿名页面、KSM页面、page cache页面、写时复制、私有映射和共享映射等。
缺页异常处理依赖于处理器的体系结构,因此缺页异常底层的处理流程在内核代码中特定体系结构的部分。下面以ARMv7为例来介绍底层缺页异常处理的过程。
当在数据访问周期里进行存储访问时发生异常,基于ARMv7-A架构的处理器会跳转到异常向量表的Data abort向量中。Data abort的底层汇编处理和irq中断类似。汇编处理流程为__vectors_start->vector_dabt->__dabt_usr/__dabt_svc->dabt_helper->v7_early_abort,我们从ENTRY(v7_early_abort)开始介绍。
[arch/arm/mm/abort-ev7.S]
ENTRY(v7_early_abort)
mrc p15, 0, r1, c5, c0, 0 @ get FSR
mrc p15, 0, r0, c6, c0, 0 @ get FAR
/*
* V6 code adjusts the returned DFSR.
* New designs should not need to patch up faults.
*/
#if defined(CONFIG_VERIFY_PERMISSION_FAULT)
/*
* Detect erroneous permission failures and fix
*/
ldr r3, =0x40d @ On permission fault
and r3, r1, r3
cmp r3, #0x0d
bne do_DataAbort
mcr p15, 0, r0, c7, c8, 0 @ Retranslate FAR
isb
mrc p15, 0, ip, c7, c4, 0 @ Read the PAR
and r3, ip, #0x7b @ On translation fault
cmp r3, #0x0b
bne do_DataAbort
bic r1, r1, #0xf @ Fix up FSR FS[5:0]
and ip, ip, #0x7e
orr r1, r1, ip, LSR #1
#endif
b do_DataAbort
ARM的MMU中有如下两个与存储访问失效相关的寄存器。
-
失效状态寄存器(Data Fault Status Register, FSR).
-
失效地址寄存器(Data Fault Address Register, FAR).
当发生存储访问失效时,失效状态寄存器FSR会反映所发生的存储失效的相关信息,包括存储访问所属域和存储访问的类型等,同时失效地址寄存器会记录访问失效的虚拟地址。汇编函数v7_early_abort通过协处理器的寄存器c5和c6读取出FSR和FAR寄存器后,直接调用C语言的do_DataAbort()函数。
/*
* Dispatch a data abort to the relevant handler.
*/
asmlinkage void __exception
do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
/*struct fsr_info数据结构用于描述一条失效状态对应的处理方案,下面给出,请立即查看*/
const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
struct siginfo info;
if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
return;
pr_alert("Unhandled fault: %s (0x%03x) at 0x%08lx\n",
inf->name, fsr, addr);
show_pte(current->mm, addr);
info.si_signo = inf->sig;
info.si_errno = 0;
info.si_code = inf->code;
info.si_addr = (void __user *)addr;
arm_notify_die("", regs, &info, fsr, 0);
}
static inline int fsr_fs(unsigned int fsr)
{
return (fsr & FSR_FS3_0) | (fsr & FSR_FS4) >> 6;
}
struct fsr_info {
int (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs);
int sig;
int code;
const char *name;
};
@fn: 表示修复这条失效状态的函数指针.
@sig:表示处理失败时linux内核要发送的信号类型.
@name: 表示这条失效状态的名称.
[arch/arm/mm/fsr-2level.c]
tatic struct fsr_info fsr_info[] = {
/*
* The following are the standard ARMv3 and ARMv4 aborts. ARMv5
* defines these to be "precise" aborts.
*/
{ do_bad, SIGSEGV, 0, "vector exception" },
{ do_bad, SIGBUS, BUS_ADRALN, "alignment exception" },
{ do_bad, SIGKILL, 0, "terminal exception" },
{ do_bad, SIGBUS, BUS_ADRALN, "alignment exception" },
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" },
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
{ do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" },
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
{ do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" },
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
{ do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" },
{ do_bad, SIGBUS, 0, "external abort on translation" },
{ do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" },
{ do_bad, SIGBUS, 0, "external abort on translation" },
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" },
......
};
fsr_info[]数组列出了常见的地址失效处理方案,以页面转换失效(page translation fault)和页面访问权限失效为例,它们最终的解决方案是调用do_page_fault()来修复。