1.用户级存储器映射
之前我们介绍过关于程序加载的详细内容,我们知道在其加载执行之前要对程序进行存储器映射,Unix进程可以使用mmap函数来创建新的虚拟存储器区域,并将对象映射到这些区域。
Mmap函数要求内核创建一个新的虚拟存储器区域,最好是从start开始的地址,并将文件描述fd标识对象的一个连续的片映射到这个新的区域。连续的对象片大小为length,从距文件开始处偏移量为offset的地方开始。Prot指定了新创建的虚拟页面的访问位权限(之前提到过的虚拟页面的读写、执行权限)。最后,falgs字段描述的是被映射对象的类型,可以用来标记匿名对象,私有、写时拷贝对象和共享对象。调用mmap函数成功后就会返回对应新区域的地址。
和mmap相对应,munmap函数用来删除虚拟存储器的区域:
Munmap函数删除从虚拟地址start开始的,由接下来length字节组成的区域,对已删除区域的引用会引起段错误。
2.动态存储器分配
我们可以通过mmap和munmap来创建和删除虚拟存储器区域,但对开发人员来说使用起来并不方便,况且没有很好的移植性,所以提出了使用动态存储分配器来管理进程空间中的堆区域。
动态存储分配器维护着一个进程的虚拟存储器区域,称为堆。堆是从低位地址向高位向上增长的,对于每个进程,内核维护着一个brk,它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟存储器片,要么是已分配的,要么是空闲的。已分配的显示地保留为供应应用程序使用。空闲块可用来分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的,要么是存储分配器隐式执行的,它们的都是显式的来分配存储块的,不同之处在于由哪个实体来负责释放已分配的块。
a) 显式分配器,要求显式的释放已分配的块。如C标准库中的malloc和free,C++中的new和delete操作符。
b) 隐式分配器,要求分配器检测一个已分配的块何时不再被程序使用,那么就释放这个块。隐式分配器也叫做垃圾收集器(Grabage collector)。如java语言就依赖于类似分配器。
下面我们看下malloc和free的实现是如何管理一个C程序的16字的小堆的。每个方框代表一个4字节的字。粗线标出的矩形对应于已分配块(有阴影)和空闲块(无阴影),初始时,堆是由一个大小为16个字的、双字对齐的、空闲块组成的。
a) 程序请求一个4字的块,malloc的响应是:从空闲块的前部切出一个4字的块,并返回一个指向这个块的第一个字的指针p1
b) 程序请求一个5字的块,malloc的响应是:从空闲块的前部分配一个6字的块,返回指针p2,填充的一个额外字是为了保持空闲块是双字边界对齐的。
c) 程序请求一个6字的块,而malloc就从空闲块的前部切出一个6字的块。返回指针p3
d) 程序释放在b中分配的那个6字的块。需要注意的是,在调用free返回之后,指针p2仍然指向被释放的块,在它被一个新的malloc调用重新初始化之前不能在程序中再使用p2.
e) 程序请求一个2字的块。在这种情况下,malloc分配在前一步中释放了的块的一部分,并返回指向新块的指针p4.
3.分配器的要求和目标
显式分配器必须在一些相当严格的约束条件下工作:
(1) 处理任意请求序列。一个应用可以有任意的分配请求和