Linux内核分析之内存管理-09

韩乔落

第9章:缺页异常处理

基于 Linux 6.12.38 源码


9.1 缺页异常概述

9.1.1 什么是缺页异常

缺页异常 (Page Fault) 是当程序访问的虚拟页面不在物理内存中时触发的异常。

触发场景:

  1. 按需加载: 程序访问的代码/数据页首次被访问
  2. 写时复制 (COW): fork 后父子进程共享页面,一方尝试写入
  3. 匿名页面: malloc/mmap 分配的内存首次访问
  4. 文件映射: mmap 映射的文件页首次访问
  5. 保护违例: 尝试写入只读页面

9.1.2 x86_64 缺页异常

异常向量: #PF (异常 14)

错误码格式:

1
2
3
4
5
6
7
8
9
┌────┬──────────┬─────────┬──────────┐
│ 位 │ 名称 │ 值 │ 描述 │
├────┼──────────┼─────────┼──────────┤
│ 0 │ P │ 0/1 │ 0=页不存在, 1=页存在但保护违例 │
│ 1 │ W │ 0/1 │ 0=读操作, 1=写操作 │
│ 2 │ U │ 0/1 │ 0=监督模式, 1=用户模式 │
│ 3 │ R │ 0/1 │ 0=普通访问, 1=保留位访问 │
│ 4 │ I │ 0/1 │ 0=取数据, 1=取指令 │
└────┴──────────┴─────────┴──────────┘

错误码宏定义:

位置: arch/x86/include/asm/traps.h

1
2
3
4
5
6
7
8
/* 页错误标志 */
#define X86_PF_WRITE (1UL << 0) /* 写操作 */
#define X86_PF_PRESENT (1UL << 1) /* 页存在 */
#define X86_PF_USER (1UL << 2) /* 用户模式 */
#define X86_PF_RESERVED (1UL << 3) /* 保留位 */
#define X86_PF_INSTR (1UL << 4) /* 指令取 */
#define X86_PF_PK (1UL << 5) /* 保护密 */
#define X86_PF_SGX (1UL << 15) /* SGX */

9.2 缺页异常处理流程

9.2.1 处理流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
用户/内核访问无效地址


┌──────────────────────────────────────┐
│ CPU 触发 #PF 异常 │
│ - 错误码推入栈 │
│ - 故障地址存入 CR2 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ do_page_fault(regs, error_code, │
│ address) │
│ │
│ 1. 检查地址是否在内核空间 │
│ 2. 检查是否在临界区 │
│ 3. 查找对应的 VMA │
│ 4. 检查访问权限 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ handle_mm_fault(vma, address, │
│ flags, regs) │
│ │
│ 1. 检查 VMA 权限 │
│ 2. 调用特定处理函数 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 根据页面类型处理: │
│ │
│ ┌────────────────────────────────┐ │
│ │ 文件映射: filemap_fault │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ 匿名映射: do_anonymous_page │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ 写时复制: do_wp_page │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ 堆栈扩展: expand_stack │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘

9.2.2 入口函数

位置: arch/x86/mm/fault.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* 页错误处理入口 */
DEFINE_IDTENTRY_RAW(exc_page_fault)
{
unsigned long address = read_cr2(); /* 读取故障地址 */
irqentry_state_t state;

/*
* 页错误可能发生在任何上下文中:
* - 用户空间
* - 内核空间
* - 中断上下文
* - NMI 上下文
*/

/* 保存寄存器状态 */
state = irqentry_enter(regs);

/* 处理页错误 */
handle_page_fault(regs, error_code, address);

/* 恢复寄存器状态 */
irqentry_exit(regs, state);
}

/* 内核页错误处理 */
static __always_inline void handle_page_fault(struct pt_regs *regs,
unsigned long error_code,
unsigned long address)
{
/* 检查是否是内核地址 */
if (unlikely(fault_in_kernel_space(address))) {
/* 内核空间页错误 */
do_kern_addr_fault(regs, error_code, address);
} else {
/* 用户空间页错误 */
do_user_addr_fault(regs, error_code, address);
}
}

9.3 匿名页面缺页

9.3.1 do_anonymous_page

位置: mm/memory.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct page *page;
vm_fault_t ret = VM_FAULT_OOM;
pte_t entry;

/* 1. 检查 VMA 是否为匿名映射 */
if (vma->vm_file) {
/* 不是匿名映射 */
return VM_FAULT_SIGBUS;
}

