Linux 程序与进程地址空间
1-1 研究平台与分析工具
平台说明
本文基于 x86_64 架构 Linux 操作系统 展开,同时对比32位保护模式差异:
- 32位系统:虚拟地址总线32位,总寻址空间 2^32=4GB
- 64位系统:仅使用低48位有效地址(规范地址Canonical Address),高16位为符号扩展,总寻址空间 2^48=256TB,用户空间与内核空间各占128TB
必备分析工具
命令 作用 size a.out快速查看ELF文件中 text、data、bss 段的大小 readelf -S a.out查看ELF完整节头表,输出所有段的地址、大小、权限 objdump -d a.out反汇编代码段,查看机器指令与虚拟地址 cat /proc/[pid]/maps查看运行中进程的完整虚拟地址空间布局 pmap -x [pid]可视化进程内存映射,统计RSS、PSS物理内存占用 cat /proc/[pid]/status查看VmSize、VmRSS、VmPeak等关键内存指标
1-2 程序地址空间回顾(磁盘ELF文件)
程序地址空间是静态概念,指磁盘上ELF(Executable and Linkable Format)可执行文件的分段结构,程序尚未运行,不占用物理内存。
ELF文件整体结构
ELF文件由三部分组成:
- ELF头部:记录文件类型、架构、入口地址、程序头表/节头表的偏移与大小
- 程序头表(Program Header Table):加载视图,操作系统加载程序时使用,描述哪些段需要映射到内存
- 节头表(Section Header Table):链接视图,编译链接时使用,描述所有节的信息
核心段详解
段名 权限 内容 磁盘占用 .text只读可执行 编译后的机器指令、所有函数代码 占用 .rodata只读 字符串常量、const修饰的全局常量、跳转表 占用 .data可读写 已经初始化****的全局变量、静态局部变量 占用 .bss可读写 未初始化****的全局变量、静态局部变量 不占用 .symtab/.strtab调试用 符号表、字符串表,strip后可移除 占用 .bss段的本质:
全称Block Started by Symbol,它在磁盘上只记录大小,不存储实际数据。程序加载时,内核会在内存中分配对应大小的空间,并全部初始化为0,因此未初始化全局变量默认值为0。
示例验证
const char* s = "hello world"; // 字符串"hello world"存.rodata;指针s存.data int g_init = 100; // .data 已初始化全局变量 int g_uninit; // .bss 未初始化全局变量 int main() { static int s_init = 10; // .data static int s_uninit; // .bss int local = 1; // 运行时存栈,不属于ELF段 return 0; }编译后执行
size a.out可直接看到 text、data、bss 三段的字节数。
1-3 虚拟地址
核心定义
虚拟地址(Virtual Address, VA) 是进程内部使用的地址编号,不是真实的物理内存地址。
- 应用层C语言指针存储的全部是虚拟地址,程序员完全看不到物理地址
- CPU访问内存时,由硬件MMU(内存管理单元)自动完成虚拟地址→物理地址的翻译
- 每个进程拥有完全独立的虚拟地址空间,相同虚拟地址在不同进程中可映射到不同物理内存
地址概念辨析(x86架构)
很多教材混淆逻辑地址、线性地址、物理地址,在Linux下关系如下:
- 逻辑地址:段选择子 + 段内偏移,x86分段机制产物
- 线性地址(虚拟地址):段基址 + 段内偏移
- 物理地址:内存芯片上的真实地址,通过分页机制转换得到
Linux弱化了分段机制,所有段的基址都设为0,
因此逻辑地址 = 线性地址 = 虚拟地址,我们通常说的虚拟地址就是线性地址。
32位 vs 64位地址空间划分
地址空间 32位Linux 64位Linux 用户空间 0x00000000 ~ 0xBFFFFFFF(3GB) 0x0000000000000000 ~ 0x00007FFFFFFFFFFF(128TB) 内核空间 0xC0000000 ~ 0xFFFFFFFF(1GB) 0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF(128TB) 中间空洞 无 用户空间与内核空间之间存在巨大非法地址空洞 64位系统中指针大小为8字节,但只有低48位有效,访问非规范地址会触发缺页异常。
1-4 进程地址空间
进程地址空间是动态概念:
程序加载运行后,操作系统为进程抽象出的完整虚拟内存布局,由内核
struct mm_struct结构体描述,每个进程独立一份。mm_struct:
内存描述符,被 task_struct 指向,完整描述一个进程的全部虚拟地址空间布局,每个进程独立拥有一份。
进程地址空间从低地址到高地址的完整布局如下:
1. 代码段(text segment)
- 权限:
r-x只读可执行- 内容:映射ELF文件的.text段,存放机器指令
- 特性:同一份程序启动多个进程,代码段物理内存共享,节省系统内存
2. 只读数据段(rodata segment)
- 权限:
r--只读- 内容:字符串常量、const全局变量
- 特性:写入该区域会触发段错误(SIGSEGV)
3. 数据段(data segment)
- 权限:
rw-可读写- 内容:已初始化的全局变量、静态变量
- 特性:进程私有,fork后采用写时复制机制
4. BSS段
- 权限:
rw-可读写- 内容:未初始化全局变量、静态变量
- 特性:加载时由内核清零,不占用磁盘空间
5. 堆(Heap)
- **位置:紧邻BSS段之后,**向高地址方向增长
- 管理:通过
brk / sbrk系统调用移动program break指针扩展- **用途:
malloc / new动态分配内存,**小内存(默认<128KB)从堆分配- 特点:分配速度快,容易产生内存碎片
6. 文件映射区(mmap区)
- 位置:堆与栈之间,可动态调整
- 用途:
- 动态库(.so)加载:共享映射,所有进程共用同一份物理代码页
- 大内存分配:malloc分配大内存时采用匿名映射
- 文件映射:将磁盘文件直接映射到内存
- 共享内存:进程间通信
- 分类:共享映射、私有映射(写时复制)
7. 栈(Stack)
- **位置:用户空间最高地址处,**向低地址方向增长
- 管理:由编译器自动分配释放,栈顶由rsp寄存器指向
- 内容:函数栈帧、局部变量、函数参数、返回地址、保存的寄存器
- 大小:默认8MB,可通过
ulimit -s查看和修改- 栈顶上方:存放命令行参数argv、环境变量env表
8. 内核空间
- 位置:虚拟地址最高处
- 访问权限:用户态无法直接访问,只能通过系统调用、中断进入内核态
- 特性:所有进程共享同一份内核地址空间,内核页表全局唯一
堆 vs 栈 核心对比
维度 堆 栈 增长方向 向高地址增长 向低地址增长 管理方式 程序员手动分配释放 编译器自动管理 大小 可动态扩展,理论很大 固定大小,默认8MB 分配效率 较慢,存在碎片 极快,仅移动栈指针 生命周期 全程有效,直到free 函数返回自动释放 实战:查看真实进程地址空间
执行
cat /proc/self/maps可查看当前进程的完整映射,每一行格式为:
地址范围 权限 偏移 主设备号:次设备号 inode 路径名
1-5 虚拟内存管理(第一讲)
这张图完整呈现了 Linux 内核进程虚拟地址空间的管理架构,从进程描述符
task_struct到内存描述符mm_struct,再到逐个虚拟内存区域vm_area_struct(VMA)的层级组织关系,是理解 Linux 内存管理内核实现的核心结构图。
一、顶层:task_struct 进程描述符
位于最左侧,是 Linux 内核中每个进程的核心管理结构体(即进程控制块 PCB),图中标出了三个关键字段:
pid_t pid:进程 ID,进程在内核中的唯一标识struct files_struct *files:文件描述符表指针,管理进程打开的所有文件资源struct mm_struct *mm:内存描述符指针,指向该进程专属的内存管理结构mm_struct每个进程有且仅有一个
task_struct,对应一个独立的mm_struct;而同一进程内的线程会共享同一个mm_struct,这是进程与线程在内核层面的核心区别之一。
二、中层:mm_struct 内存描述符
cppstruct mm_struct { /*...*/ struct vm_area_struct *mmap; /* 指向虚拟区间(VMA)链表 */ struct rb_root mm_rb; /* red_black树 */ unsigned long task_size; /*具有该结构体的进程的虚拟地址空间的⼤⼩*/ /*...*/ // 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。 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; /*...*/ }是整个进程地址空间的"总管",完整描述一个进程的全部虚拟地址空间信息。
- 图中核心字段:
struct vm_area_struct *mmap,指向虚拟内存区域(VMA)双向链表的首个节点,所有 VMA 按虚拟地址从低到高排序串联。- 完整的
mm_struct还包含页全局目录基址(pgd)、代码段/数据段/栈的起止边界、内存占用统计、VMA 红黑树根节点等信息,图中做了简化展示。fork 创建子进程时,内核会复制一份 mm_struct 给子进程,同时复制 VMA 链表,并将页表标记为只读,配合写时复制(COW)机制实现高效的内存隔离。
三、底层:vm_area_struct(VMA)虚拟内存区域
cppstruct vm_area_struct { unsigned long vm_start; //虚存区起始 unsigned long vm_end; //虚存区结束 struct vm_area_struct *vm_next, *vm_prev; //前后指针 struct rb_node vm_rb; //红⿊树中的位置 unsigned long rb_subtree_gap; struct mm_struct *vm_mm; //所属的 mm_struct pgprot_t vm_page_prot; unsigned long vm_flags; //标志位 struct { struct rb_node rb; unsigned long rb_subtree_last; } shared; struct list_head anon_vma_chain; struct anon_vma *anon_vma; const struct vm_operations_struct *vm_ops; //vma对应的实际操作 unsigned long vm_pgoff; //⽂件映射偏移量 struct file * vm_file; //映射的⽂件 void * vm_private_data; //私有数据 atomic_long_t swap_readahead_info; #ifndef CONFIG_MMU struct vm_region *vm_region; /* NOMMU mapping region */ #endif #ifdef CONFIG_NUMA struct mempolicy *vm_policy; /* NUMA policy for the VMA */ #endif struct vm_userfaultfd_ctx vm_userfaultfd_ctx; } __randomize_layout;是虚拟地址空间的最小管理单元,每一段连续、访问属性相同的虚拟地址区间,对应一个 VMA 结构体。内核不会逐个字节管理地址空间,而是按区域批量管理,大幅降低了管理开销。
每个 VMA 包含的核心信息(图中标注):
- 地址范围
vm_start:该区域的起始虚拟地址vm_end:该区域的结束虚拟地址- 区间遵循左闭右开原则,
vm_end - vm_start就是该区域的总大小- 链表指针
vm_prev/vm_next:所有 VMA 以双向链表形式按地址排序串联,便于顺序遍历- 内核同时还维护红黑树(
mm_rb)组织 VMA,用于快速查找地址所属区域,图中未画出- 回指指针
vm_mm:反向指向所属的mm_struct,便于从 VMA 快速定位到进程的内存描述符- 权限标志
VM_READ:内存可读VM_WRITE:内存可写VM_EXEC:内存可执行- 不同区域的权限组合不同,对应代码段、数据段等不同功能属性
四、右侧:六大虚拟内存区域(VMA 对应实体)
从低地址到高地址,每个 VMA 对应地址空间中的一段,图中清晰标注了各自的权限和增长方向:
区域 权限标志 核心作用 增长方向 代码段 `VM_READ VM_EXEC` 存放编译后的机器指令,只读可执行,禁止写入 数据段 `VM_READ VM_WRITE` 存放已初始化的全局变量、静态局部变量,可读写 BSS 段 `VM_READ VM_WRITE` 存放未初始化的全局变量,程序加载时由内核清零 堆 `VM_READ VM_WRITE VM_EXEC` 文件映射与匿名映射区 `VM_READ VM_WRITE VM_EXEC` 栈 `VM_READ VM_WRITE` 函数栈帧、局部变量、函数参数与返回地址,编译器自动管理 最上方的内核空间独立于用户地址空间,用户态进程无法直接访问,系统中所有进程共享同一份内核地址空间。
五、设计意义与高频面试考点
为什么用 VMA 管理地址空间,而不是直接逐页管理?
进程地址空间中存在大量连续、属性一致的内存区间,用 VMA 按区域管理,相比逐页管理可以大幅减少管理结构体的数量,降低内存开销和查找复杂度。例如一个 10MB 的连续堆区,只需要 1 个 VMA 就能描述,不需要上千个页表项级别的管理结构。
缺页异常中 VMA 的作用是什么?
当进程访问虚拟地址触发缺页时,内核首先通过 VMA 链表/红黑树判断地址合法性:
- 不属于任何 VMA:非法地址,向进程发送 SIGSEGV 段错误信号
- 属于合法 VMA,但访问权限不匹配:同样触发段错误
- 属于合法 VMA 且权限合法:再继续执行物理页分配、文件加载、写时复制等后续处理
malloc / mmap 和 VMA 是什么关系?
- 小块内存的 malloc 从堆区分配,本质是扩展堆 VMA 的
vm_end边界- 大块内存的 malloc 走 mmap 匿名映射,本质是新建一个匿名 VMA
- 释放内存时,对应缩小或销毁对应的 VMA
进程和线程在地址空间结构上的本质区别?
进程拥有独立的
task_struct+ 独立的mm_struct+ 独立的 VMA 集合 + 独立页表; 同一进程下的多个线程,各自有独立的task_struct,但共享同一个mm_struct、所有 VMA 和页表,这就是线程"共享地址空间"的内核本质。
VMA 同时用链表和红黑树组织,分别有什么作用?
双向链表适合顺序遍历场景,比如进程退出时批量回收内存、加载动态库;
红黑树适合快速查找指定地址所属的 VMA,时间复杂度 O(log n),是缺页异常、地址合法性检查的核心数据结构。
虚拟内存的核心是分页机制 + MMU硬件 + 页表,三者配合完成地址翻译与内存管理。
分页机制
操作系统把物理内存和虚拟地址空间都切割成固定大小的页,Linux默认页大小为4KB,此外还支持2MB、1GB大页。
- 虚拟页(Page):虚拟地址空间的最小单位
- 物理页(Page Frame):物理内存的最小单位
- 页表:记录虚拟页→物理页的映射关系,以及权限、状态位
1. 基础概念
- 页(Page) :虚拟内存切出来的块,叫虚拟页,大小 4KB;
- 页框(Page Frame):物理内存切出来的块,叫物理页框,大小和虚拟页完全一样 4KB;
- 页表 :一张映射表,记录:
虚拟页号 → 物理页框号。CPU 里的MMU 内存管理单元,专门干地址翻译: 程序给的是虚拟地址,MMU 拆分地址:
- 高位:虚拟页号,去查页表,拿到物理页框号
- 低位:页内偏移,不用转换,直接拼到物理页框号后面,得到最终物理地址。
页内偏移:一页 4KB=4096 字节,需要 12bit,这 12 位在虚拟、物理地址完全不变。
举个简单例子
- 页大小:4KB
- 虚拟地址:
0x12345678- 高 20 位 = 虚拟页号,查页表得到物理页框号;低 12 位不变,拼接得到物理地址。
2. 为什么需要分页?(核心好处)
- **进程拥有独立虚拟地址空间****每个进程有自己独立页表。**不同进程可以有相同的虚拟地址,映射到不同物理内存,实现进程隔离。
- 内存离散使用,不用连续物理内存****进程的一大块虚拟内存,可以映射到物理内存里不连续的物理页框。 没有分页的话,程序需要一大片连续物理内存,很容易外部碎片。分页把大内存拆小,碎片问题大幅缓解。
- 按需分配(缺页) **创建进程、malloc 的时候,不会立刻分配物理内存,只修改 VMA、填页表项标记未分配。 程序第一次访问这个虚拟地址,触发缺页异常 Page Fault,内核才分配物理页框,建立映射。**延迟分配,节省物理内存。
- 内存保护****页表项自带权限位:可读、可写、可执行。 写代码段(只读页)、访问没有映射的虚拟页 → MMU 报错,内核发 SIGSEGV 段错误。
- Swap 交换****物理内存不够时,可以把暂时不用的物理页写到磁盘 swap 分区;需要的时候再换回来。 分页机制让内核可以把部分页 "移出物理内存"。
3. 和分段的区别(容易混淆)
- 分段 Segment:按程序逻辑划分(代码段、数据段、堆、栈),段大小不固定。对应内核的
vm_area_struct(VMA)。- 分页 Page:硬件 MMU 机制,固定大小切割内存,纯粹内存管理。
Linux:分段逻辑由软件 VMA 实现,硬件层面只用分页。 VMA(段逻辑)描述一块逻辑区域;底层 MMU 分页做地址翻译。
4. 两种缺页
- 主缺页(Major Page Fault) 页面不在物理内存,要读磁盘(swap 回写 / 读取 elf 文件),慢。
- 次缺页(Minor Page Fault) 页还在物理内存,只是页表没建立映射,不用访问磁盘,速度快。
fork 写时复制 COW 触发的就是次缺页。
5. 面试高频题
Q1:分页的作用?
- 实现虚拟地址到物理地址转换;
- 进程地址空间隔离,每个进程独立页表;
- 物理内存可以离散分配,减少外部碎片;
- 支持缺页异常,物理内存按需分配;
- 页表权限实现内存保护;
- 支持 swap 内存换出。
Q2:页内偏移为什么不用转换?
虚拟页和物理页框大小完全相同,页内的偏移位置不变,直接拼接即可。
Q3:VMA 和分页是什么关系?
- VMA(
vm_area_struct):内核软件,管理逻辑分段(代码段、堆、栈),描述一段连续虚拟地址;- 分页:CPU 硬件 MMU,按页粒度做虚拟→物理地址映射。
一个 VMA 会包含很多个页面。访问 VMA 范围内的虚拟地址,MMU 通过页表映射到物理页。
Q4:为什么 fork 开销小?
fork 只拷贝 mm_struct、VMA、页表项,不拷贝物理页框。页表全部指向父进程物理页,标记只读;写入触发分页的缺页异常,才复制物理页,也就是写时复制 COW。
MMU与TLB
- MMU(内存管理单元):CPU内置硬件,负责虚拟地址到物理地址的翻译,以及权限检查
- TLB(Translation Lookaside Buffer,快表):MMU内部的高速缓存,缓存最近使用的页表项
- TLB命中:直接得到物理地址,无需访问内存页表
- TLB缺失:需要遍历内存中的页表,性能开销大
- 大页可以显著减少TLB miss,提升内存访问性能
多级页表
为什么不使用一级页表?
因为虚拟地址空间太大,一级页表会占用巨大内存。
Linux采用多级页表,64位系统为四级页表:
PGD(全局页目录)→ PUD(上级页目录)→ PMD(中间页目录)→ PTE(页表项)
- 优势:仅为已使用的虚拟地址建立页表项,大部分空洞地址不需要页表,极大节省页表本身的内存开销
- 每个进程拥有独立的页表,进程切换时切换CR3寄存器(指向PGD基址),完成地址空间切换
缺页异常(Page Fault)
当进程访问的虚拟地址没有建立物理映射,或权限不匹配时,CPU会触发缺页中断,由内核处理。分为三类:
- 合法缺页(按需分配)
- 场景:malloc后第一次访问内存、访问尚未加载的文件映射页
- 处理:内核分配物理页,建立页表映射,进程继续执行
- 这就是"malloc申请内存不会立刻占用物理内存"的根本原因
- 写时复制缺页(COW, Copy On Write)
- 场景:fork之后,父子进程共享所有物理页,页表设为只读;当其中一方写入时触发
- 处理:内核复制一份新的物理页,修改页表为可写,父子进程各自拥有私有副本
- 价值:大幅提升fork效率,节省物理内存
- 非法缺页
- 场景:访问空指针、访问已释放内存、写只读代码段、越界访问
- 处理:内核向进程发送SIGSEGV信号,进程崩溃,即"段错误"
Swap交换机制
**物理内存不足时,内核可将不常访问的脏页写入磁盘swap分区,释放物理内存;**后续访问时再从磁盘换入。页表项会标记页面所在的swap位置,访问时触发缺页换入。
1-6 为什么要有虚拟地址空间
虚拟地址不是"多此一举",而是操作系统内存管理的基石,核心价值有五点:
1. 进程隔离与安全
每个进程拥有独立虚拟地址空间,进程无法直接访问其他进程的物理内存。一个进程的内存越界或崩溃,不会影响其他进程和系统内核,从硬件层面保障了稳定性与安全性。
2. 提升物理内存利用率
通过写时复制、共享库、按需分配、Swap交换等机制,让有限的物理内存承载更多进程。比如100个进程运行同一个bash,代码段物理内存只需要一份。
3. 简化程序加载与编译
程序编译链接时使用固定的虚拟地址布局(如64位程序代码段从0x400000开始),不需要关心物理内存实际位置。加载器只需要按页映射,无需重定位,大大简化了程序加载与链接。
4. 细粒度内存保护
页表项为每一页设置独立权限**:代码段只读可执行、数据段可读写、栈不可执行(NX位)。可以防止恶意代码通过栈溢出注入执行,提升系统安全性。**
5. 内存虚拟化
每个进程都认为自己拥有完整的地址空间,不需要关心物理内存大小。虚拟地址空间可以远大于物理内存容量,为大型程序运行提供了可能。
🚩 高频面试题汇总
虚拟地址转物理地址完整过程?
1.MMU 拆分虚拟地址,分为多级页索引 + 页内偏移;
2.CR3 寄存器获取页目录基地址;
3. 逐级遍历多级页表,得到页表项 PTE;
4. 如果页表项存在位有效,取出物理页框号,拼接页内偏移得到物理地址;
5. 如果存在位无效,触发缺页异常,内核处理异常,分配物理页、更新页表,重新执行指令;
6.TLB 缓存映射,提升转换速度。
程序地址空间和进程地址空间有什么区别?
程序地址空间是磁盘上ELF文件的静态分段结构,不占用内存;
进程地址空间是程序运行后内核为进程创建的虚拟内存布局,每个进程独立一份。
C语言指针的值是虚拟地址还是物理地址?
虚拟地址。应用层完全看不到物理地址,地址翻译由MMU硬件自动完成。
两个不同进程中相同值的指针,指向同一块物理内存吗?
一般情况下不是。每个进程有独立页表,相同虚拟地址可映射不同物理页。
只有共享内存场景下,才会映射到同一块物理页。
.bss段的特点是什么?占用磁盘空间吗?
存放未初始化****的全局变量和静态变量;
不占用磁盘空间,仅记录大小**;加载时由内核清零。**
malloc申请100MB内存,会立刻占用100MB物理内存吗?为什么?
不会。malloc仅在虚拟地址空间划分区域,建立页表标记,不分配物理页;第一次访问时触发缺页异常,内核才分配物理内存,这就是按需分配。
什么是写时复制(COW)?哪些场景会用到?
写时复制是一种延迟拷贝优化:
fork后父子进程共享物理页,页表设为只读;
写入时才复制物理页。
应用场景:fork创建进程、私有mmap映射。
多级页表的作用是什么?为什么不用一级页表?
节省页表本身的内存开销。虚拟地址空间巨大,一级页表需要连续存储所有页表项,浪费内存;多级页表只为已使用的地址建立映射,空洞地址不占用页表。
什么是TLB?作用是什么?
TLB是MMU内部的高速缓存,缓存最近使用的页表项。
TLB命中可直接得到物理地址,避免访问内存页表,大幅提升内存访问速度。
段错误(segmentation fault)的本质是什么?常见触发场景?
本质是非法缺页异常。
常见场景:访问空指针、越界访问数组、释放后继续使用、写只读内存、栈溢出。
堆和栈有什么区别?
堆向高地址增长,手动管理,大小灵活,有碎片;
栈向低地址增长,自动管理,大小固定,速度快。
fork之后,父子进程的物理内存是完全拷贝一份吗?
不是。fork采用写时复制,父子共享物理页,只有写入时才复制。只有数据段、堆、栈会发生复制,代码段始终共享。
为什么要有虚拟地址空间?
进程隔离安全、提高内存利用率、简化程序加载、
细粒度权限保护、内存虚拟化。
mm_struct 和 vm_area_struct 是什么关系?
mm_struct 是整个进程地址空间的总描述符;vm_area_struct(VMA)是单个连续虚拟内存区域的描述符。一个 mm_struct 通过链表和红黑树管理多个 VMA,每个 VMA 代表一段地址连续、访问属性一致的虚拟内存区间。
进程和线程在 mm_struct 层面的本质区别?
进程拥有独立的 mm_struct、独立页表、独立 VMA 集合;同一进程内的所有线程共享同一个 mm_struct,共享地址空间、页表与所有虚拟区域。这也是线程间可以直接共享全局变量的底层原因。
进程切换时,地址空间是如何完成切换的?
调度器选中新进程后,内核将新进程 mm_struct 中的
pgd(页全局目录基地址)写入 CPU 的 CR3 寄存器,MMU 随即使用新进程的页表进行地址翻译,完成地址空间切换。内核线程的 mm 指针为什么为空?
内核线程仅运行在内核空间,不需要用户态地址空间,因此不需要独立的 mm_struct。运行时通过
active_mm借用普通进程的页表访问内核空间,避免不必要的页表切换开销。fork 之后子进程的 mm_struct 和父进程完全一致吗?
结构体字段大部分拷贝,但子进程拥有独立的页表与独立的 VMA 结构;物理内存通过写时复制机制共享,并非完整独立拷贝。子进程写入数据时触发缺页异常,分配新物理页并更新自身页表。
brk 系统调用的本质是什么?
修改 mm_struct 中的
brk堆顶指针,扩展或收缩堆区 VMA 的边界;不会立刻分配物理内存,物理内存按需在第一次访问时通过缺页异常分配。





