第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)
#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
| #define USER_DS TASK_SIZE_64 #define TASK_SIZE_64 (1UL << vabits_actual)
#define PAGE_OFFSET (UL(0xffffffffffffffff) << VA_BITS) #define KERNEL_DS (-1UL)
#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; } ____cacheline_aligned_in_smp;
struct maple_tree mm_mt;
unsigned long mmap_base; unsigned long mmap_legacy_base;
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES unsigned long mmap_compat_base; unsigned long mmap_compat_legacy_base; #endif
unsigned long task_size; pgd_t *pgd;
#ifdef CONFIG_MEMBARRIER atomic_t membarrier_state; #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;
spinlock_t page_table_lock;
struct rw_semaphore mmap_lock;
struct list_head mmlist;
#ifdef CONFIG_PER_VMA_LOCK int mm_lock_seq; #endif
unsigned long hiwater_rss; unsigned long hiwater_vm;
unsigned long total_vm; unsigned long locked_vm; atomic64_t pinned_vm; unsigned long data_vm; unsigned long exec_vm; unsigned long stack_vm; 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;
unsigned long flags;
#ifdef CONFIG_MEMCG struct task_struct __rcu *owner; #endif
struct user_namespace *user_ns;
struct file __rcu *exe_file;
#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[]; };
|
4.2.2 mm_struct 引用计数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static inline void mmget(struct mm_struct *mm) { atomic_inc(&mm->mm_users); }
static inline void mmgrab(struct mm_struct *mm) { atomic_inc(&mm->mm_count); }
void mmput(struct mm_struct *mm);
void mmdrop(struct mm_struct *mm);
|
4.2.3 mm_struct 操作
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct mm_struct *mm_alloc(void);
void __mmput(struct mm_struct *mm);
struct mm_struct *copy_mm(unsigned long clone_flags, struct task_struct *tsk);
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 { u32 pasid; struct list_head sva_domains; };
|
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
| static inline void mm_pasid_init(struct mm_struct *mm) {
mm->iommu_mm = NULL; }
static inline bool mm_valid_pasid(struct mm_struct *mm) { return READ_ONCE(mm->iommu_mm); }
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; }
void mm_pasid_drop(struct mm_struct *mm);
#define IOMMU_NO_PASID (0U) #define IOMMU_FIRST_GLOBAL_PASID (1U) #define IOMMU_PASID_INVALID (-1U)
|
SVA (Shared Virtual Addressing) 操作:
1 2 3 4 5 6 7 8 9
| struct iommu_sva *iommu_sva_bind_device(struct device *dev, struct mm_struct *mm);
void iommu_sva_unbind_device(struct iommu_sva *handle);
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
| 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);
u32 pasid = iommu_sva_get_pasid(handle);
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); 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);
void *vmalloc_user(unsigned long size);
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
| struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);
struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm, unsigned long addr);
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
| 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
| unsigned long get_mm_rss(struct mm_struct *mm) __acquires(mmap_lock);
bool __range_in_vma(struct vm_area_struct *vma, unsigned long start, unsigned long end);
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 的虚拟地址空间:
- 地址空间布局: 用户空间 (128TB) + 内核空间 (128TB)
- 地址区域: 直接映射区、vmalloc 区、固定映射区、模块区
- mm_struct: 内存描述符,包含 VMA 树、页表、统计信息等
- 内核地址空间: 直接映射、vmalloc、固定映射、模块区域
- 地址空间操作: VMA 查找、mmap 锁、统计信息
- 内核页表: 内核页表初始化和操作
下一章将介绍页表管理。