1.物理页面的分配
函数__get_free_pages用于物理页块的分配,其定义如下:
unsigned long __get_free_pages(int gfp_mask, unsigned long order);
其中gfp_mask是分配标志,表示对所分配内存期间的特殊要求。常用的标志为GFP_KERNEL和GFP_ATOMIC,前者表示在分配内存期间可以睡眠,在进程中使用;后者表示不可睡眠,在中断处理程序中使用。
order是指数,所请求的页块大小为2的order次幂个物理页面,即页块在free_area数组中的索引。
该函数所作工作如下:
(1)检查所请求的页块是否能够满足
if(order >= 10) goto nopage;
(2)检查系统中空闲物理页的总数是否以低于允许的界限
if(nr_free_pages > freepages.min){
if(!low_on_memory) goto ok_to_allocate
if(nr_free_pages >= freepages.high) {
low_on_memory = 0 ;
goto ok_to_allocate;
}
}
因为物理内存是十分紧张的系统资源,很容易被用完。而一旦内存被用完,当出现特殊情况时系统将无法处理,因此必须留下足够的内存以备急需。另外,当系统中空闲内存数量太少时,要唤醒内核交换守护进程,让其将内核中的某些页交换到外存,从而保证系统中有足够的空闲页块。为此,Linux系统定义了一个结构变量freepages,
struct freepages_v1 {
unsigned int min; //系统中的空闲物理页面绝对不能少于该值
unsigned int low; //系统中的空闲物理页面少于该值时加强交换
unsigned int high;//系统中的空闲物理页面少于该值时启动后台交换
}freepages_t;
freepages_t freepages;
在系统初始化时,变量freepages被赋予了初值,其各个界限值的大小是根据实际物理内存计算出来的。
全局变量nr_free_pages中记录的是系统中当前空闲的物理页面。
如果空闲物理页数大于freepages.min的界限,则正常分配;否则,换页。
(3)正常分配。从free_area数组的第order项开始。
a.如果该链表中有满足要求的页块,则执行以下操作:
a)将其从链表中摘下;
b)将free_area数组的位图中该页块所对应的位取反,表示页块已用;
c)修改全局变量nr_free_pages(减去分配出去的页数)
d)根据该页块在mem_map数组中的位置,算出其初始物理地址,返回。
b.如果该链表中没有满足的页块,则在free_area数组中顺序向上查找。其结果有以下两种情况:
a)整个free_area数组中都没有满足要求的页块,此次无法分配,返回。
b)找到一个满足要求的页块,则执行以下操作:
(a)将其从链表中摘下;
(b)将free_area数组的位图中该页块所对应的位取反,表示页块已用;
(c)修改全局变量nr_free_pages(减去分配出去的页数);
(d)因为页块比申请的页块要大,所以要将他分成适当大小的块。因为所有的页块都有2的幂次的页数组成,所以这个分割的过程比较简单,只需要将它平分就可以,其方法如下:
i.将其平分为两个伙伴,将小伙伴加入free_pages数组中相应的链表,修改位图中相应的位;
ii.如果大伙伴仍比深情的页块大,则转i,继续划分。
iii.大伙伴的大小正是所要的大小,修改位图中相应的位,根据其在mem_map数组中的位置,算出它的其实物理地址,返回。
(4)换页。通过下列语句调用函数try_to_free_pages(),启动换页程序。
try_to_free_pages(gfp_mask)
该函数所作的工作十分简单,唤醒内核守护进程kswapd:
wake_up_process(kswapd_process);
其中kswapd_process是指向内核交换守护进程kswapd的指针。(kewapd将在交换机制提到)
2.物理页面的回收
分配页块的过程将大的页块分为小的页块,将会使内存更为零散。页回收的过程与页分配的过程正好相反,只要可能,就把小页块合并成大页块。
函数free_pages用于页块的回收,其定义如下:
void free_pages(unsigned long addr, unsigned long order)
其中addr是要回收的页块的首地址;order指出要回收的页块的大小为2的order次幂个物理页。
该函数所作工作如下:
(1)根据页块的首地址addr算出该页块的第一页在mem_map数组中的索引。
(2)如果该页内核在使用,则不允许收回。
(3)将页块第一页对应的page结构中的_count域减1,表示引用该页的进程数减1个。如果_count域的值不是0,则说明还有别的进程使用该页块,因此不能回收它,简单的返回。
(4)清除页块第一页对应的page结构中flags域的PG_referenced位,表示该页块不再被引用。
(5)调整全局变量nr_free_pages,将其值加上回收的物理页数。
(6)将页块加入到数组free_area的相应链表中。
要加入链表由order参数指定,即将页块加入到free_area[order]链表中。检查free_area[order]的位图map,看该页块的伙伴是否已在链表中(在位图中,两伙伴使用同一位,实则就是每一个页块用一位表示其状态,当伙伴拆开后分成两个页块就由各自页块的map表示其各自的状态)。
a)其伙伴不在链表中,说明该页块的伙伴还在使用,不需要合并。此时只需要将位图中该页块相应的位取反,表示页块已经自由,将其加入到free_area[order]链表的头部。
b)其伙伴在链表中,说明页块及其伙伴均获取自由,可以将它们合并成更大的页块。将页块的伙伴从链表中摘下,将他们在位图中对应的位取反,表示页块已不可用;计算新的大页块在mem_map数组中的索引(页块索引和伙伴索引的小索引);order++,转加入过程,将大页块加入到数组free_area的相应链表中。