mm/page_alloc.c
纵观这个模块,实际上是buddy算法的核心所在.除了buddy算法的几个核心函数,
其余都是简单的再封装.
I) 全局量,概述
从头到尾的看吧.首先是几个变量的声明,这些变量是lru表的关键组成部分:
nt nr_swap_pages;
int nr_active_pages;
int nr_inactive_dirty_pages;
pg_data_t *pgdat_list;
struct list_head active_list;
struct list_head inactive_dirty_list;
在分析mm/filemap.c的时候分析过lru. mm/memory.c中也稍有涉及,主要是从lru
表中恢复页面映射.
这里再次用一个图表看一下lru队列和操作lru的几个关键函数的作用:
mm->rss *********************************
|swap_out->try_to_swap_out
active_list->******************************* |
/\ | refill_inactive_scan
|page_launder \ / \ /
inactive_dirty_list->*****************************************************
| page_launder
\ / (wite back to disk if necessary)
zone_t->inactive_clean_pages->************* --- reclaim_page->直接使用
| kreclaimd->reclaim_page
\ /
zone_t->free_area(buddy)->***********************
这里除了定义lru的几个队列再无其他和lru相关的东西了,我也就是这样简单
提提.
接下来是关于zone的一组参数:(没有什么好解释的)
static char *zone_names[MAX_NR_ZONES] = { "DMA", "Normal", "HighMem" };
static int zone_balance_ratio[MAX_NR_ZONES] = { 32, 128, 128, };
static int zone_balance_min[MAX_NR_ZONES] = { 10 , 10, 10, };
static int zone_balance_max[MAX_NR_ZONES] = { 255 , 255, 255, };
II)Buddy 算法核心
最后开始理解最终要的buddy系统,为了说明白算法到底如何工作,我们先来引入
几个重要的概念:
1)某组page:
假设order是2,则page_idx是0-3的是一组,4-7是一组.组中最低地址的那个页
面代表这个组连接进area的list链表,并且组内其他page不会存在于其他任何area
的list。
2)组对(相关组):
area中有两个重要的数据结构:
(1)list : 属于此order(area)的pgae,或者page组的链表
(2)*map : 每一位代表一对相关组(buddy)。所谓相关组就是可以合并成更高
order的两个连续page组,我们也称之为相关组.
map中的组对bit位的意义我们暂且不说,当释放时0代表这对相关组的其中一组还
在使用,无法合并,如果释放是1则代表可以合并。初始值是0,对应的list也为空。
map中bit的意义对于理解buddy算法很有好处,可惜一直未能彻底搞明白,这次希
望运气好些.
通过对初始化的分析看看map中bit位的变化:
1)初始为0,也代表配对page无法合并吗?(显然不是)list也为空.内存初始化调用所
有页面的释放函数,挂入area0 的list,对应位置1.
2)配对的page释放后对应位又变成0,从list摘出先释放的那个一齐放入area1.(如果
重复释放某个page,page组,就会出现严重错误)
3)分配的时候,从areaX的list中出队,mapbit从0变成1,如果这时释放刚分配的page,
1代表可以合并.如果另一个组也分配出去了,位图又变成0,代表释放的时候不能合并.
此时不会在分配这两个组了,因为list中这两个组都已经出队.
所以,操作顺序,bitmap,和list相互配合完成buddy的管理.
仔细考虑,总结一下,map中的bit含义其实是:
0,相关组的状态一致
1,相关组的状态不同
(god,是个异或)
所以分配的时候反转两次,释放的时候反转两次.在看上面的分析:
1)初始为0,连个页面都处于使用中. 释放其中一个的时候,bit为0,代表状态相同,即
都在使用中(我们是在释放啊),我们释放完成后当然无法合并.同时我们释放其中一个
后反转bit,bit变成1,代表现在状态不同了.
2)当配对的一个释放时,发现bit是1,代表状态不同.因为这在使用中,所以另一个已经
释放,故这个释放后就可以合并了.这就是为什么释放时bit为0代表可以合并.
3)请自己分析。
Buddy算法的核心函数之一,__free_pages_ok:释放page组,根据bitmap判断buddy
page组的状态,合并buddy page组到更高的order.
static void FASTCALL(__free_pages_ok (struct page *page, unsigned long order));
static void __free_pages_ok (struct page *page, unsigned long order)
{
unsigned long index, page_idx, mask, flags;
free_area_t *area;
struct page *base;
zone_t *zone; if (page->buffers)
BUG();
.......... page->flags &= ~((1<<PG_referenced) | (1<<PG_dirty));
page->age = PAGE_AGE_START;
zone = page->zone;
mask = (~0UL) << order; base = mem_map + zone->offset;
page_idx = page - base;
if (page_idx & ~mask) BUG();
index = page_idx >> (1 + order);
area = zone->free_area + order;
spin_lock_irqsave(&zone->lock, flags);
zone->free_pages -= mask;
while (mask + (1 << (MAX_ORDER-1))) { struct page *buddy1, *buddy2;
if (area >= zone->free_area + MAX_ORDER)
BUG();
if (!test_and_change_bit(index, area->map))
break;
buddy1 = base + (page_idx ^ -mask); buddy2 = base + page_idx;
if (BAD_RANGE(zone,buddy1))
BUG();
if (BAD_RANGE(zone,buddy2))
BUG();
memlist_del(&buddy1->list);
mask <<= 1; area++;
index >>= 1;
page_idx &= mask; }
memlist_add_head(&(base + page_idx)->list, &area->free_list);
spin_unlock_irqrestore(&zone->lock, flags);
......
}
Buddy系统的另一个核心函数,expand:当指定的order,low无页面可以分配的时候
从oredr为high的area分配,将high中一个页面组依次向低order的area拆分,一次一
半,直至order为low的那个area为止.如,我们要order为1的页面组,从order为3的开
始分配, 8=4+2+(2),(2)代表我们所要的页面组.
static inline struct page * expand (zone_t *zone, struct page *page,
unsigned long index, int low, int high, free_area_t * area)
{ unsigned long size = 1 << high;
while (high > low) { if (BAD_RANGE(zone,page))
BUG();
area--; high--; size >>= 1; memlist_add_head(&(page)->list, &(area)->free_list); MARK_USED(index, high, area); index += size; page += size; }
if (BAD_RANGE(zone,page))
BUG();
return page;
}
分析完expand后对于rmqueue应该不难了:
static struct page * rmqueue(zone_t *zone, unsigned long order)
{
free_area_t * area = zone->free_area + order;
unsigned long curr_order = order;
struct list_head *head, *curr;
unsigned long flags;
struct page *page;
spin_lock_irqsave(&zone->lock, flags);
do { head = &area->free_list;
curr = memlist_next(head);
if (curr != head) { unsigned int index;
page = memlist_entry(curr, struct page, list);
if (BAD_RANGE(zone,page))
BUG();
memlist_del(curr);
index = (page - mem_map) - zone->offset;
MARK_USED(index, curr_order, area); zone->free_pages -= 1 << order;
page = expand(zone, page, index, order, curr_order, area);
spin_unlock_irqrestore(&zone->lock, flags);
set_page_count(page, 1);
if (BAD_RANGE(zone,page))
BUG();
DEBUG_ADD_PAGE
return page;
} curr_order++;
area++;
} while (curr_order < MAX_ORDER);
spin_unlock_irqrestore(&zone->lock, flags);
return NULL;
}
III) __alloc_pages:基于zone的buddy系统分配的核心策略
先看一个内部接口函数,按照指定的可回收页面(free+inactive clean)的水
位寻找合适的zone分配page:
static struct page * __alloc_pages_limit(zonelist_t *zonelist,
unsigned long order, int limit, int direct_reclaim)
{zone_t **zone = zonelist->zones;
for (;;) {
zone_t *z = *(zone++);
unsigned long water_mark;
if (!z)
break;
if (!z->size)
BUG();
switch (limit) {
default:
case PAGES_MIN: water_mark = z->pages_min;
break;
case PAGES_LOW:
water_mark = z->pages_low;
break;
case PAGES_HIGH:
water_mark = z->pages_high;
}
if (z->free_pages + z->inactive_clean_pages > water_mark) {
struct page *page = NULL;
if (direct_reclaim && z->free_pages < z->pages_min + 8)
page = reclaim_page(z);
if (!page)
page = rmqueue(z, order);
if (page)
return page;
}
}
return NULL;
}
这里说的可回收页面包括两个部分即:free_pages+inactive_clean_pages.
__alloc_pages处于内核内存管理的最底层,无论slab,vmallc,kmalloc,mmap,brk
还是page cache,buffer都要通过__alloc_pages获取最基本的物理内存pages.
linux执行这样一种内存管理策略:
a)充分利用物理内存,建立各种cache,优化程序性能,减少磁盘操作.这一点和win
dows系统不同,windows系统中总是有很多内存空闲,即便是进行了大量的磁盘操作后.
而linux中真正空闲的物理内存几乎就看不到.
b)保证有足够的潜在物理内存(页面),可以立即加以回收,或称潜在可分配页面.通
过内核的守护进程kswapd,bdflush,kreclaimd的定期处理,加上每次内存分配对系统
的调整,即通过__alloc_pages所遇到的各种内存分配压力,不断的调整守护进程的工
作方向,保证系统拥有足够的潜在可回收内存.
先看看对内存页面有些什么样的保有量要求:
1)可分配页面的保有量要求:inactive_clean+free pages(in buddy pages)
系统的期望值是freepages.high + inactive_target / 3,inactive_target就是
min((memory_pressure >> INACTIVE_SHIFT),num_physpages / 4)).可见期望的保
有量有动态的因素在内.
现在的保有量是nr_free_pages() + nr_inactive_clean_pages();
mm/vmscan.c中的函数free_shortage,计算期望的可分配页面和现实之差距.如果
保有量合格,但看zone中的inbuddy free pages是比期望值少.只要有一个保有量不
合格,就必须立即加以调整.free_shortage请自己阅读.
2)潜在可分配页面的保有量要求:(buddyfree+inactiveclean+inactive_dirty)
期望保有量:freepages.high+inactive_target
现存量:
nr_free_pages()+nr_inactive_clean_pages()+nr_inactive_dirty_pages.
所做分析已注入代码:
struct page * __alloc_pages(zonelist_t *zonelist, unsigned long order)
{
zone_t **zone;
int direct_reclaim = 0;
unsigned int gfp_mask = zonelist->gfp_mask;
struct page * page;
memory_pressure++;
if (order == 0 && (gfp_mask & __GFP_WAIT) &&
!(current->flags & PF_MEMALLOC))
direct_reclaim = 1;
if (inactive_shortage() > inactive_target / 2 && free_shortage())
wakeup_kswapd(0);
else if (free_shortage() && nr_inactive_dirty_pages > free_shortage()
&& nr_inactive_dirty_pages >= freepages.high)
wakeup_bdflush(0);
try_again:
zone = zonelist->zones;
for (;;) {
zone_t *z = *(zone++);
if (!z)
break;
if (!z->size)
BUG();
if (z->free_pages >= z->pages_low) { page = rmqueue(z, order);
if (page)
return page;
} else if (z->free_pages < z->pages_min &&
waitqueue_active(&kreclaimd_wait)) {
wake_up_interruptible(&kreclaimd_wait);
}
}
page = __alloc_pages_limit(zonelist, order, PAGES_HIGH, direct_reclaim);
if (page)
return page;
page = __alloc_pages_limit(zonelist, order, PAGES_LOW, direct_reclaim);
if (page)
return page;
wakeup_kswapd(0);
if (gfp_mask & __GFP_WAIT) {
__set_current_state(TASK_RUNNING);
current->policy |= SCHED_YIELD;
schedule();
}
page = __alloc_pages_limit(zonelist, order, PAGES_MIN, direct_reclaim);
if (page)
return page;
if (!(current->flags & PF_MEMALLOC)) {
if (order > 0 && (gfp_mask & __GFP_WAIT)) {
zone = zonelist->zones;
current->flags |= PF_MEMALLOC; page_launder(gfp_mask, 1); current->flags &= ~PF_MEMALLOC; for (;;) {
zone_t *z = *(zone++);
if (!z)
break;
if (!z->size)
continue;
while (z->inactive_clean_pages) {
struct page * page;
page = reclaim_page(z);
if (!page)
break;
__free_page(page);
page = rmqueue(z, order); if (page)
return page;
}
}
}
if ((gfp_mask & (__GFP_WAIT|__GFP_IO)) == (__GFP_WAIT|__GFP_IO)) {
wakeup_kswapd(1);
memory_pressure++;
if (!order) goto try_again;
} else if (gfp_mask & __GFP_WAIT) { try_to_free_pages(gfp_mask);
memory_pressure++;
if (!order)
goto try_again;
}
}
zone = zonelist->zones;
for (;;) {
zone_t *z = *(zone++);
struct page * page = NULL;
if (!z)
break;
if (!z->size)
BUG();
if (direct_reclaim) {
page = reclaim_page(z);
if (page)
return page;
}
if (z->free_pages < z->pages_min / 4 &&
!(current->flags & PF_MEMALLOC))
continue;
page = rmqueue(z, order);
if (page)
return page;
}
printk(KERN_ERR "__alloc_pages: %lu-order allocation failed.\n", order);
return NULL;
}
与内存分配有关的函数还有:
unsigned long get_zeroed_page(int gfp_mask)
void __free_pages(struct page *page, unsigned long order)
void free_pages(unsigned long addr, unsigned long order)
另外还有几个用于统计内存压力的函数:
unsigned int nr_free_pages (void)
unsigned int nr_inactive_clean_pages (void)
unsigned int nr_free_buffer_pages (void)
unsigned int nr_free_highpages (void)
这些函数较为简单,不再分析.
IV)zone 初始化的相关函数
重点来看看2.4的zonelist.不知道你是否有疑问,其实2.4的zonelist用处不是
想象的那么大,还没有成熟,或者说还没有写完.
2.4的pgdata,zonelist的关系如下图:
+---------+ zone_list
pgdat->node_zonelist |zone_list|-----> +--------+
+---------+ | normal?+
256个 + dma? +
+---------+ | high? +
+--------+
zone_list中第一个zone的类型是由zone_list在node_zonelist中的索引所决定
的,即由GFP_XXX决定.
可惜看GFP的定义,根本不能够有256个,这是其一. 其次,pgdat中的zone_list都
是指向本node的zone,而不是依据距node的距离排列,这就起不到node分区的本意.看
函数 alloc_pages(mm/numa.c),是在pgdat之间遍历而已,并无优先级的考虑.
再看mm/page_alloc.c中的build_zonelists:
static inline void build_zonelists(pg_data_t *pgdat)
{
int i, j, k;
for (i = 0; i < NR_GFPINDEX; i++) { zonelist_t *zonelist;
zone_t *zone;
zonelist = pgdat->node_zonelists + i;
memset(zonelist, 0, sizeof(*zonelist));
zonelist->gfp_mask = i;
j = 0;
k = ZONE_NORMAL;
if (i & __GFP_HIGHMEM)
k = ZONE_HIGHMEM;
if (i & __GFP_DMA)
k = ZONE_DMA;
switch (k) {
default:
BUG();
case ZONE_HIGHMEM:
zone = pgdat->node_zones + ZONE_HIGHMEM;
if (zone->size) {
#ifndef CONFIG_HIGHMEM
BUG();
#endif
zonelist->zones[j++] = zone;
}
case ZONE_NORMAL:
zone = pgdat->node_zones + ZONE_NORMAL;
if (zone->size)
zonelist->zones[j++] = zone;
case ZONE_DMA:
zone = pgdat->node_zones + ZONE_DMA;
if (zone->size)
zonelist->zones[j++] = zone;
}
zonelist->zones[j++] = NULL;
}
}
和2.6的内核对比一下,2.6中结构变成:
+---------+ zone_list
pgdat->node_zonelist |zone_list|-----> +--------+
+---------+ | nodes*3+
3个 + +
+---------+ | +
| n.d.h | +--------+
不详细分析2.6中的相关代码了.
最后剩下函数free_area_init_core,在分析mm/memory.c的时候曾详细讨论过他
设置mem_map的方式,请参考理解free_area_init_core对mem_map的初始化,这里简
单讨论对zone和buddy的初始化,就不列出详细的代码了:
void __init free_area_init_core(int nid, pg_data_t *pgdat,
struct page **gmap, unsigned long *zones_size ,
unsigned long zone_start_paddr, unsigned long *zholes_size,
struct page *lmem_map)
{
struct page *p;
unsigned long i, j;
unsigned long map_size;
unsigned long totalpages, offset, realtotalpages;
unsigned int cumulative = 0;
....
...
map_size = (totalpages + 1)*sizeof(struct page);
if (lmem_map == (struct page *)0) {
lmem_map = (struct page *) alloc_bootmem_node(pgdat, map_size);
lmem_map = (struct page *)(PAGE_OFFSET +
MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET));
}
*gmap = pgdat->node_mem_map = lmem_map;
pgdat->node_size = totalpages;
pgdat->node_start_paddr = zone_start_paddr;
pgdat->node_start_mapnr = (lmem_map - mem_map);
for (p = lmem_map; p < lmem_map + totalpages; p++) {
set_page_count(p, 0);
SetPageReserved(p);
init_waitqueue_head(&p->wait);
memlist_init(&p->list);
}
offset = lmem_map - mem_map;
for (j = 0; j < MAX_NR_ZONES; j++) { zone_t *zone = pgdat->node_zones + j;
unsigned long mask;
unsigned long size, realsize;
realsize = size = zones_size[j];
if (zholes_size)
realsize -= zholes_size[j];
printk("zone(%lu): %lu pages.\n", j, size);
zone->size = size;
........ zone->zone_mem_map = mem_map + offset;
zone->zone_start_mapnr = offset;
zone->zone_start_paddr = zone_start_paddr;
for (i = 0; i < size; i++) {
struct page *page = mem_map + offset + i