/* 2. 如果是写操作,分配新页面 */
if (vmf->flags & FAULT_FLAG_WRITE) {
/* 分配零页 */
page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
if (!page)
goto oom;

/* 设置页表项 */
ret = set_pte_range(vma, vmf->address, vmf->address + PAGE_SIZE,
page, 1);
if (ret)
goto oom;
} else {
/* 只读访问,使用共享零页 */
entry = pte_mkspecial(pfn_pte(my_zero_pfn(0), vma->vm_page_prot));
ret = finish_fault(vmf);
}

return ret;

oom:
return VM_FAULT_OOM;
}

9.3.2 匿名页面分配流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
do_anonymous_page(vmf)


┌──────────────────────────────────────┐
│ 1. 检查 VMA 是否为匿名映射 │
│ - 如果有 vm_file,返回错误 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 2. 检查操作类型 │
│ - 写操作: 分配新页面 │
│ - 读操作: 使用共享零页 │
└──────────────────────────────────────┘

写操作

┌──────────────────────────────────────┐
│ 3. 分配零页 │
│ - alloc_zeroed_user_highpage │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 设置页表项 │
│ - set_pte_range │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 5. 返回 VM_FAULT_NOPAGE │
└──────────────────────────────────────┘

9.4 文件映射缺页

9.4.1 filemap_fault

位置: mm/filemap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
vm_fault_t filemap_fault(struct vm_fault *vmf)
{
int error;
struct file *file = vmf->vma->vm_file;
struct file_lock_context *file_lock_context = file->f_lock_context;
struct address_space *mapping = file->f_mapping;

/* 1. 检查页缓存 */
struct folio *folio;
pgoff_t offset = vmf->pgoff;

folio = filemap_get_folio(mapping, offset);
if (IS_ERR(folio)) {
/* 页面不在缓存,需要读取 */
error = filemap_read_folio(file, mapping, ra, offset,
&folio, NULL);
if (error)
return VM_FAULT_SIGBUS;
}

/* 2. 检查 folio 错误 */
if (folio_test_error(folio)) {
/* 页面错误 */
folio_unlock(folio);
folio_put(folio);
return VM_FAULT_SIGBUS;
}

/* 3. 映射 folio 到用户空间 */
error = vmf_insert_folio(vmf, folio);

/* 4. 释放 folio 引用 */
folio_put(folio);

return error;
}

9.4.2 文件映射缺页流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
filemap_fault(vmf)


┌──────────────────────────────────────┐
│ 1. 检查页缓存 │
│ - filemap_get_folio │
└──────────────────────────────────────┘

不在缓存

┌──────────────────────────────────────┐
│ 2. 从文件读取页面 │
│ - filemap_read_folio │
│ - 启动 IO 操作 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 3. 等待 IO 完成 │
│ - folio_wait_locked │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 映射到用户空间 │
│ - vmf_insert_folio │
└──────────────────────────────────────┘

9.5 写时复制

9.5.1 do_wp_page

位置: mm/memory.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
vm_fault_t do_wp_page(struct vm_fault *vmf)
__releases(vmf->ptl)
{
struct vm_area_struct *vma = vmf->vma;
struct folio *folio = vmf->page;

/* 1. 检查页面是否私有的 */
if (folio_mapcount(folio) == 1 && !folio_test_dirty(folio)) {
/*
* 只有当前进程映射此页面,直接标记为可写
* 无需复制
*/
wp_page_reuse(vmf, folio);
return VM_FAULT_WRITE;
}

/* 2. 多个进程共享,需要复制 */
get_folio(folio);

/* 3. 分配新页面 */
struct folio *new_folio = alloc_folio(GFP_HIGHUSER_MOVABLE, 0);
if (!new_folio) {
folio_put(folio);
return VM_FAULT_OOM;
}

/* 4. 复制页面内容 */
copy_user_highpage(new_folio, folio, vmf->address, vma);

/* 5. 设置新页面为可写 */
__folio_mark_uptodate(new_folio);
folio_set_dirty(new_folio);

/* 6. 更新页表 */
pte_unmap_unlock(vmf->pte, vmf->ptl);
vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address, &vmf->ptl);
if (likely(pte_same(*vmf->pte, vmf->orig_pte))) {
pte_t entry = mk_pte_folio(new_folio, vma->vm_page_prot);
entry = pte_mkdirty(pte_mkwrite(entry));
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);
}

folio_put(folio);
return VM_FAULT_WRITE;
}

9.5.2 COW 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
do_wp_page(vmf)


┌──────────────────────────────────────┐
│ 1. 检查页面映射计数 │
│ - mapcount == 1: 私有页面 │
│ - mapcount > 1: 共享页面 │
└──────────────────────────────────────┘

私有页面

