接上文
大段代码警告
本文主要讲物理页面的分配以代码为主
有以下四个函数
请主要关注前三个函数. 特别是rmqueue
与expand
函数.
核心流程: __alloc_pages
→ \rightarrow →rmqueue
→ \rightarrow →expend
/*
* This is the 'heart' of the zoned buddy allocator:
* 函数参数
1.gfp_mask : 分配请求标志
2.order : 大小, 根据order 分配 2^order个物理页帧
3.zonelist : 指向node的node_zonelists某个元素, 里面指定了寻找要分配物理页的zone次序
* 函数功能:
分配2^order个页面
* 函数返回值
返回物理页帧描述符page, page指向页面块中第一个页面的起始地址。
*/
struct page * __alloc_pages(unsigned int gfp_mask, unsigned int order, zonelist_t *zonelist)
{
unsigned long min;
zone_t **zone, * classzone;
struct page * page;
int freed;
/*
1.zone指向zones数组,
2.classzone指向zones数组第一个元素
3.根据order计算实际分配页数(最少分配一个)
*/
zone = zonelist->zones;
classzone = *zone;
min = 1UL << order; //1UL代表 unsigned long(无符号长整型数)
/*
从第一个zone开始, 将请求大小与zone水位比较.
如果zone中的空闲页数大于 水位 pages_low+min
那么调用rmqueue分配物理页,
分配成功,返回第一个物理页的描述符地址
否则, 查询下一个zone
*/
for (;;) {
zone_t *z = *(zone++); // 相当于zone_t *z = *(zone[i])
if (!z)
// 循环结束, 即遍历完数组,退出循环
break;
min += z->pages_low; //累加计算水位
if (z->free_pages > min) { // 空闲页面足够多, 即 z->free_pages > ((1UL<< order) + z->pages_low + 之前pages->low的累加和)
page = rmqueue(z, order);// 从z中分配 2^order个页帧
if (page) // 分配成功
return page; //返回物理页帧描述符page, page指向页面块中第一个页面的起始地址。
}
}
/*
上面分配未成功,即各zone都超过水位pages_low了,设置classzone的need_balance标志
同时唤醒 kswapd, 期待它能将某些物理页交换出去
*/
classzone->need_balance = 1;
mb();
if (waitqueue_active(&kswapd_wait))
//唤醒 kswapd
wake_up_interruptible(&kswapd_wait);
/*
1.kswapd唤醒后, 重新检查有没有空闲页
2.重新顺序遍历zonelist中的各个zone
3.如果zone的空闲页数大于请求大小+水位pages_min,调用rmqueue分配
其中如果请求标志为不可等, 那么将水位暂时除以4再比较
4.若分配成功,则返回首页描述符
*/
zone = zonelist->zones;
min = 1UL << order;
for (;;) {
unsigned long local_min;
zone_t *z = *(zone++); // 相当于zone_t *z = *(zone[i])
if (!z)
// 循环结束, 即遍历完数组,退出循环
break;
local_min = z->pages_min;
if (!(gfp_mask & __GFP_WAIT)) //请求标志不可等待
local_min >>= 2; // local_min=(local_min/4)
min += local_min; //累加水位
if (z->free_pages > min) { //足够的空闲页面
page = rmqueue(z, order);// 从z中分配 2^order个页帧
if (page)
//分配成功,返回page, page指向页面块中第一个页面的起始地址。
return page;
}
}
/* here we're in the low on memory slow path */
rebalance:
/*
上述分配未成功, 检查当前进程标志,
如果具有PF_MEMALLOC 或 PF_MEMDIE其中的一个, 直接调用rmqueue分配
通常情况下, 进程标志为PF_MEMALLOC 或 PF_MEMDIE的 可能是 kswapd, oom killers
*/
if (current->flags & (PF_MEMALLOC | PF_MEMDIE)) {
// 具有PF_MEMALLOC 或 PF_MEMDIE其中的一个, 直接调用rmqueue分配
zone = zonelist->zones; //zones数组
for (;;) {
zone_t *z = *(zone++);//遍历数组
if (!z)
//数据遍历完成,跳出循环
break;
//直接从z中分配 2^order个页帧
page = rmqueue(z, order);
if (page)
//分配成功,返回page, page指向页面块中第一个页面的起始地址。
return page;
}
//分配不成功, 返回NULL
return NULL;
}
/*
没有足够的空闲物理页满足请求
如果请求标志又是不可等, 直接返回NULL
*/
/* Atomic allocations - we can't balance anything */
if (!(gfp_mask & __GFP_WAIT))
//没有足够的物理页帧满足请求, 且请求标志是不可等待
//直接返回NULL
return NULL;
//1.尝试将classzone中的物理页释放
page = balance_classzone(classzone, gfp_mask, order, &freed);
if (page)
//1.1释放成功, 返回page
return page;
//1.2 这里即,balance_classzone释放未成功
/*
再次查看水位 pages_min,如果空闲页数够了,调用rmqueue分配
再次查看原因, 上面操作可能释放了某些物理页
*/
zone = zonelist->zones;
min = 1UL << order;
for (;;) {
zone_t *z = *(zone++); //遍历数组
if (!z)
//遍历完成
break;
//累加水位
min += z->pages_min;
if (z->free_pages > min) {
//有足够的空闲页面, 分配2^order个页面
page = rmqueue(z, order);
if (page)
//返回page, page指向页面块中第一个页面的起始地址。
return page;
}
}
/*请求太大了 kswapd操作代价很高, 即不分配连续的大块内存
Don't let big-order allocations loop */
if (order > 3)
return NULL;
/*等待CPU调度, 跳转到rebalance执行代码
Yield for kswapd, and try again */
current->policy |= SCHED_YIELD; //调度策略为等待调度
__set_current_state(TASK_RUNNING); //设置当前进程为运行状态
schedule(); //进行调度
goto rebalance;//回到rebalance标志再次分配页框
}
static struct page * rmqueue(zone_t *zone, unsigned int order){
free_area_t * area = zone->free_area + order; // 根据order将area指向相应的内存分组
unsigned int curr_order = order;
struct list_head *head, *curr;
unsigned long flags;
struct page *page;
spin_lock_irqsave(&zone->lock, flags); //上锁
do {
//指定的order没有页面了, 依次向高order的area遍历
// head指向free_list头部, curr指向第一个物理页
head = &area->free_list;
curr = memlist_next(head);
if (curr != head) { // 还有页面可以分配
unsigned int index;
//free_list不空, 从list中得到描述符
page = memlist_entry(curr, struct page, list);
//1.如果该物理页非法, 出错
if (BAD_RANGE(zone,page))
BUG();
//2.否则将该物理页从free_list中删除
memlist_del(curr);
index = page - zone->zone_mem_map; //得到当前 page在zone_mem_map数组中的下标
if (curr_order != MAX_ORDER-1)
/*如果不是在最大那组分配的, 将area的map相应位设为1*/
MARK_USED(index, curr_order, area);
zone->free_pages -= 1UL << order; //减少被分配的页数
//把大块连续物理内存分配到 小的连续的物理内存
page = expand(zone, page, index, order, curr_order, area);
spin_unlock_irqrestore(&zone->lock, flags); //释放锁
set_page_count(page, 1); //设置count为1,即page的引用计数为1
//对page进行合法性检查
if (BAD_RANGE(zone,page))
BUG();
if (PageLRU(page)) //page在active链表或者inactive链表中
BUG();
if (PageActive(page)) //page在active链表中
BUG();
return page;
}
/*
若该组没有找到连续的物理页来满足分配,尝试从更大的组中分配,
重复遍历直到遍历完最大组*/
curr_order++;
area++;
} while (curr_order < MAX_ORDER);
spin_unlock_irqrestore(&zone->lock, flags); //释放锁
//一直没有找到, 返回NULL
return NULL;
}
大块连续物理内存拆分为小块物理内存
/*
*Buddy 核心函数
*函数功能:
当指定的order,low无页面可以分配的时候
将从order大的area一次一半拆分为 order小的area
*函数参数
low : 目标order
high : 当前free_area数组的下标
page : order为high的页面组
index: page组在order为high的area中的位图索引
*/
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;//high对应的连续物理内存的页框数
while (high > low) {
//从高order到低order遍历 for(int i=high;i>low;i--)
if (BAD_RANGE(zone,page)) //合法性检查
BUG();
area--; // 此时high对应的low area
high--; //从高order 向low order遍历
size >>= 1;//大块物理内存拆分为小块物理内存(一次一半)
memlist_add_head(&(page)->list, &(area)->free_list); //将拆分的头一个组加入低级别的area
MARK_USED(index, high, area);// 在此低级别的area中反转状态
index += size; //低 order的位图索引
page += size; //留下同一对的后一个组分配给用户
}
if (BAD_RANGE(zone,page))//合法性检查
BUG();
//返回物理页帧描述符
return page;
}
static struct page * balance_classzone(zone_t * classzone, unsigned int gfp_mask, unsigned int order, int * freed)
{
struct page * page = NULL;
int __freed = 0;
// 1.不允许等待
if (!(gfp_mask & __GFP_WAIT))
goto out;//2.跳转到out, 退出函数
//3.若在中断中, 出错
if (in_interrupt())
BUG();
current->allocation_order = order; //设置当前进程的order
current->flags |= PF_MEMALLOC | PF_FREE_PAGES; //设置current的标志位
// 直接从classzone中释放页
__freed = try_to_free_pages(classzone, gfp_mask, order);
//再次清除标志位, 来保证free functions 不会继续把paes加入local_pages链表
current->flags &= ~(PF_MEMALLOC | PF_FREE_PAGES);
//判断pages是否在local_pages链表
if (current->nr_local_pages) {
//在local_pages链表
struct list_head * entry, * local_pages;
struct page * tmp;
int nr_pages;
//从链表头开始
local_pages = ¤t->local_pages;
if (likely(__freed)) { //分支预测优化...
// pages 被try_to_free_pages_zones()释放
/* pick from the last inserted so we're lifo */
entry = local_pages->next;
do { /* 循环, 直到找到一个tmp->index==order,且
tmp->zone等于classzone*/
//从链表中获得页帧
tmp = list_entry(entry, struct page, list);
/*
1.找到想要的的页面
2.判断tmp->index与order,且tmp属于正确的zone
*/
if (tmp->index == order && memclass(tmp->zone, classzone)) {
list_del(entry); // 从链表中删除
current->nr_local_pages--; //包含页帧减1
set_page_count(tmp, 1); // 设置tmp的count为1,准备释放
page = tmp;// 设置page准备返回, tmp要释放在local_pages中剩下的页帧
//保证释放的安全性
if (page->buffers) // 如果page存在缓冲区
BUG();
if (page->mapping)// 如果page存在文件映射
BUG();
if (!VALID_PAGE(page)) //如果page失效
BUG();
if (PageSwapCache(page)) //page属于交换区
BUG();
if (PageLocked(page)) //如果page被上锁
BUG();
if (PageLRU(page)) //如果page在active链表或者inactive链表中
BUG();
if (PageActive(page))//如果page在active链表
BUG();
if (PageDirty(page))//如果page为脏页
BUG();
break;
}
//循环, 找到下一个page继续检查
} while ((entry = entry->next) != local_pages);
}
//得到准备释放页帧的数量
nr_pages = current->nr_local_pages;
/* free in reverse order so that the global order will be lifo */
//遍历完local_pages链表
while ((entry = local_pages->prev) != local_pages) {
// 将entry从链表删除
list_del(entry);
//从entry中得到页帧
tmp = list_entry(entry, struct page, list);
//释放页帧
__free_pages_ok(tmp, tmp->index);
/*
如果nr_pages==0 但是此时list中还有物理页帧,
说明有人向local_pages中加入页帧,所以调用BUG(),
即如果nr_pages小于0,即有人向local_pages加入页帧,
所以要调用BUG()
*/
if (!nr_pages--)
BUG();
}
//表示页帧都被释放了
current->nr_local_pages = 0;
}
out:
//更新freed,说明释放页与否
*freed = __freed;
return page;//返回根据order与classzone找到的页帧
}