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

韩乔落

第4章:虚拟地址空间

基于 Linux 6.12.38 源码


4.1 地址空间布局

4.1.1 x86_64 地址布局

Linux 6.12 支持 4 级或 5 级页表,默认使用 4 级页表 (PGD -> P4D -> PUD -> PMD -> PTE)。

48位虚拟地址布局:

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
┌─────────────────────────────────────────────────────────────────┐
│ 用户空间 (128TB) │
│ 0x0000000000000000 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ 代码段 │ │ 数据段 │ │ 栈段 (向下增长) │ │
│ │ (Text) │ │ (Data/BSS) │ │ │ │
│ └──────────────┘ └──────────────┘ └─────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 内存映射段 (mmap 区域, 向上生长) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 栈 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 内核空间 (128TB) │
│ 0xffff800000000000 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ 直接映射区 │ │ vmalloc区 │ │ 模块区 │ │
│ │ (Phys Map) │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ PAGE_OFFSET │ │ VMALLOC_START│ │ MODULES_VADDR │ │
│ │ │ │ ~ │ │ ~ │ │
│ │ │ │ VMALLOC_END │ │ MODULES_END │ │
│ └──────────────┘ └──────────────┘ └─────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 固定映射区 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ KASAN 区域 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

4.1.2 地址定义

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define PAGE_OFFSET    ((unsigned long)__PAGE_OFFSET)

/* 用户空间地址范围 */
#define TASK_SIZE_MAX ((1UL << __VIRTUAL_MASK_SHIFT) - PAGE_SIZE)
#define DEFAULT_MAP_WINDOW ((1UL << 47) - PAGE_SIZE)

/* 内核空间地址范围 */
#define __PAGE_OFFSET_BASE_L4 _AC(0xffff880000000000, UL)
#define __PAGE_OFFSET_BASE_L5 _AC(0xff11000000000000, UL)

#define __PAGE_OFFSET PAGE_OFFSET
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)

/* vmalloc 区域 */
#define VMALLOC_START _AC(0xffffc90000000000, UL)
#define VMALLOC_END _AC(0xffffe8ffffffffff, UL)
#define VMEMMAP_START _AC(0xffffea0000000000, UL)
#define VMEMMAP_END _AC(0xffffebffffffffff, UL)

/* 模块区域 */
#define MODULES_VADDR _AC(0xffffffffa0000000, UL)
#define MODULES_END _AC(0xffffffffff000000, UL)

4.1.3 ARM64 地址布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 用户空间 (TTBR0_EL1) */
#define USER_DS TASK_SIZE_64
#define TASK_SIZE_64 (1UL << vabits_actual)

/* 内核空间 (TTBR1_EL1) */
#define PAGE_OFFSET (UL(0xffffffffffffffff) << VA_BITS)
#define KERNEL_DS (-1UL)

/* vmalloc 区域 */
#define VMALLOC_START (MODULES_END)
#define VMALLOC_END (PAGE_OFFSET - KASAN_SHADOW_SIZE)
#define VMEMMAP_START (PAGE_OFFSET - KASAN_SHADOW_SIZE - VMEMMAP_SIZE)
#define VMEMMAP_END (PAGE_OFFSET - KASAN_SHADOW_SIZE)

/* 模块区域 */
#define MODULES_VADDR (VA_START + KASAN_SHADOW_SIZE)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)

4.2 内存描述符 mm_struct

4.2.1 mm_struct 结构