┌──────────────────────────────────────┐
│ 2. 直接标记为可写 │
│ - wp_page_reuse │
└──────────────────────────────────────┘

共享页面

┌──────────────────────────────────────┐
│ 3. 分配新页面 │
│ - alloc_folio │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 复制页面内容 │
│ - copy_user_highpage │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 5. 更新页表 │
│ - 设置新页面 PTE │
└──────────────────────────────────────┘

9.6 堆栈扩展

9.6.1 expand_stack

位置: mm/mmap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int expand_stack(struct vm_area_struct *vma, unsigned long address)
{
/* 1. 检查是否是栈 VMA */
if (!(vma->vm_flags & VM_GROWSDOWN))
return -ENOMEM;

/* 2. 检查地址是否在合理范围内 */
if (address < vma->vm_start) {
/* 向下扩展 */
unsigned long size = vma->vm_end - address;
if (size > rlimit(RLIMIT_STACK))
return -ENOMEM;

/* 3. 检查是否与现有 VMA 重叠 */
struct mm_struct *mm = vma->vm_mm;
struct vm_area_struct *next = find_vma(mm, vma->vm_end);

if (next && next->vm_start < vma->vm_end + size)
return -ENOMEM;
}

/* 4. 扩展 VMA */
vma->vm_start = address;

return 0;
}

9.6.2 栈扩展流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
expand_stack(vma, address)


┌──────────────────────────────────────┐
│ 1. 检查是否是栈 VMA │
│ - VM_GROWSDOWN 标志 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 2. 检查地址范围 │
│ - 检查 RLIMIT_STACK │
│ - 检查与现有 VMA 重叠 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 3. 扩展 VMA │
│ - 调整 vm_start │
└──────────────────────────────────────┘

9.7 缺页统计

9.7.1 vm_fault 结构 (完整版)

位置: include/linux/mm.h:547

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* struct vm_fault - 页 fault 信息
*
* vm_fault 由页 fault 处理器填充并传递给 vma 的 ->fault 函数。
* vma 的 ->fault 负责返回 VM_FAULT_xxx 标志的位掩码,
* 给出关于 fault 如何处理的详细信息。
*
* MM 层为页面分配填充 gfp_mask,但 fault 处理器可以
* 在其实现需要不同的分配上下文时修改它。
*
* 如果可能,应使用 pgoff 而不是 virtual_address。
*/
struct vm_fault {
/* 常量字段 - 由页 fault 处理器填充 */
const struct {
struct vm_area_struct *vma; /* 目标 VMA */
gfp_t gfp_mask; /* 分配的 gfp 掩码 */
pgoff_t pgoff; /* 基于 vma 的逻辑页偏移 */
unsigned long address; /* 故障虚拟地址 - 已掩码 */
unsigned long real_address; /* 故障虚拟地址 - 未掩码 */
};

enum fault_flag flags; /* FAULT_FLAG_xxx 标志 */
pmd_t *pmd; /* 匹配地址的 PMD 条目指针 */
pud_t *pud; /* 匹配地址的 PUD 条目指针 */

union {
pte_t orig_pte; /* 故障时 PTE 的值 */
pmd_t orig_pmd; /* 故障时 PMD 的值 (PMD fault) */
};

spinlock_t *ptl; /* 页表锁 */
pte_t *pte; /* PTE 指针 */

union {
struct page *page; /* 分配的页面 */
struct folio *folio; /* 分配的 folio (内核 6.6+) */
};

atomic_subword_t nr_pages; /* 多页数量 */
};

9.7.2 fault_flag 标志

位置: include/linux/mm.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Fault 标志枚举 */
enum fault_flag {
FAULT_FLAG_WRITE = 0x01, /* 写 fault */
FAULT_FLAG_MKWRITE = 0x02, /* 创建写映射 (PTE 标记为写) */
FAULT_FLAG_ALLOW_RETRY = 0x04, /* 允许重试 (处理信号) */
FAULT_FLAG_RETRY_NOWAIT = 0x08, /* 不等待重试 */
FAULT_FLAG_KILLABLE = 0x10, /* 可被 fatal signal 杀死 */
FAULT_FLAG_TRIED = 0x20, /* 已尝试一次 */
FAULT_FLAG_USER = 0x40, /* 用户空间 fault */
FAULT_FLAG_REMOTE = 0x80, /* 远程节点 fault (NUMA) */
FAULT_FLAG_INSTRUCTION = 0x100, /* 指令取 fault */
FAULT_FLAG_INTERRUPTIBLE = 0x200, /* 可中断等待 */
FAULT_FLAG_VMA_LOCK = 0x400, /* 使用 Per-VMA 锁 */
FAULT_FLAG_ORIG_PTE_VALID = 0x800, /* orig_pte 有效 */
};

