【Linux】 进程(9)程序与进程地址空间(基础+进阶+面试题)


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文件由三部分组成:

  1. ELF头部:记录文件类型、架构、入口地址、程序头表/节头表的偏移与大小
  2. 程序头表(Program Header Table):加载视图,操作系统加载程序时使用,描述哪些段需要映射到内存
  3. 节头表(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下关系如下:

  1. 逻辑地址:段选择子 + 段内偏移,x86分段机制产物
  2. 线性地址(虚拟地址):段基址 + 段内偏移
  3. 物理地址:内存芯片上的真实地址,通过分页机制转换得到

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 内存描述符

cpp 复制代码
struct 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)虚拟内存区域

cpp 复制代码
struct 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 包含的核心信息(图中标注):

  1. 地址范围
    • vm_start:该区域的起始虚拟地址
    • vm_end:该区域的结束虚拟地址
    • 区间遵循左闭右开原则,vm_end - vm_start 就是该区域的总大小
  2. 链表指针
    • vm_prev / vm_next:所有 VMA 以双向链表形式按地址排序串联,便于顺序遍历
    • 内核同时还维护红黑树(mm_rb)组织 VMA,用于快速查找地址所属区域,图中未画出
  3. 回指指针
    • vm_mm:反向指向所属的 mm_struct,便于从 VMA 快速定位到进程的内存描述符
  4. 权限标志
    • 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 链表/红黑树判断地址合法性:

  1. 不属于任何 VMA:非法地址,向进程发送 SIGSEGV 段错误信号
  2. 属于合法 VMA,但访问权限不匹配:同样触发段错误
  3. 属于合法 VMA 且权限合法:再继续执行物理页分配、文件加载、写时复制等后续处理

malloc / mmap 和 VMA 是什么关系?

  1. 小块内存的 malloc 从堆区分配,本质是扩展堆 VMA 的 vm_end 边界
  2. 大块内存的 malloc 走 mmap 匿名映射,本质是新建一个匿名 VMA
  3. 释放内存时,对应缩小或销毁对应的 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 拆分地址:

  1. 高位:虚拟页号,去查页表,拿到物理页框号
  2. 低位:页内偏移,不用转换,直接拼到物理页框号后面,得到最终物理地址。

页内偏移:一页 4KB=4096 字节,需要 12bit,这 12 位在虚拟、物理地址完全不变。

举个简单例子
  • 页大小:4KB
  • 虚拟地址:0x12345678
  • 高 20 位 = 虚拟页号,查页表得到物理页框号;低 12 位不变,拼接得到物理地址。
2. 为什么需要分页?(核心好处)
  1. **进程拥有独立虚拟地址空间****每个进程有自己独立页表。**不同进程可以有相同的虚拟地址,映射到不同物理内存,实现进程隔离。
  2. 内存离散使用,不用连续物理内存****进程的一大块虚拟内存,可以映射到物理内存里不连续的物理页框。 没有分页的话,程序需要一大片连续物理内存,很容易外部碎片。分页把大内存拆小,碎片问题大幅缓解。
  3. 按需分配(缺页) **创建进程、malloc 的时候,不会立刻分配物理内存,只修改 VMA、填页表项标记未分配。 程序第一次访问这个虚拟地址,触发缺页异常 Page Fault,内核才分配物理页框,建立映射。**延迟分配,节省物理内存。
  4. 内存保护****页表项自带权限位:可读、可写、可执行。 写代码段(只读页)、访问没有映射的虚拟页 → MMU 报错,内核发 SIGSEGV 段错误。
  5. Swap 交换****物理内存不够时,可以把暂时不用的物理页写到磁盘 swap 分区;需要的时候再换回来。 分页机制让内核可以把部分页 "移出物理内存"。
3. 和分段的区别(容易混淆)
  • 分段 Segment:按程序逻辑划分(代码段、数据段、堆、栈),段大小不固定。对应内核的vm_area_struct(VMA)
  • 分页 Page:硬件 MMU 机制,固定大小切割内存,纯粹内存管理。

Linux:分段逻辑由软件 VMA 实现,硬件层面只用分页。 VMA(段逻辑)描述一块逻辑区域;底层 MMU 分页做地址翻译。

4. 两种缺页
  1. 主缺页(Major Page Fault) 页面不在物理内存,要读磁盘(swap 回写 / 读取 elf 文件),慢。
  2. 次缺页(Minor Page Fault) 页还在物理内存,只是页表没建立映射,不用访问磁盘,速度快。