位置: include/linux/mm_types.h:820

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
struct mm_struct {
struct {
/* 写入频繁的字段放在单独的缓存行 */
struct {
atomic_t mm_count; /* &struct mm_struct 的引用计数 */
} ____cacheline_aligned_in_smp;

struct maple_tree mm_mt; /* VMA 的 maple tree (Linux 5.13+) */

unsigned long mmap_base; /* mmap 区域基址 */
unsigned long mmap_legacy_base; /* 向上分配的 mmap 基址 */

#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
/* 兼容 mmap() 的基址 */
unsigned long mmap_compat_base;
unsigned long mmap_compat_legacy_base;
#endif

unsigned long task_size; /* 任务 vm 空间大小 */
pgd_t *pgd; /* 页全局目录 */

#ifdef CONFIG_MEMBARRIER
atomic_t membarrier_state; /* membarrier 状态 */
#endif

atomic_t mm_users; /* 包括用户空间在内的用户数量 */

#ifdef CONFIG_SCHED_MM_CID
struct mm_cid __percpu *pcpu_cid;
unsigned long mm_cid_next_scan;
#endif

#ifdef CONFIG_MMU
atomic_long_t pgtables_bytes; /* 所有页表的大小 */
#endif

int map_count; /* VMA 数量 */

spinlock_t page_table_lock; /* 保护页表和一些计数器 */

struct rw_semaphore mmap_lock; /* mmap 读写信号量 */

struct list_head mmlist; /* 可能被交换的 mm 列表 */

#ifdef CONFIG_PER_VMA_LOCK
int mm_lock_seq; /* VMA 锁序列号 */
#endif

unsigned long hiwater_rss; /* RSS 使用高水位 */
unsigned long hiwater_vm; /* 虚拟内存使用高水位 */

unsigned long total_vm; /* 映射的总页面数 */
unsigned long locked_vm; /* 设置了 PG_mlocked 的页面 */
atomic64_t pinned_vm; /* 永久增加的引用计数 */
unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK */
unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK */
unsigned long stack_vm; /* VM_STACK */
unsigned long def_flags;

seqcount_t write_protect_seq; /* 写保护序列号 */

spinlock_t arg_lock; /* 保护以下字段 */

unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;

unsigned long saved_auxv[AT_VECTOR_SIZE];

struct percpu_counter rss_stat[NR_MM_COUNTERS];

struct linux_binfmt *binfmt;

mm_context_t context; /* 架构特定的 MM 上下文 */

unsigned long flags; /* 必须使用原子位操作访问 */

#ifdef CONFIG_MEMCG
struct task_struct __rcu *owner; /* "owner" 任务 */
#endif

struct user_namespace *user_ns;

struct file __rcu *exe_file; /* /proc/PID/exe 符号链接 */

#ifdef CONFIG_MMU_NOTIFIER
struct mmu_notifier_subscriptions *notifier_subscriptions;
#endif

#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !defined(CONFIG_SPLIT_PMD_PTLOCKS)
pgtable_t pmd_huge_pte;
#endif

#ifdef CONFIG_NUMA_BALANCING
unsigned long numa_next_scan;
unsigned long numa_scan_offset;
int numa_scan_seq;
#endif

atomic_t tlb_flush_pending;

#ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
atomic_t tlb_flush_batched;
#endif

struct uprobes_state uprobes_state;

#ifdef CONFIG_PREEMPT_RT
struct rcu_head delayed_drop;
#endif

#ifdef CONFIG_HUGETLB_PAGE
atomic_long_t hugetlb_usage;
#endif

struct work_struct async_put_work;

#ifdef CONFIG_IOMMU_MM_DATA
struct iommu_mm_data *iommu_mm;
#endif

#ifdef CONFIG_KSM
unsigned long ksm_merging_pages;
unsigned long ksm_rmap_items;
atomic_long_t ksm_zero_pages;
#endif

#ifdef CONFIG_LRU_GEN_WALKS_MMU
struct {
struct list_head list;
unsigned long bitmap;
#ifdef CONFIG_MEMCG
struct mem_cgroup *memcg;
#endif
} lru_gen;
#endif
} __randomize_layout;

unsigned long cpu_bitmap[]; /* CPU 位图 */
};

4.2.2 mm_struct 引用计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 增加 mm_users 引用 */
static inline void mmget(struct mm_struct *mm)
{
atomic_inc(&mm->mm_users);
}

/* 增加 mm_count 引用 */
static inline void mmgrab(struct mm_struct *mm)
{
atomic_inc(&mm->mm_count);
}

/* 减少 mm_users 引用 */
void mmput(struct mm_struct *mm);

/* 减少 mm_count 引用 */
void mmdrop(struct mm_struct *mm);

4.2.3 mm_struct 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 分配新的 mm_struct */
struct mm_struct *mm_alloc(void);

/* 初始化 mm_struct */
void __mmput(struct mm_struct *mm);

/* 复制 mm_struct */
struct mm_struct *copy_mm(unsigned long clone_flags,
struct task_struct *tsk);

/* 活跃 mm */
void mmgrab(struct mm_struct *mm);
void mmdrop(struct mm_struct *mm);

4.2.4 mm_struct 与 IOMMU 集成

iommu_mm_data 结构:

位置: include/linux/iommu.h:1017

1
2
3
4
5
6
7
8
9
/**
* struct iommu_mm_data - mm_struct 与 IOMMU 的集成数据
* @pasid: 分配给此 mm 的 PASID (Process Address Space ID)
* @sva_domains: 使用此 mm 的 SVA (Shared Virtual Addressing) 域列表
*/
struct iommu_mm_data {
u32 pasid; /* 分配给此 mm 的 PASID */
struct list_head sva_domains; /* SVA 域列表 */
};

PASID (Process Address Space ID) 概述:

PASID 是 IOMMU 用于区分不同进程地址空间的标识符,允许单个设备同时访问多个进程的虚拟地址空间。

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────────────────────────────────┐
│ PASID 作用示意 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 进程 A (PASID=1) ──┐ │
│ │ │
│ 进程 B (PASID=2) ──┼──→ 设备 ──→ IOMMU ──→ 根据事务的 PASID │
│ │ 选择对应的页表进行地址转换 │
│ 进程 C (PASID=3) ──┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

mm_struct PASID 操作:

位置: include/linux/iommu.h:1500-1556

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
/* 初始化 PASID */
static inline void mm_pasid_init(struct mm_struct *mm)
{
/*
* 在 dup_mm() 期间,新的 mm 会从旧 mm memcpy,这会导致
* 新的 mm 和旧 mm 指向同一个 iommu_mm 实例。当其中一个
* mm 释放时,iommu_mm 实例被释放,导致另一个 mm 出现
* use-after-free/double-free 问题。
* 因此需要将新 mm 的 iommu_mm 指针清零。
*/
mm->iommu_mm = NULL;
}

/* 检查 PASID 是否有效 */
static inline bool mm_valid_pasid(struct mm_struct *mm)
{
return READ_ONCE(mm->iommu_mm);
}

/* 获取 ENQCMD PASID (Intel ENQCMD 指令支持) */
static inline u32 mm_get_enqcmd_pasid(struct mm_struct *mm)
{
struct iommu_mm_data *iommu_mm = READ_ONCE(mm->iommu_mm);

if (!iommu_mm)
return IOMMU_PASID_INVALID;
return iommu_mm->pasid;
}

/* 释放 PASID */
void mm_pasid_drop(struct mm_struct *mm);

/* PASID 定义 */
#define IOMMU_NO_PASID (0U) /* 无 PASID (传统 DMA) */
#define IOMMU_FIRST_GLOBAL_PASID (1U) /* 全局 PASID 起始范围 */
#define IOMMU_PASID_INVALID (-1U) /* 无效 PASID */

SVA (Shared Virtual Addressing) 操作:

1
2
3
4
5
6
7
8
9
/* 绑定设备到进程地址空间 (启用 SVA) */
struct iommu_sva *iommu_sva_bind_device(struct device *dev,
struct mm_struct *mm);

/* 解绑设备 */
void iommu_sva_unbind_device(struct iommu_sva *handle);

/* 获取 PASID */
u32 iommu_sva_get_pasid(struct iommu_sva *handle);

SVA 使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 设备驱动中使用 SVA */
struct device *dev = /* 设备指针 */;
struct mm_struct *mm = current->mm;

/* 绑定设备到当前进程 */
struct iommu_sva *handle = iommu_sva_bind_device(dev, mm);
if (IS_ERR(handle))
return PTR_ERR(handle);

/* 获取 PASID */
u32 pasid = iommu_sva_get_pasid(handle);

/* 设备现在可以直接使用进程虚拟地址进行 DMA */
/* 设备驱动将 PASID 和虚拟地址传递给硬件 */

/* 完成后解绑 */
iommu_sva_unbind_device(handle);

IOMMU MM 数据生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────┐
│ iommu_mm_data 生命周期 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ fork() │
│ │ │
│ ├── dup_mm() │
│ │ │ │
│ │ └── mm_pasid_init(mm) ──→ mm->iommu_mm = NULL │
│ │ │
│ ├── 第一个 SVA 绑定 ──→ 分配 PASID ──→ mm->iommu_mm 创建 │
│ │ │
│ └── 后续 SVA 绑定 ──→ 使用已有 PASID │
│ │
│ exec() ──→ mm_pasid_drop() ──→ 释放旧 mm 的 PASID │
│ │
│ exit() ──→ mm_pasid_drop() ──→ 释放 PASID │
│ │
└─────────────────────────────────────────────────────────────────┘

4.3 内核地址空间

4.3.1 直接映射区

直接映射区将物理内存线性映射到内核空间。

1
2
3
4
5
6
7
/* 物理内存线性映射到内核空间 */
#define __va(x) ((void *)((unsigned long)(x) + PAGE_OFFSET))
#define __pa(x) ((unsigned long)(x) - PAGE_OFFSET)

/* 示例 */
void *virt_addr = __va(0x1000000); /* 物理地址 16MB → 虚拟地址 */
unsigned long phys_addr = __pa(virt_addr); /* 虚拟地址 → 物理地址 */

直接映射区特点:

  • 物理内存一对一映射到虚拟内存
  • 访问速度快,无需页表转换
  • 适合访问大部分物理内存

4.3.2 vmalloc 区

