在Linux中,CPU访问的地址是虚拟地址空间的虚地址。因此,对于内存的管理,通常是先在虚存空间中分配一个虚存区间,然后才根据需要为此区间分配相应的物理页面并和建立映射,也就是说虚存区间先分配,物理页面后分配。
1.页描述符
从虚拟内存的角度来看,页就是最小单位。体系结构不同,支持的页大小也不尽相同。大多数32位体系结构支持4KB,而64位体系结构一般会支持8KB。
内核用struct page结构来表示系统中的每个页,也叫页描述符。
struct page {
page_flag_t flags; //用来存放页的状态,而flags的每一位都可以表示一个状态,所以它至少可以同时表示32种不同的状态(这些标志定义在linux/page_flags.h中)
atomic_t _count; //存放页的计数引用,当计数为0时就说明当前内核没有引用这一页,于是在新的分配中就可以使用他。而内核代码不直接检查该域,而是通过 page_count()函数进行检查,该函数唯一参数就是page结构。对该函数而言,返回0表示页空闲,返回正数表示页在使用。
atomic_t _mapcount;
unsigned long private; //一个页所装有的私有数据
struct address_space *mapping; //一个页可以由页缓存(针对以页为操作的所有操作,并考虑了特定体系结构上的页长度,一个主要的例子是映射技术)使用,这就是由mapping域所指向的address_space对象
pgoff_t index;
struct list_head lru; //lru指向最近久未使用(LRU)链表中的相应结点。该链表用于页面的回收。
void *virtual; //页的虚地址(通常情况下,它就是页在虚拟内存中的地址)
};
必须要理解的一点是page结构与物理页相关。因此该结构对页的描述只是短暂的。内核仅仅用这个数据结构来描述当前时刻在相关的物理页中存放的东西。这种数据结构的目的在于描述物理内存本身,而不是描述包含在其中的数据。内核用该结构管理系统中所有的页,因为内核需要知道一个页是否空闲(也就是页有没有被分配)。如果页被分配,内核还要知道是谁拥有该页。
须知,系统中的每个物理页都需要分配一个这样的结构体。要管理这么多的物理页面,采用的时最简单的数组结构:
struct page *mem_map; //用于存放页描述符的数组
系统在初始化时就建立page结构的数组mem_map,在内核代码中,该数组是一个全局变量,它描述了系统中的全部物理页面。
随着用户程序的执行和结束,需要不断的为其分配和释放物理页面,但是
频繁的请求和释放不同大小的一组连续页面,必然导致在已分配的内存块中分散着许多小块的空闲页面(外碎片随小,但数量众多),即外碎片,这些小块的空闲页面加起来可以满足所请求的页面,但分散开却并不足以满足。为此,Linux采用伙伴算法来解决外碎片问题。
2.伙伴算法
Linux的伙伴算法把所有的空闲页面分为10个块链表,每个链表中的一个块含有2的幂次个页面,我们把这种块叫做“页块”,例如第0个链表中块的大小都为2的0次方(1个页面),第一个链表块的大小都为2的1次方(2个页面),第9个链表中块的大小都为2的9次方(512个页面)。
伙伴算法采用的数据结构是一个叫做free_area的数组。
struct free_area_struct {
struct page *next;
struct page *prev; //next和prev用于将物理页面结构struct page连接成一个双向链表
unsigned int *map; //map域指向一个位图其大小取决于现有的页面数如果位图的某位为0,表示一对兄弟块中或者两个都空闲,或者两个都被分配,如果为1,肯定有一块已被分配。当兄弟块都空闲时,内核把它们当作一个大小为2k+1的单独快来处理
}free_area[10];
我们通过一个简单的例子来说明该算法的工作原理。
假设要求分配的块其大小为128个页面(由多个页面组成的块我们就叫做页面块)。该算法先在块大小为128个页面的链表中查找,看是否有这样一个空闲块。如果有,就直接分配;如果没有,该算法会查找下一个更大的块,具体地说,就是在块大小为256个页面的链表中查找一个空闲块。如果存在这样的空闲块,内核就把这256个页面分为两等份,一份分配出去,另一份插入到块大小为128个页面的链表中。如果在块大小为256个页面的链表中也没有找到空闲页块,就继续找更大的块,即512个页面的块。如果存在这样的块,内核就从512个页面的块中分出128个页面满足请求,然后从384个页面中取出256个页面插入到块大小为256个页面的链表中。然后把剩余的128个页面插入到块大小为128个页面的链表中。如果512个页面的链表中还没有空闲块,该算法就放弃分配,并发出出错信号。
以上过程的逆过程就是块的释放过程,这也是该算法名字的来由。满足以下条件的两个块称为伙伴:
· 两个块的大小相同
· 两个块的物理地址连续
伙伴算法把满足以上条件的两个块合并为一个块,该算法是迭代算法,如果合并后的块还可以跟相邻的块进行合并,那么该算法就继续合并。