操作系统 | 内存
log
- 12
- 13 抽象:地址空间
- 14 插叙:内存操作API 简单看看
- 15 机制:地址转换
- 16 分段
- 17 空闲空间管理
- 18 分页:介绍
在虚拟化这一章节
虚拟化内存的目标:
- 透明:程序本身感受不到内存虚拟化的事实
- 效率:要在追求虚拟化的同时不消耗太多额外的内存和时间(TLB就是这样的一种硬件功能)
- 保护:进程间不相互影响
内存操作API(插叙)
malloc()
free()
忘记分配内存:
上述是c语言的库调用,这些库调用是建立在一些系统调用上的
brk,sbrk,mmap()
机制:地址转换
从程序的角度看,地址空间从0开始16KB结束,但对虚拟内存来说,操作系统希望将这个进程的地址空间放在物理内促你的其他位置
怎么在内存中重定位这个进程,同时对该进程透明?
介入
在硬件访问内存的图中插一脚,偷偷修改地址
在虚拟内存中:CPU每次访问内存前,硬件自动把进程给的虚拟地址,转换成真实的物理地址
优点:透明
动态重定位
用基址(base)寄存器和界限(bound)寄存器
为此操作系统产生的新问题
- 进程创建时,操作系统要为进程的地址空间找到内存空间
- 进程终止是,操作系统回收内存给其他进程或操作系统用
- 上下文切换时,每个CPU毕竟只有一个基址寄存器和界限寄存器,所一切换进程是,操作系统要保存和恢复基址和界限寄存器,如放入PCB中
分段
Question
用基址+界限这种方式,会把整个虚拟地址空间完整、连续地放入内存空间,哪怕中间有一大片空白区域进程没有使用
导致了内存浪费
如何解决?怎么支持一个很大的地址空间,但不要把里面没用的空洞也真的放入物理内存?
分段:泛化的基址/界限
segmentation
典型的地址空间里有3个逻辑不同的段:代码、栈和堆,分段的机制可以让操作系统吧不同的段放到不同的物理内存区域,从而避免了虚拟地址空间中的未使用部分占用物理内存
段错误
段错误指在支持分段的机器上发生了非法的内存访问
每个段可以单独放到物理内促你的不同位置,不需要整个地址空间连续放置,这就是泛化的基址/界限机制
也因此,在地址转换时,多了一步:先判断属于哪个段
分段地址转换不是直接:
而是
- 判断 VA 属于哪个段
- 算出 VA 在这个段内的偏移 offset
- 检查 offset 是否小于该段 bounds
- PA = 该段 base + offset
举例
假设虚拟地址空间大小16KB
段表:
| 段 | 虚拟范围 | base | bounds |
|---|---|---|---|
| code | 0KB ~ 2KB | 32KB | 2KB |
| heap | 4KB ~ 6KB | 40KB | 2KB |
| stack | 14KB ~ 16KB | 28KB | 2KB |
如果访问VA=5KB
属于heap段,heap段虚拟起点为4KB,所以
得到:stack有点特殊
heap是向下长,stack是向上长,所以段表里还要表示是正向还是反向增长的信息,除了base/bounds,还可能记录:
空闲空间管理
在一块连续的堆空间里,如何管理已用空间和空闲空间
free list
链表
申请内存时,就要从空闲空间切分出来(splitting)
申请内存是,如果旁边也是空闲块,就要合并(Coalescing)
分配策略:首次适配、最佳适配、最坏适配
对齐 Alignment
对齐,让访问更高效,代价是产生内部碎片
分页:介绍
虚拟地址 = 页号 + 偏移量
页号 ---页表地址转换->> 物理页号
物理地址 = 物理页号 + 偏移量
页表在哪里
如果没有虚拟内存这个概念,整个地址空间的东西都要放入页表中,页表就很大。
而实际上,内存可以虚拟化,因此页表可以存储在操作系统的虚拟内存中
页表中有什么
要把虚拟地址映射到物理地址
。。。
分页带来的问题
分页新增了一个额外的内存引用,一遍首先从页表中获取地址转换,额外的内存引用开销很大