vmalloc 区用于分配非连续的物理内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 分配非连续内存 */
void *vmalloc(unsigned long size);
void vfree(void *addr);

/* 分配并清零 */
void *vzalloc(unsigned long size);

/* 分配并初始化为 0 */
void *vmalloc_user(unsigned long size);

/* 映射物理页到 vmalloc 区 */
void *vmap(struct page **pages, unsigned int count,
unsigned long flags, pgprot_t prot);
void vunmap(void *addr);

/* 内核映射页数组 */
void *vm_map_ram(struct page **pages, unsigned int count,
int node, pgprot_t prot);
void vm_unmap_ram(const void *mem, unsigned int count);

vmalloc 使用场景:

  • 需要大块连续虚拟内存
  • 物理内存可以不连续
  • 模块加载

4.3.3 固定映射区

固定映射区在编译时固定但虚拟地址可配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 编译时固定但虚拟地址可配置 */
enum fixed_addresses {
FIX_HOLE,
FIX_VSYSCALL,
FIX_TEXT_POKE0,
FIX_TEXT_POKE1,
__end_of_fixed_addresses
};

extern void *__fix_to_virt(enum fixed_addresses idx);
#define FIX_ADDR(idx) __fix_to_virt(idx)

/* 设置固定映射 */
void __set_fixmap(enum fixed_addresses idx,
phys_addr_t phys, pgprot_t flags);

固定映射用途:

  • 临时映射单个页面
  • 早期内核初始化
  • 特定硬件访问

4.3.4 模块区域

模块区域用于加载内核模块。

1
2
3
4
5
6
7
8
/* 模块区域 */
#define MODULES_VADDR _AC(0xffffffffa0000000, UL)
#define MODULES_END _AC(0xffffffffff000000, UL)
#define MODULES_RANGE (MODULES_END - MODULES_VADDR)

/* 模块分配 */
void *module_alloc(unsigned long size);
void module_memfree(void *module_region);

4.4 地址空间操作

4.4.1 查找 VMA

1
2
3
4
5
6
7
8
9
10
11
/* 查找包含地址的 VMA */
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);

/* 查找并锁定 VMA */
struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm,
unsigned long addr);

/* 查找最近的 VMA */
struct vm_area_struct *find_vma_prev(struct mm_struct *mm,
unsigned long addr,
struct vm_area_struct **pprev);

4.4.2 mmap 锁操作

1
2
3
4
5
6
7
8
9
10
11
12
/* 降级 mmap 锁 */
void mmap_read_lock(struct mm_struct *mm);
void mmap_read_unlock(struct mm_struct *mm);
void mmap_write_lock(struct mm_struct *mm);
void mmap_write_unlock(struct mm_struct *mm);

/* 信号量锁 */
int mmap_read_trylock(struct mm_struct *mm);
int mmap_write_trylock(struct mm_struct *mm);

/* 降级写锁为读锁 */
void mmap_write_downgrade(struct mm_struct *mm);

4.4.3 地址空间统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 获取 RSS 使用 */
unsigned long get_mm_rss(struct mm_struct *mm) __acquires(mmap_lock);

/* 检查地址是否在 mm 范围内 */
bool __range_in_vma(struct vm_area_struct *vma,
unsigned long start, unsigned long end);

/* 获取 mm 使用统计 */
void get_mm_counters(struct mm_struct *mm,
unsigned long *rss,
unsigned long *shared,
unsigned long *text,
unsigned long *data,
unsigned long *dt);

4.5 内核页表

4.5.1 内核页表初始化

1
2
3
4
5
6
7
8
/* 创建内核页表 */
void __init paging_init(void);

/* 映射内核文本 */
void __init kernel_ident_mapping_init(void);

/* 设置早期页表 */
void __init __early_pte_alloc(void);

4.5.2 内核页表操作

1
2
3
4
5
6
/* 设置内核页表 */
void set_pte_at(struct mm_struct *mm, unsigned long addr,
pte_t *ptep, pte_t pte);

/* 清除页表项 */
void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep);

4.6 本章小结

本章介绍了 Linux 6.12 的虚拟地址空间:

  1. 地址空间布局: 用户空间 (128TB) + 内核空间 (128TB)
  2. 地址区域: 直接映射区、vmalloc 区、固定映射区、模块区
  3. mm_struct: 内存描述符,包含 VMA 树、页表、统计信息等
  4. 内核地址空间: 直接映射、vmalloc、固定映射、模块区域
  5. 地址空间操作: VMA 查找、mmap 锁、统计信息
  6. 内核页表: 内核页表初始化和操作

下一章将介绍页表管理。

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