您的位置:首页 > 运维架构 > Linux

Linux中的内存分配和释放之__alloc_pages()函数分析

2010-03-08 20:13 633 查看
在上篇文章的结尾,我们说会在接下来的文章分析分配函数的具体代码,结合我上篇文章说的伙伴机制和冷热区的概念,更好得去理解这个分配过程。好了,我们不再多说了,我们现在开始分析代码吧。

struct page * fastcall __alloc_pages(unsigned int gfp_mask, unsigned int order,struct zonelist *zonelist)

{
const int wait = gfp_mask & __GFP_WAIT;//gfp_mask是申请内存时用到的控制字,这一句就是为了检测我们的控制字里面是

//否有__GPF_WAIT这个属性,我们先来看些宏定义吧,这样你会更清楚的。

//#define GFP_LEVEL_MASK (__GFP_WAIT|__GFP_HIGH|__GFP_IO|__GFP_FS| /
__GFP_COLD|__GFP_NOWARN|__GFP_REPEAT| /
__GFP_NOFAIL|__GFP_NORETRY|__GFP_NO_GROW|__GFP_COMP)

//#define GFP_ATOMIC (__GFP_HIGH)
//#define GFP_NOIO (__GFP_WAIT)
//#define GFP_NOFS (__GFP_WAIT | __GFP_IO)
//#define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)
//#define GFP_USER (__GFP_WAIT | __GFP_IO | __GFP_FS)
//#define GFP_HIGHUSER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HIGHMEM)

//可以看到上面的每个define都代表一个控制字,上面大部分都有__GPF_WAIT,只是GFP_ATOMIC是不包含的。如果当申请内存的控制字有__GFP_WAIT时,我们就要求一直等待到申请到内存为止,反之进程就进入睡眠状态,不进行等待。

unsigned long min;
struct zone **zones, *z;
struct page *page;
struct reclaim_state reclaim_state;
struct task_struct *p = current;
int i;
int alloc_type;
int do_retry;
int can_try_harder;

might_sleep_if(wait);//如果wait不等于0,则调用该函数的进程可能休眠。

can_try_harder = (unlikely(rt_task(p)) && !in_interrupt()) || !wait;//如果当前进程是实时进程的话同时又不在软中断和硬中断

//或者是wait=0。这样can_try_harder=1。

//#define rt_task(p) (unlikely((p)->prio < MAX_RT_PRIO))当前进程的优先级prio是否小于MAX_RT_PRIO

//#define MAX_RT_PRIO MAX_USER_RT_PRIO其中MAX_USER_RT_PRIO=100。如果prio<100

//我们就说当前进程是实时进程。

zones = zonelist->zones;//zonelist是struct node中的一个成员,它表示系统内所有normal内存页区的连接链表。

//我们这里的zonelist->zones是这个节点的normal内存zone的struct zoon指针。我们一般把zones称为

//struct zoon结构指针列表,这个表是以本次内存node的对应类型页区的struct zoon为头指针,即头指针是

//指向struct zoon这个结构体的。整个表都是结构类型为struct zoon的指针。

if (unlikely(zones[0] == NULL)) {

return NULL;

}//如果发现头指针为空,即没有指向struct zoon的有效指针,我们就直接返回错误。

alloc_type = zone_idx(zones[0]);//zone_idx(zone)=((zone) - (zone)->zone_pgdat->node_zones)。我们由

//zone_pgdat知道zoon所在的内存node,我们通过node又可以知道其成员node_zones.我们

//通过页区数组,和本页区的位置可以求得这个页区的类型。

for (i = 0; (z = zones[i]) != NULL; i++) {//遍历struct zoon结构指针列表

min = z->pages_low + (1<<order) + z->protection[alloc_type];//这里有变化的是两个变量,一个是order,alloc_type

//表示在正常情况下,如果要在一个zoon中申请1<<order页的内存

//时,就要确保至少有min这么多的空闲内存页。

if (z->free_pages < min)
continue;//如果本zoon的空闲页比min要小的话,就查看下一个内存页。

page = buffered_rmqueue(z, order, gfp_mask);//函数成功时,会返回申请内存空间的首页struct page结构指针。这个函

//会在后面展开仔细分析的,现在就记住它的功能(内存分配)。

if (page)

goto got_pg;

}

for (i = 0; (z = zones[i]) != NULL; i++)//如果执行到这里,说明内存分配失败
wakeup_kswapd(z);//唤醒所有页区所在节点处于TASK_INTERRUPTIBLE状态的内存置换进程然后异步回收内存

