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. 虚拟地址空间详细图解
一、进程虚拟内存空间划分(从代码到地址布局)
代码与数据地址观测
通过打印变量地址(如全局变量、堆、栈、常量等),可观察到:
代码段(
code addr)、初始化全局(init global)、未初始化全局(unit global)地址接近,属于代码/数据区。堆(
heap addr)地址递增,符合"堆向上生长";栈(stack addr)地址递减,符合"栈向下生长"。命令行参数(
argv)、环境变量(env)位于栈附近,属于栈区附属区域。虚拟地址空间的本质
程序运行即进程,进程存在则虚拟地址空间必然存在。OS 通过规划虚拟地址空间(而非直接操作物理内存),将代码、数据、堆、栈等分区管理,再映射到物理内存。
二、页表与地址转换(MMU 的角色)
页表的作用
虚拟地址 → 物理地址的转换由 **MMU(内存管理单元,CPU 内置硬件)** 完成。页表存储虚拟地址到物理地址的映射关系,CPU 只负责读取虚拟地址,交给 MMU 转换,过程对软件透明。
页表的细节
惰性分配 :进程
malloc时仅分配虚拟地址空间,物理内存延迟到"读/写"时才会创建映射(避免空占物理内存)。权限标志 (如
r/w/x):页表项标记内存区域的权限(只读、可写、可执行),若违规操作(如写只读页),内核会终止进程。三、虚拟地址空间的意义(为何需要虚拟内存)
管理内存碎片:虚拟地址将零散的物理内存"拼接"成连续的地址空间,便于统一管理。
保护内存安全:进程仅操作虚拟地址,内核通过页表隔离不同进程的内存,防止越权访问。
解耦进程与内存模块:进程关心虚拟地址,内核关心页表映射,降低模块耦合。
支持大内存与并发:通过"惰性分配 + swap 扩展",进程可使用远超物理内存的虚拟空间,提升并发能力。
四、VMA(虚拟内存区域)与 mm_struct(内存描述符)
**mm_struct:进程的"虚拟地址空间总控"**
每个进程对应一个
mm_struct,记录虚拟地址空间的全局信息(如代码段、数据段、堆、栈的起始/结束地址,总内存大小等)。**VMA:虚拟地址的"分段管理器"**
堆、栈反复创建销毁会导致虚拟地址空间"零散",
mm_struct的简单start/end无法表示所有碎片。因此引入
vm_area_struct(VMA),每个 VMA 描述一段连续的虚拟内存区域 (含权限、起始/结束地址)。多个 VMA 通过链表(mmap)串联,覆盖所有零散空间。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,形成"进程-虚拟空间-物理内存"的完整映射链。


