内存保护、共享内存与写时复制是什么
内存管理不只是“怎么分配页、怎么换页”,还包括一个更关键的问题:
为什么一个进程不能随便访问另一个进程的内存? 既然进程隔离这么强,为什么多个进程又能共享同一块内存?
这背后依赖的就是内存保护与共享机制。
一、什么是内存保护
内存保护可以简单理解成:
操作系统要保证一个进程只能按规定方式访问属于自己的那部分内存。
它要防止这些情况:
- 进程 A 乱读进程 B 的数据
- 普通程序写内核空间
- 程序修改只读代码页
- 把数据页当成代码执行
所以内存保护本质上做的是两件事:
- 地址空间隔离
- 访问权限控制
二、内存保护为什么能做到
关键原因是,进程访问的不是物理地址,而是虚拟地址。真正能不能访问,要由页表和硬件一起决定。
也就是说,一个进程即使写出了某个地址,也必须经过:
- 地址转换
- 有效性检查
- 权限检查
只有这些都通过,访问才会真正发生。
三、页表不只是做映射,还负责权限控制
很多人刚开始只把页表理解成“虚拟页到物理页框的映射表”,但页表项通常还会带很多控制位,例如:
- present / valid:当前页是否有效
- read / write:是否允许写入
- execute:是否允许执行
- user / supervisor:用户态是否可访问
- accessed:是否被访问过
- dirty:是否被修改过
所以页表不仅回答“映射到哪”,还回答“能不能这样访问”。
四、为什么一个进程不能直接访问另一个进程的内存
因为每个进程都有自己独立的页表。
进程 A 发出的虚拟地址,只会在 A 自己的页表里做解释,而不会跑到进程 B 的页表里查映射。所以 A 无法通过“猜地址”的方式直接访问 B 的物理页。
如果它访问了一个:
- 对自己来说没有映射的地址
- 或者权限不允许的地址
结果通常就是触发异常,常见表现是段错误。
五、共享内存的本质是什么
共享内存并不是“把隔离取消了”,而是:
操作系统让不同进程的页表,把各自的虚拟页映射到同一个物理页框。
例如:
- 进程 A 的虚拟页
VA1 - 进程 B 的虚拟页
VB1
都可以映射到同一个物理页框 P。
这样虽然两个进程使用的是不同虚拟地址,但底层操作的是同一块物理内存,于是就实现了共享。
六、共享内存为什么快,但也更危险
共享内存的最大优点是快。
因为多个进程直接操作同一块物理页,不需要像消息队列、管道那样经过多次拷贝。
但问题也很明显:
- 多个进程可能同时读写同一份数据
- 很容易出现竞态条件
- 数据一致性需要自己保证
所以共享内存通常要搭配同步机制一起使用,例如:
- 互斥锁
- 信号量
- 条件变量
- 原子操作
也就是说:
共享内存解决的是“能不能看到同一份数据” 同步机制解决的是“怎么安全地一起使用这份数据”
七、什么是写时复制(COW)
写时复制可以理解成:
先共享,写的时候再复制。
最典型的场景就是 fork。
如果 fork 后立刻把父进程全部内存都复制给子进程,代价会非常大。操作系统通常会采用更节省的方式:
- 父子进程先各自拥有独立页表
- 但页表中的部分页表项,先共同指向同一批物理页
- 并把这些页临时标成只读
这样如果父子都只是读取,就根本不用复制。
当其中某一方真的要写时,才会:
- 触发保护异常
- 分配新的物理页
- 把旧内容复制过去
- 修改当前进程页表,让它指向新的页并赋予可写权限
- 重新执行写操作
这就是写时复制。
八、内存保护与共享其实是一套机制的两种用法
很多人会觉得:
- 保护是隔离
- 共享是打通
好像是对立关系。
其实不是。
它们本质上都依赖同一套基础设施:
- 虚拟地址空间
- 页表映射
- 权限位
- CPU 的访问检查
也就是说:
- 想隔离时,映射分开、权限限制
- 想共享时,让不同页表映射到同一个物理页
- 想减少复制时,用只读页加写时复制
所以保护和共享并不冲突,而是一套机制的不同用法。
九、总结
内存保护主要依赖虚拟地址空间隔离和页表权限控制来实现。每个进程都有自己的页表,访问内存时不仅要做地址转换,还要经过有效性和权限检查,因此进程之间天然隔离。共享内存的本质则是让不同进程的虚拟页映射到同一个物理页框,从而高效共享数据,但通常需要同步机制配合。写时复制是一种先共享、写时再复制的优化机制,它利用只读保护在真正写入时才分裂物理页,从而减少不必要的内存复制开销。