fork 写时复制 COW 触发的就是次缺页。

5. 面试高频题
Q1:分页的作用?
  1. 实现虚拟地址到物理地址转换;
  2. 进程地址空间隔离,每个进程独立页表;
  3. 物理内存可以离散分配,减少外部碎片;
  4. 支持缺页异常,物理内存按需分配;
  5. 页表权限实现内存保护;
  6. 支持 swap 内存换出。
Q2:页内偏移为什么不用转换?

虚拟页和物理页框大小完全相同,页内的偏移位置不变,直接拼接即可。

Q3:VMA 和分页是什么关系?
  • VMA(vm_area_struct):内核软件,管理逻辑分段(代码段、堆、栈),描述一段连续虚拟地址;
  • 分页:CPU 硬件 MMU,按页粒度做虚拟→物理地址映射。

一个 VMA 会包含很多个页面。访问 VMA 范围内的虚拟地址,MMU 通过页表映射到物理页。

Q4:为什么 fork 开销小?

fork 只拷贝 mm_struct、VMA、页表项,不拷贝物理页框。页表全部指向父进程物理页,标记只读;写入触发分页的缺页异常,才复制物理页,也就是写时复制 COW。


MMU与TLB

  1. MMU(内存管理单元):CPU内置硬件,负责虚拟地址到物理地址的翻译,以及权限检查
  2. TLB(Translation Lookaside Buffer,快表):MMU内部的高速缓存,缓存最近使用的页表项
    • TLB命中:直接得到物理地址,无需访问内存页表
    • TLB缺失:需要遍历内存中的页表,性能开销大
    • 大页可以显著减少TLB miss,提升内存访问性能

多级页表

为什么不使用一级页表?

因为虚拟地址空间太大,一级页表会占用巨大内存。

Linux采用多级页表,64位系统为四级页表:

PGD(全局页目录)→ PUD(上级页目录)→ PMD(中间页目录)→ PTE(页表项)

  • 优势:仅为已使用的虚拟地址建立页表项,大部分空洞地址不需要页表,极大节省页表本身的内存开销
  • 每个进程拥有独立的页表,进程切换时切换CR3寄存器(指向PGD基址),完成地址空间切换

缺页异常(Page Fault)

当进程访问的虚拟地址没有建立物理映射,或权限不匹配时,CPU会触发缺页中断,由内核处理。分为三类:

  1. 合法缺页(按需分配)
    • 场景:malloc后第一次访问内存、访问尚未加载的文件映射页
    • 处理:内核分配物理页,建立页表映射,进程继续执行
    • 这就是"malloc申请内存不会立刻占用物理内存"的根本原因
  2. 写时复制缺页(COW, Copy On Write)
    • 场景:fork之后,父子进程共享所有物理页,页表设为只读;当其中一方写入时触发
    • 处理:内核复制一份新的物理页,修改页表为可写,父子进程各自拥有私有副本
    • 价值:大幅提升fork效率,节省物理内存
  3. 非法缺页
    • 场景:访问空指针、访问已释放内存、写只读代码段、越界访问
    • 处理:内核向进程发送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 的边界;不会立刻分配物理内存,物理内存按需在第一次访问时通过缺页异常分配。


相关推荐
不怕犯错,就怕不做41 分钟前
git prune 自动删除本地记录中那些远程已经不存在的分支引用
linux·服务器·git
BizzZ_1 小时前
C++(22)——类型转换和IO流
开发语言·c++
小范同学_1 小时前
JDK1.7 与 JDK1.8 HashMap 底层原理对比 + 数组并发扩容死循环详解
java·开发语言
richdata1 小时前
商品数字化不是先做大数据,而是先把数据变成可用决策
大数据·运维·数据治理·商品数字化·智能补货·ai商品决策
geovindu2 小时前
CSharp: 万年历
开发语言·后端·c#·.net
weixin_416667962 小时前
【无标题】
运维·服务器·网络
我找到地球的支点啦2 小时前
Matlab系列(009) 一CRC循环冗余校验详解
开发语言·数据结构·算法·matlab·信息与通信
予昊2 小时前
从零实现“在线五子棋对战“:WebSocket 实时通信 + 段位匹配
java·开发语言·网络·websocket
一池秋_2 小时前
arm低配linux设备,桌面应用冷启动提速方法
linux·运维·arm开发