< 12 > Linux进程:进程虚拟地址空间机制 —— 内存管理的美学

1. 程序地址空间回顾

C语言阶段学习过程序地址空间,长这样

代码段,数据段:这些是常量区,栈区,堆区,还有一些系统需要的空间

这些是内存吗? ------不是内存。这些都是虚拟地址空间,OS给我们画的大饼!

整齐排列的虚拟地址 -> 通过页表映射物理地址 -> 存放在零散的物理内存中

实际上的物理内存中,不存在那么多分区,各种数据都是零散存放的!

虚拟地址的意义之一就是:管理内存碎片!

2. 通过C代码,展示虚拟地址排布,感受虚拟内存

复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int g_unval;
int g_val = 100;

int main(int argc, char *argv[], char *env[])
{
    const char *str = "helloworld";
    printf("code addr: %p\n", main);
    printf("init global addr: %p\n", &g_val);
    printf("uninit global addr: %p\n", &g_unval);

    static int test = 10;
    char *heap_mem = (char*)malloc(10);
    char *heap_mem1 = (char*)malloc(10);
    char *heap_mem2 = (char*)malloc(10);
    char *heap_mem3 = (char*)malloc(10);

    printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)
    printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)
    printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)
    printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)

    printf("test static addr: %p\n", &test); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)
    printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)

    printf("read only string addr: %p\n", str);
    for(int i = 0 ; i < argc; i++)
    {
        printf("argv[%d]: %p\n", i, argv[i]);
    }
    for(int i = 0; env[i]; i++)
    {
        printf("env[%d]: %p\n", i, env[i]);
    }

    return 0;
}

通过打印对应变量的地址,感受每种变量在内存的排布

3. 一个id,两个值,地址相同 -> 虚拟地址

地址一样,但是两个值,绝对不是物理地址。而是:虚拟地址空间

4. 虚拟地址空间详细图解

一、进程虚拟内存空间划分(从代码到地址布局)
  1. 代码与数据地址观测

    通过打印变量地址(如全局变量、堆、栈、常量等),可观察到:

    • 代码段(code addr)、初始化全局(init global)、未初始化全局(unit global)地址接近,属于代码/数据区

    • 堆(heap addr)地址递增,符合"堆向上生长";栈(stack addr)地址递减,符合"栈向下生长"。

    • 命令行参数(argv)、环境变量(env)位于栈附近,属于栈区附属区域

  2. 虚拟地址空间的本质

    程序运行即进程,进程存在则虚拟地址空间必然存在。OS 通过规划虚拟地址空间(而非直接操作物理内存),将代码、数据、堆、栈等分区管理,再映射到物理内存。

二、页表与地址转换(MMU 的角色)
  1. 页表的作用

    虚拟地址 → 物理地址的转换由 **MMU(内存管理单元,CPU 内置硬件)**​ 完成。页表存储虚拟地址到物理地址的映射关系,CPU 只负责读取虚拟地址,交给 MMU 转换,过程对软件透明。

  2. 页表的细节

    • 惰性分配 :进程 malloc时仅分配虚拟地址空间,物理内存延迟到"读/写"时才会创建映射(避免空占物理内存)。

    • 权限标志 (如 r/w/x):页表项标记内存区域的权限(只读、可写、可执行),若违规操作(如写只读页),内核会终止进程。

三、虚拟地址空间的意义(为何需要虚拟内存)
  1. 管理内存碎片:虚拟地址将零散的物理内存"拼接"成连续的地址空间,便于统一管理。

  2. 保护内存安全:进程仅操作虚拟地址,内核通过页表隔离不同进程的内存,防止越权访问。

  3. 解耦进程与内存模块:进程关心虚拟地址,内核关心页表映射,降低模块耦合。

  4. 支持大内存与并发:通过"惰性分配 + swap 扩展",进程可使用远超物理内存的虚拟空间,提升并发能力。

四、VMA(虚拟内存区域)与 mm_struct(内存描述符)
  1. **mm_struct:进程的"虚拟地址空间总控"**​

    每个进程对应一个 mm_struct,记录虚拟地址空间的全局信息(如代码段、数据段、堆、栈的起始/结束地址,总内存大小等)。

  2. **VMA:虚拟地址的"分段管理器"**​

    • 堆、栈反复创建销毁会导致虚拟地址空间"零散",mm_struct的简单 start/end无法表示所有碎片。

    • 因此引入 vm_area_struct(VMA),每个 VMA 描述一段连续的虚拟内存区域 (含权限、起始/结束地址)。多个 VMA 通过链表(mmap)串联,覆盖所有零散空间。

  3. VMA 回指 mm_struct(vm_mm)的意义

    VMA 通过 vm_mm指针回指所属进程的 mm_struct,让局部内存区域能反向定位全局上下文(如缺页异常时,通过 VMA→mm_struct 找到页表,修复映射)。

五、写时拷贝(COW)与进程创建
  • 子进程通过 fork创建时,默认共享父进程的物理内存(浅拷贝),仅复制页表。

  • 若父子进程尝试"写"操作,触发写时拷贝:内核为写操作分配新物理内存,复制数据后修改页表,保证进程间内存独立。

六、虚拟内存空间的最终框架(结构关系)
  • 进程控制块(task_struct)→ 包含 mm_struct(虚拟地址空间总控)→ mm_struct包含 vm_area_struct(VMA 链表)。

  • 各 VMA 对应虚拟地址空间的不同区域(代码、数据、堆、栈、文件映射等),通过 vm_mm关联 mm_struct,形成"进程-虚拟空间-物理内存"的完整映射链。

相关推荐
码完就睡1 小时前
Linux——进程间通信
linux·运维·服务器
AOwhisky1 小时前
Docker 学习笔记:Docker Compose 多容器编排
linux·运维·笔记·学习·docker·容器
Mr_pyx1 小时前
LeetCode 226. 翻转二叉树(多种解法详解)
算法·深度优先
qeen871 小时前
【算法笔记】各种常见排序算法详细解析(上)
c语言·数据结构·c++·学习·算法·排序算法
j_xxx404_1 小时前
Linux进程信号:内核数据结构与捕捉递达全流程
linux·运维·服务器·人工智能·ai
绿蕉1 小时前
自动驾驶技术的演进之路:从规则算法到端到端架构
算法·架构·自动驾驶
Ulyanov1 小时前
《从质点到位姿:基于Python与PyVista的导弹制导控制全栈仿真》: 基石——3-DOF质点弹道的高保真建模与数值稳定性分析
开发语言·python·算法·ui·系统仿真
一条大祥脚1 小时前
蚁群算法(例题TSP问题)
算法
青山师1 小时前
数组与链表深度解析:从内存布局到工业级实践
数据结构·算法·链表·数组·算法与数据结构