for (i = 0; (z = zones[i]) != NULL; i++) {

min = z->pages_min;//刚才的pages_low属于正常情况,如果选择pages_min说明是最差的情况

if (gfp_mask & __GFP_HIGH)//如果传进来的是GFP_ATOMIC标志,本次申请是原子操作,必须要成功。
min /= 2;
if (can_try_harder)
min -= min / 4;
min += (1<<order) + z->protection[alloc_type];//除了GFP_ATOMIC标志时,我们才减少min的3/4来提供更多的内存来分

//配,其他的只是减少1/4而已。

if (z->free_pages < min)
continue;//如果空闲页不满足的话,我们进行下一个页区进行查找。

page = buffered_rmqueue(z, order, gfp_mask);
if (page)
goto got_pg;
}//如果执行到下面的if,说明上面的分配还是失败。

if ((p->flags & (PF_MEMALLOC | PF_MEMDIE)) && !in_interrupt()) {//如果当前进程允许本次申请的内存可以被释放,并且不

//处于软硬中断的状态,我们不顾忌必须保留最小空闲内存页,强行分配

for (i = 0; (z = zones[i]) != NULL; i++) {
page = buffered_rmqueue(z, order, gfp_mask);
if (page)
goto got_pg;
}
goto nopage;
}

if (!wait)
goto nopage;//如果前两次内存分配失败,而且当前进程又不允许分配的内存可以被以后释放,同时我们的进程不需等待

rebalance://允许等待,开始同步收回内存。

p->flags |= PF_MEMALLOC;//当前进程的struct task_struct结构标志字flags或上PF_MEMALLOC标志
reclaim_state.reclaimed_slab = 0;//将slab回收器清零
p->reclaim_state = &reclaim_state;

try_to_free_pages(zones, gfp_mask, order);//尝试从页区列表zones的页区释放1<<order页内存,该函数会在后面细将的。

p->reclaim_state = NULL;
p->flags &= ~PF_MEMALLOC;这里又把标志清零了。

for (i = 0; (z = zones[i]) != NULL; i++) {
min = z->pages_min;
if (gfp_mask & __GFP_HIGH)
min /= 2;
if (can_try_harder)
min -= min / 4;
min += (1<<order) + z->protection[alloc_type];

if (z->free_pages < min)
continue;

page = buffered_rmqueue(z, order, gfp_mask);
if (page)
goto got_pg;
}//以上for和第二遍申请内存的操作过程一样,这里的意思是再执行一遍。

//以上申请还是失败

do_retry = 0;
if (!(gfp_mask & __GFP_NORETRY)) {//如果没有设置不重试标志
if ((order <= 3) || (gfp_mask & __GFP_REPEAT))//如果order<3或者设置了反复操作标志,则重试
do_retry = 1;
if (gfp_mask & __GFP_NOFAIL)//如果设置了不能失败标志,也重试
do_retry = 1;
}
if (do_retry) {
blk_congestion_wait(WRITE, HZ/50);//睡眠两个滴答时间
goto rebalance;//再回到rebalance重试
}

nopage:
if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {//如果没有设置屏蔽告警标志,那么就按照打印频率限制打印信息,

//如果当超出一定频率后,printk_ratelimit()函数会返回0。做这种限制的

//作用是不影响系统速度,还有就是怕文件日志溢出。
printk(KERN_WARNING "%s: page allocation failure."
" order:%d, mode:0x%x/n",
p->comm, order, gfp_mask);
dump_stack();
}
return NULL;//返回空指针。

got_pg:
zone_statistics(zonelist, z);//更新zone->pageset[cpu]中的统计数据
kernel_map_pages(page, 1 << order, 1);
return page;//返回申请到的内存首页的struct page结构指针
}

很好,到这里把__alloc_pages()函数大概了分析了一遍,里面涉及的核心函数buffered_rmqueue()函数,我们会在下面仔细分。

static struct page * buffered_rmqueue(struct zone *zone, int order, int gfp_flags)

