Linux内存管理-缺页异常 (Page fault)实现分析及优化

1.什么是缺页异常

在Linux虚拟内存管理中,缺页异常(Page Fault) 是CPU在访问虚拟地址时发现对应物理页未就绪时触发的中断。根据触发原因,缺页异常分为两类:

次要缺页(Minor Fault):物理页已存在(如缓存或共享内存),只需建立映射。
主要缺页(Major Fault):需要分配物理页或从磁盘加载数据(如匿名页首次访问或文件页未缓存)。
匿名页面(Anonymous Page) 是指不与任何文件关联的内存页(如进程堆、栈或mmap(MAP_ANONYMOUS)分配的内存),其生命周期与进程绑定,可能被交换到磁盘(Swap)。

当进程访问一个虚拟内存地址时,若该地址对应的物理页尚未分配、权限不足或已被换出到磁盘,CPU 会触发缺页异常。此时,内核接管异常处理流程,完成内存分配、权限修复或数据加载后,进程才能继续执行。

2.缺页异常的内核处理流程


Linux 内核的缺页异常处理入口为 do_page_fault(x86 架构),其核心逻辑如下:

1. 硬件触发异常:CPU 将触发异常的虚拟地址存入 CR2 寄存器。进入内核态,保存现场并调用缺页处理函数。

2. 异常原因检查
内核通过以下步骤判断异常原因:

// 伪代码逻辑
if (地址超出进程虚拟空间范围) {
    触发 SIGSEGV 信号(段错误);
} else if (访问权限不足) {
    检查是否可修复(如 COW);
} else {
    进入物理页分配流程;
}

3. 物理页分配与映射
内核调用 handle_mm_fault,根据虚拟内存区域(VMA)类型处理:

匿名页(Anonymous Page):分配物理页并填充零(Zero Page)。

文件映射页(File-backed Page):从文件系统读取数据到物理页。

Swap 页:从 Swap 分区加载数据到物理页。


4. 关键数据结构
VMA(vm_area_struct):描述进程虚拟内存区域(如堆、栈、文件映射)。页表项(PTE):存储虚拟地址到物理地址的映射关系及权限标志。
反向映射(Reverse Mapping):加速物理页的回收和 Swap 操作。

2.缺页异常的核心函数分析

1.函数do_page_fault

1.函数定义(以 x86 架构为例)

// arch/x86/mm/fault.c
void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code) {
    unsigned long address = read_cr2();  // 获取触发异常的虚拟地址
    struct vm_area_struct *vma;
    struct task_struct *tsk;
    struct mm_struct *mm;
    int fault;

    tsk = current;
    mm = tsk->mm;

    // 检查异常是否发生在内核态(如内核模块访问非法地址)
    if (unlikely(fault_in_kernel_space(address))) {
        if (vmalloc_fault(address) >= 0)  // 处理 vmalloc 异常
            return;
        // 触发内核 oops 或 panic
        bad_area_nosemaphore(regs, error_code, address);
        return;
    }

    // 检查进程是否处于中断上下文或未分配内存
    if (unlikely(!mm || pagefault_disabled())) {
        bad_area_nosemaphore(regs, error_code, address);
        return;
    }

    // 查找虚拟地址对应的 VMA(虚拟内存区域)
    vma = find_vma(mm, address);
    if (unlikely(!vma)) {
        bad_area(regs, error_code, address);
        return;
    }

    // 检查地址是否在 VMA 的合法范围内
    if (likely(vma->vm_start <= address && address < vma->vm_end)) {
        // 调用 handle_mm_fault 处理具体缺页逻辑
        fault = handle_mm_fault(vma, address, flags);
        if (unlikely(fault & VM_FAULT_ERROR)) {
            // 处理错误(如权限不足)
            __bad_area(regs, error_code, address, vma, fault);
            return;
        }
        // 成功处理缺页,返回用户态
        return;
    }

    // 地址不在 VMA 范围内,触发段错误(SIGSEGV)
    bad_area(regs, error_code, address);
}

2.do_page_fault 核心流程


1. 硬件触发异常
CPU 将触发异常的虚拟地址存入 CR2 寄存器(x86 特性)。
保存寄存器状态到 pt_regs 结构体,进入内核态。
2. 异常地址合法性检查
内核空间地址:检查是否由 vmalloc 区域访问引发,尝试修复映射。
用户空间地址:检查进程的 mm_struct 是否存在,确认地址是否合法。
3. 查找虚拟内存区域(VMA)
通过 find_vma 函数在进程的 VMA 红黑树中查找包含 address 的 VMA。
VMA 描述了进程虚拟地址空间的属性(如权限、文件映射、堆栈等)。
4. 权限与类型检查
检查访问权限(读/写/执行)是否与 VMA 的 vm_flags 匹配。
根据错误码 error_code 判断异常原因:
写操作触发:error_code & PF_WRITE
用户态触发:error_code & PF_USER
5. 调用 handle_mm_fault
fault = handle_mm_fault(vma, address, flags);
handle_mm_fault 进一步调用 handle_pte_fault,根据页表项(PTE)状态处理:
PTE 不存在:分配物理页(匿名页或文件映射页)。
PTE 存在但权限不足:处理写时复制(COW)或权限升级。
6. 错误处理
若 handle_mm_fault 返回错误(如 VM_FAULT_OOM),向进程发送 SIGSEGV 信号。

2.函数handle_mm_fault

// mm/memory.c
int handle_mm_fault(struct vm_area_struct *vma, unsigned long address, unsigned int flags) {
    pgd_t *pgd;  // 页全局目录项
    p4d_t *p4d;  // 四级页目录项(x86 五级分页时为 p4d,否则映射到 pgd)
    pud_t *pud;  // 页上级目录项
    pmd_t *pmd;  // 页中间目录项
    pte_t *pte;  // 页表项

    // 逐级查找页表项
    pgd = pgd_offset(vma->vm_mm, address);
    p4d = p4d_alloc(mm, pgd, address);
    pud = pud_alloc(mm, p4d, address);
    pmd = pmd_alloc(mm, pud, address);
    pte = pte_offset_map(pmd, address);

    // 处理 PTE 状态
    return handle_pte_fault(vma, address, pte, pmd, flags);
}

3.函数handle_pte_fault

// mm/memory.c
static int handle_pte_fault(struct vm_fault *vmf) {
    pte_t entry;

    if (!vmf->pte) {  // PTE 不存在
        if (vma_is_anonymous(vmf->vma))
            return do_anonymous_page(vmf);  // 分配匿名页
        else
            return do_fault(vmf);           // 文件映射页
    }

    entry = *vmf->pte;
    if (!pte_present(entry)) {  // PTE 存在但页不在内存
        if (pte_none(entry))
            return do_swap_page(vmf);  // 从 Swap 分区加载页
    }

    if (pte_protnone(entry))    // 权限不足(如 COW)
        return do_numa_page(vmf);

    if (flags & FAULT_FLAG_WRITE) {
        if (!pte_write(entry))  // 写操作触发 COW
            return do_wp_page(vmf);
    }

    // 其他错误处理
    return VM_FAULT_SIGBUS;
}

3.缺页异常的分类

1.缺页异常的基本分类


Linux内核将缺页异常分为两大类:Major Fault**(主要缺页)和Minor Fault(次要缺页)。  
两者的核心区别在于是否涉及磁盘I/O操*:

| 类型     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux技术芯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值