10.3 内存页的分配和释放 系统运行过程中,经常需要进行物理内存页的分配或释放。例如,执行程序时,操作系统需要为相应的进程分配页,而进程终止时,则要释放这些物理页。再如,页表本身也需要动态分配和释放。物理页的分配和释放机制及其相关数据结构是虚拟内存管理子系统的关键部分。 图 10-3 多级页表示意图 一般而言,有两种方法可用来管理内存的分配和释放。一种是采用位图,另外一种是采用链表。 内存单元的分配和释放, 必须判定该内存单元是否为空闲. 利用位图可记录内存单元的使用情况。例如,如果某个系统有 1024 字节内存,而内存的分配单元是 4 字节,则可以利用 1024/(4*8) = 32 个字节来记录使用情况。这 32 个字节的每个位分别代表相应分配单元的使用情况。如果位图中某个位为 1,则对应的分配单元是空闲的。利用这一办法,内存的分配就可以通过对位值的检测来简化。如果一次要分配 5 个单元的空间,内存管理程序就需要找出 5 个连续位值均为 1 的位图位置,但这种操作比较慢,因为连续的位有时要跨越字节边界。 利用链表则可以分别记录已分配的内存单元和空闲的内存单元。通常这些内存单元设计为双向链表结构,从而可加速空闲内存的搜索或链表的处理。这种方法相对位图方法要好一些,也更加有效。 Linux 的物理页分配采用链表和位图结合的方法。参照图 10-4,Linux 内核定义了一个称为 free_area 的数组,该数组的每一项描述某一种页块的信息。{dy}个元素描述单个+页的信息,第二个元素则描述以 2 个页为一个块的页块信息,第三个元素描述以 4 个页为一个块的页块信息,依此类推,所描述的页块大小以 2 的倍数增加。free_area 数组的每项包含两个元素:list 和 map。list 是一个双向链表的头指针,该双向链表的每个节点包含空闲页块的起始物理页帧编号;而 map 则是记录这种页块组分配情况的位图,例如,位图的第 N 位为 1,则表明第 N 个页块是空闲的。从图中也可以看到,用来记录页块组分配情况的位图大小各不相同,显然页块越小,位图越大。 图 10-4 中,free_area 数组的元素 0包含了一个空闲页(页帧编号为 0);而元素 2 则包含了两个以 4 页为大小的空闲页块,{dy}个页块的起始页帧编号为 4,而另一个页块的起始页帧编号为 56。 图 10-4 Linux 物理页块的分配示意 Linux 采用 Buddy 算法有效分配和释放物理页块。按照上述数据结构,Linux 可以分配的内存大小只能是 1 个页块,2 个页块或 4 个页块等等。在分配物理页块时,Linux 首先在 free_area 数组中搜索大于或等于要求尺寸的最小页块信息,然后在对应的 list 双向链表中寻找空闲页块,如果没有空闲页块,Linux 则继续搜索更大的页块信息,直到发现一个空闲的页块为止。如果搜索到的页块大于满足要求的最小页块,则只需将该页块剩余的部分划分为小的页块并添加到相应的 list 链表中。 页块的分配会导致内存的碎片化,而页块的释放则可以将页块重新组合成大的页块。如果和被释放的页块大小相等的相邻页块是空闲的,则可以将这两个页块组合成一个大的页块,这一过程一直继续,直到把所有可能的页块组合成尽可能大的页块为止。 知道了上述原理,读者可以自己想象系统启动时,初始的 free_area 数组中的信息。 |