{
unsigned long flags;
struct page *page = NULL;
int cold = !!(gfp_flags & __GFP_COLD);

if (order == 0) {//如果只申请一页,那么就从当前cpu的高速缓存内存中申请。
struct per_cpu_pages *pcp;

pcp = &zone->pageset[get_cpu()].pcp[cold];//获取zoon的当前处理器的高速缓存内存描述结构指针。
local_irq_save(flags);
if (pcp->count <= pcp->low)//如果高速缓存内存的count小于low时,
pcp->count += rmqueue_bulk(zone, 0,pcp->batch, &pcp->list);//调用此函数从伙伴系统中分配batch空闲内存到高速缓存

//内存中。我们会在后面细讲的。
if (pcp->count) {//如果pcp->count大于pcp->low时,就可以直接在高速缓存内存内申请一页内存。
page = list_entry(pcp->list.next, struct page, lru);//我们从pcp->list链表开始的第一个lru起,去寻找相应的struct page结

//构体。
list_del(&page->lru);由于被分配出去了,所以高速缓存内存中不再包含这页内存,所以从链表里删除这一项。
pcp->count--;//相应的当前页数也要减少。
}
local_irq_restore(flags);
put_cpu();//使能抢占。
}

if (page == NULL) {//如果上述申请失败,或是order大于0
spin_lock_irqsave(&zone->lock, flags);
page = __rmqueue(zone, order);//调用函数申请内存。我们会在下面细讲的。
spin_unlock_irqrestore(&zone->lock, flags);
}

if (page != NULL) {//如果申请成功,要进行检测。
BUG_ON(bad_range(zone, page));//判断申请到得页是否在本次zone的范围内,不是的话系统崩溃。
mod_page_state_zone(zone, pgalloc, 1 << order);//这个函数会先判断zoon所属哪类页区,然后更新当前处理器的页状态

//描述结构体per_cpu_pages_state中的成员pgalloc_(normal,highmem,dma)

//我们称这个成员为分配页数计数器。
prep_new_page(page, order);//这里就是对struct page结构体中的flags成员进行一些维护。
if (order && (gfp_flags & __GFP_COMP))如果order>0并且设置了把申请到的内存页组合成组合页。
prep_compound_page(page, order);
}
return page;//返回申请到的内存空间的首页内存页的struct page结构指针。
}
我们先来分析上述的requeue_bulk()函数,一下是它的具体代码.

static int rmqueue_bulk(struct zone *zone, unsigned int order, unsigned long count, struct list_head *list)
{
unsigned long flags;
int i;
int allocated = 0;
struct page *page;

spin_lock_irqsave(&zone->lock, flags);
for (i = 0; i < count; ++i) {//执行count次内存申请操作
page = __rmqueue(zone, order);//下面会展开来讲解的。
if (page == NULL)
break;
allocated++;//统计计数,等下要返回此值,表示申请成功的次数。
list_add_tail(&page->lru, list);//成功后把申请成功的内存空间的首页内存页对应的struct page的lru挂在相应的链表中。
}
spin_unlock_irqrestore(&zone->lock, flags);
return allocated;
}//这个函数是用于申请多次的内存空间的,函数里面的__rmqueue()函数用于一次的申请,我们来仔细讲解一下。

static struct page *__rmqueue(struct zone *zone, unsigned int order)
{
struct free_area * area;
unsigned int current_order;
struct page *page;
unsigned int index;

for (current_order = order; current_order < MAX_ORDER; ++current_order) {
area = zone->free_area + current_order;
if (list_empty(&area->free_list))
continue;//如果发现该order对应的空闲内存区域的链表为空,说明这里没有空闲区域可以分配,那就查询下一个

//区域。

page = list_entry(area->free_list.next, struct page, lru);//要是链表不为空,我们就从这个链表的第一个开始,寻找对应

//的struct page,这样我们就可以访问到这片空闲区域。
list_del(&page->lru);//我们把这些区域分配出去,这样就把描述这个空闲区域的链表free_list中删除这一项。
index = page - zone->zone_mem_map;//求出偏移页数,这个偏移是相对于本内存node的开始页。
if (current_order != MAX_ORDER-1)//如果不超过范围的话
MARK_USED(index, current_order, area);//我们就改变free_area->map中相应位,位码表--map反映了他与伙伴区域的

//的关系发生变化,有可能是两个状态时一样,现在就不再一样了。也有可能本来

//状态不一样的,现在一样了。
zone->free_pages -= 1UL << order;//由于被分配出去了,所以现在的内存页区的空闲页数也要减少了。
return expand(zone, page, index, order, current_order, area);//如果分配的current_order>order的话,就说明申请的

//内存空间要比要求的大,这样就需要调整,把不必要的恢复为空闲内存。
}

return NULL;
}

我们这里要讲解一下expand()函数,一下是它的具体代码。

static inline struct page * expand(struct zone *zone, struct page *page,unsigned long index, int low, int high, struct free_area *area)
{
unsigned long size = 1 << high;//现在已经分配的内存页数。

while (high > low) {
area--;//我们先把一般的内存页恢复,对应的free_area
也要减少,这样才可以访问到其free_list链表
high--;//这个是上面说的current_order的值,我们也要相应减少
size >>= 1;//内存页数减半
BUG_ON(bad_range(zone, &page[size]));//看看size这么大内存区域的首页是否在该内存zoon的范围下,不是则系统崩溃
list_add(&page[size].lru, &area->free_list);//如果无误,我们就把这一段空间恢复为空闲区域中。
MARK_USED(index + size, high, area);//index+size是要恢复的内存页号,我们这里也要改变它的伙伴位码。
}
return page;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: