11. 缺页中断处理概述

在之前介绍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()来修复。

上一篇:使用PX4FLOW的pixhawk悬停模式疑难杂症(二)BAD OPTFLOW HEALTH


下一篇:CSS——CSS书写规范