< 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,形成"进程-虚拟空间-物理内存"的完整映射链。

相关推荐
xx~t21 分钟前
嵌入式学习——ARM1
linux·arm开发·嵌入式硬件·arm
xx~t23 分钟前
嵌入式——ARM1(详细篇)
linux·arm开发·嵌入式硬件·arm
mmmmath_326 分钟前
LeetCode.541.反转字符串II
数据结构·算法·leetcode
Navigator_Z37 分钟前
LeetCode //MySQL - 1251. Average Selling Price
c语言·算法·leetcode
我要见SA姐11 小时前
告别 Copilot?Codex 本地化部署指南
运维·数据库·机器学习·oracle·回归
醇氧2 小时前
MySQL 8.0 系统表损坏与引擎转换故障排查实战
数据结构·算法
Elastic 中国社区官方博客2 小时前
列式存储并不等同于列式数据库。Columnar 模式为 Elasticsearch 带来了什么
大数据·运维·数据库·elasticsearch·搜索引擎
大熊背2 小时前
《Color constancy by characterization of illumination chromaticity》之色度色域最大化算法(二)
算法·白平衡·色度·色温
钓鱼的肝2 小时前
梳理(1-5)
c++·经验分享·笔记·算法·青少年编程