抽象:地址空间
1、操作系统需要提供一个易用(easy to use)的物理内存抽象。这个抽象叫作地址空间(address space),是运行的程序看到 的系统中的内存。
2、虚拟内存:虚拟内存系统负责为程序提供一个巨大的、稀疏的、私有的地址空间的假象,其中保存了程序的所有指令和数据。
3、栈和堆:
栈(stack)来保存当前的函数调用信息,分配空间给局部变量,传递参数和函数返回值。 堆(heap)用于管理动态分配的、用户管理的内存。
4、隔离是建立可靠系统的关键原则。
5、虚拟内存系统的主要目标:
- 透明:操作系统实现虚拟内存的方式,应该让运行的程序看不见。
- 效率:操作系统应该追求虚拟化尽可能高效。包括时间上和空间上。
- 保护:操作系统应确保进程受到保护(protect),不会受其他进程影响,操作系统本身也不会受进程影响。
内存操作API
1、内存两种类型:
- 栈内存:它的申请和释放操作是编译器来隐式管理。有时也称为自动(automatic)内存。
- 堆内存:其中所有的申请和释放操作都由程序员显式地完成。
2、malloc()调用:
基本用法:malloc 函数非常简单:传入要申请的堆空间的大小,它成功就返回一个指向新申请空间的指针,失败就返回NULL。
比如:
//size_t 类型参数,该参数表示你需要多少个字节
void *malloc(size_t size);
或者
double *d = (double *) malloc(sizeof(double));
再或者
int *x = malloc(10 * sizeof(int));
3、free()调用:
要释放不再使用的堆内存,程序员只需调用free()。
int *x = malloc(10 * sizeof(int));
...
free(x);
4、值得注意的是:malloc()调用和free()调用。都不是系统调用,而是库调用。
地址转换
1、回顾:
在实现CPU 虚拟化时,我们遵循的一般准则被称为受限直接访问(Limited Direct Execution,LDE)。
LDE 背后的想法很简单:让程序运行的大部分指令直接访问硬件,只在一些关键点(如进程发起系统调用或发生时钟中断)由操作系统介入来确保“在正确时间,正确的地点,做正确的事”。
2、地址转换(address translation):
可以看成是受限直接执行这种一般方法的补充。
作用:将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址。
3、动态(基于硬件)重定位:称为基址加界限机制(base and bound),有时又称为动态重定位。
具体实现:每个CPU 需要两个硬件寄存器:基址(base)寄存器和界限(bound)寄存器,有时称为限制(limit)寄存器。
physical address = virtual address + base
4、操作系统所做的事儿(针对地址转换功能):
- 第一,在进程创建时,操作系统必须采取行动,为进程的地址空间找到内存空间。
- 第二,在进程终止时(正常退出,或因行为不端被强制终止),操作系统也必须做一些 工作,回收它的所有内存,给其他进程或者操作系统使用。
- 第三,在上下文切换时,操作系统也必须执行一些额外的操作。
- 第四,操作系统必须提供异常处理程序(exception handler),或要一些调用的函数。
空闲空间管理
1、外部碎片和内部碎片:
- 外部碎片:未分配空间的碎片。
- 内部碎片:浪费发生在已分配单元的内部。
2、底层机制:
- 分割与合并
- 追踪已分配空间的大小
- 嵌入空闲列表
- 让堆增长
3、基本策略:
- 最优匹配:首先遍历整个空闲列表,找到和请求大小一样或更大的空闲块,然后返回这组候选者中最小的一块。
- 最差匹配:尝试找最大的空闲块,分割并满足用户需求后,将剩余的块(很大)加入空闲列表。
- 首次匹配:第一个足够大的块,将请求的空间返回给用户。
- 下次匹配:不同于首次匹配每次都从列表的开始查找,下次匹配(next fit)算法多维护一个指针,指向上一次查找结束的位置。其想法是将对空闲空间的查找操作扩散到整个列表中去,避免对列表开头频繁的分割。
4、其他方式:
- 分离空闲列表:如果某个应用程序经常申请一种(或几种)大小的内存空间,那就用一个独立的列表,只管理这样大小的对象。其他大小的请求都一给更通用的内存分配程序。
- 伙伴系统(二分伙伴分配程序)
注:
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名,转载请标明出处。