9.7.3 缺页标志位说明

标志 描述
FAULT_FLAG_WRITE 0x01 写操作导致的 fault
FAULT_FLAG_MKWRITE 0x02 创建写映射 (用于写时复制)
FAULT_FLAG_ALLOW_RETRY 0x04 允许在信号处理后重试
FAULT_FLAG_RETRY_NOWAIT 0x08 不等待重试 (立即返回)
FAULT_FLAG_KILLABLE 0x10 可被致命信号中断
FAULT_FLAG_TRIED 0x20 已经尝试过一次
FAULT_FLAG_USER 0x40 用户空间 fault
FAULT_FLAG_REMOTE 0x80 远程节点 fault (NUMA)
FAULT_FLAG_INSTRUCTION 0x100 指令获取 fault
FAULT_FLAG_INTERRUPTIBLE 0x200 可中断等待
FAULT_FLAG_VMA_LOCK 0x400 使用 Per-VMA 锁
FAULT_FLAG_ORIG_PTE_VALID 0x800 orig_pte 字段有效

9.7.4 vm_fault 返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* vm_fault_t 返回值 */
#define VM_FAULT_OOM 0x0001 /* 内存不足 */
#define VM_FAULT_SIGBUS 0x0002 /* 总线错误 */
#define VM_FAULT_MAJOR 0x0004 /* 主要缺页 (需从磁盘读取) */
#define VM_FAULT_WRITE 0x0008 /* 写时复制 */
#define VM_FAULT_HWPOISON 0x0010 /* 硬件中毒页面 */
#define VM_FAULT_HWPOISON_LARGE 0x0020 /* 大页硬件中毒 */
#define VM_FAULT_SIGSEGV 0x0040 /* 段错误 */
#define VM_FAULT_NOPAGE 0x0080 /* 无页面 */
#define VM_FAULT_LOCKED 0x0100 /* 页面已锁定 */
#define VM_FAULT_RETRY 0x0200 /* 重试 */
#define VM_FAULT_FALLBACK 0x0400 /* 回退 */
#define VM_FAULT_DONE_COW 0x0800 /* COW 完成 */
#define VM_FAULT_NEEDDSYNC 0x1000 /* 需要同步 */
#define VM_FAULT_COMPLETED 0x2000 /* 完成 */
#define VM_FAULT_HINDEX_MASK 0x1f0000 /* hindex 掩码 */

/* 返回值辅助宏 */
#define VM_FAULT_ERROR (VM_FAULT_OOM | VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV)
#define VM_FAULT_FAULTS (VM_FAULT_ERROR | VM_FAULT_HWPOISON | VM_FAULT_HWPOISON_LARGE)
#define VM_FAULT_PTE (VM_FAULT_COMPLETED | VM_FAULT_DONE_COW | VM_FAULT_FALLBACK | VM_FAULT_NOPAGE)

9.7.5 /proc 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看进程缺页统计
cat /proc/[pid]/stat

# 输出字段
# - minflt: 次要缺页 (可以从 RAM 满足)
# - majflt: 主要缺页 (需要从磁盘读取)

# 查看系统缺页统计
cat /proc/vmstat | grep -i fault

# 输出
# pgfault: 12345678
# pgmajfault: 12345


9.8 缺页处理决策树

9.8.1 完整决策流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
┌─────────────────────────────────────────────────────────────────────┐
│ 缺页异常处理决策树 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ do_page_fault(address, error_code) │
│ │ │
│ ├──► 内核地址? ──YES─► do_kern_addr_fault() │
│ │ │
│ ├──► 找不到VMA? ──YES─► 扩展栈? ──YES─► expand_stack() │
│ │ │ │
│ │ NO │
│ │ ▼ │
│ │ SIGSEGV │
│ │ │
│ └──► 用户地址 + 找到VMA │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ handle_mm_fault() │ │
│ └──────────────────────┘ │
│ │ │
│ ├──► 地址在页表中? ──NO──► 触发缺页处理 │
│ │ │ │
│ │ VMA类型? │
│ │ │ │
│ │ ┌───────────┼───────────┐ │
│ │ ▼ ▼ ▼ │
│ │ 匿名VMA 文件VMA 其他 │
│ │ │ │ │ │
│ │ ▼ ▼ ▼ │
│ │ do_anonymous filemap_fault SIGBUS │
│ │ _page() │ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ 分配页面 从文件读取 │
│ │ (alloc_pages) (read_folio) │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ 设置页表 设置页表 │
│ │ │ │ │
│ └────────┴───────────┘ │
│ │ │
│ 地址在页表中? ──YES──► 检查权限 │
│ │ │ │
│ ▼ ▼ │
│ 保护违例? ──YES─► do_wp_page() │
│ │ │ │
│ │ ▼ │
│ │ 写时复制 (COW) │
│ │ │ │
│ │ ├───► mapcount==1 ───► │
│ │ │ 直接写标记 │
│ │ │ │
│ │ └───► mapcount>1 ────► │
│ │ 分配新页 │
│ │ 复制内容 │
│ │ 更新页表 │
│ │ │
│ └─────────────────────────────────────│
│ │
│ 相关章节: │
│ ───────── │
│ • ch05 - 页表管理 (页表项检查) │
│ • ch06 - VMA管理 (find_vma) │
│ • ch09 - 缺页处理 (详细实现) │
│ • ch11 - mmap/mprotect (权限检查) │
│ • ch12 - 文件映射与页缓存 │
│ │
└─────────────────────────────────────────────────────────────────────┘

9.8.2 处理路径总结

路径 触发条件 处理函数 结果
匿名页面首次访问 PTE不存在,匿名VMA do_anonymous_page 分配零页,设置PTE
文件页面首次访问 PTE不存在,文件VMA filemap_fault 从文件读取或使用缓存
写时复制 PTE存在但只读,写操作 do_wp_page 复制页面或直接写标记
栈扩展 地址接近栈边界 expand_stack 扩展VMA
保护违例 权限不足 SIGSEGV 进程终止

9.9 本章小结与跨章节关联

9.9.1 本章要点

本章介绍了 Linux 6.12 的缺页异常处理:

  1. 缺页概述: 按需加载、COW、匿名页面、文件映射
  2. 错误码: x86_64 缺页错误码格式
  3. 处理流程: do_page_fault → handle_mm_fault → 特定处理
  4. 匿名页面: do_anonymous_page,分配零页
  5. 文件映射: filemap_fault,从文件读取
  6. 写时复制: do_wp_page,复制共享页面
  7. 堆栈扩展: expand_stack,向下扩展栈
  8. 缺页统计: /proc 接口,minflt/majflt

9.9.2 与其他章节的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌────────────────────────────────────────────────────────────┐
│ 本章内容 (缺页异常处理) │
├────────────────────────────────────────────────────────────┤
│ │
│ do_page_fault ──▶ ch05:页表管理 (检查页表项) │
│ (PTE是否有效/存在) │
│ │
│ do_page_fault ──▶ ch06:VMA管理 (find_vma) │
│ (确定地址所属VMA) │
│ │
│ do_anonymous_page ──▶ ch07:alloc_pages │
│ (分配物理页面) │
│ │
│ do_anonymous_page ──▶ ch05:页表管理 (set_pte_at) │
│ (建立虚拟→物理映射) │
│ │
│ filemap_fault ────▶ ch12:页缓存 │
│ (查找或读取文件页面) │
│ │
│ do_wp_page ────────▶ ch02:Folio/Mapcount │
│ (检查页面是否共享) │
│ │
│ do_wp_page ────────▶ ch07:alloc_pages (COW时分配新页) │
│ │
│ expand_stack ──────▶ ch06:VMA管理 (修改vm_start) │
│ │
│ 权限检查 ─────────▶ ch11:mprotect (VMA权限验证) │
│ │
│ 缺页统计 ─────────▶ ch15:vmstat (pgfault计数器) │
│ │
└────────────────────────────────────────────────────────────┘

关键处理路径:

  1. 匿名页面分配: 缺页异常do_anonymous_page()alloc_pages()set_pte_at()

    • 相关章节: ch07 (页面分配), ch05 (页表管理)
  2. 文件页面映射: 缺页异常filemap_fault()页缓存查找IO读取

    • 相关章节: ch12 (页缓存)
  3. 写时复制: 写操作缺页do_wp_page()检查mapcount复制或直接写

    • 相关章节: ch02 (folio结构), ch07 (页面分配)
  4. 栈扩展: 栈访问缺页expand_stack()修改VMA

    • 相关章节: ch06 (VMA管理)
  5. 权限检查: 缺页异常检查VMA权限mprotect验证允许或拒绝

    • 相关章节: ch06 (VMA权限), ch11 (mprotect)

下一章将介绍页面回收。

  • Title: Linux内核分析之内存管理-09
  • Author: 韩乔落
  • Created at : 2026-01-20 21:39:27
  • Updated at : 2026-02-24 14:05:20
  • Link: https://jelasin.github.io/2026/01/20/Linux内核分析之内存管理-09/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments