伙伴分配器
6 释放页
页分配器提供了以下释放页的接口。
-
(1)void __free_pages(struct page *page, unsigned int order),第一个参数是第一个物理页的page实例的地址,第二个参数是阶数。
-
(2)void free_pages(unsigned long addr, unsigned int order),第一个参数是第一个物理页的起始内核虚拟地址,第二个参数是阶数。函数__free_pages的代码如下:
1-__free_pages
函数__free_pages的代码如下:
mm/page_alloc.c
void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page)) {
if (order == 0)
free_hot_cold_page(page, false);
else
__free_pages_ok(page, order);
}
}
首先把页的引用计数减1,只有页的引用计数变成零,才真正释放页:如果阶数是0,不还给伙伴分配器,而是当作缓存热页添加到每处理器页集合中;如果阶数大于0,调用函数__free_pages_ok以释放页。
2-free_hot_cold_page
函数free_hot_cold_page把一页添加到每处理器页集合中,如果页集合中的页数量大于或等于高水线,那么批量返还给伙伴分配器。第二个参数cold表示缓存冷热程度,主动释放的页作为缓存热页,回收的页作为缓存冷页,因为回收的是最近最少使用的页。
mm/page_alloc.c
void free_hot_cold_page(struct page *page, bool cold)
{
struct zone *zone = page_zone(page);
struct per_cpu_pages *pcp;
unsigned long flags;
unsigned long pfn = page_to_pfn(page);
int migratetype;
if (! free_pcp_prepare(page))
return;
migratetype = get_pfnblock_migratetype(page, pfn); /* 得到页所属页块的迁移类型 */
set_pcppage_migratetype(page, migratetype); /* page->index保存真实的迁移类型 */
local_irq_save(flags);
__count_vm_event(PGFREE);
/*
* 每处理器集合只存放不可移动、可回收和可移动这3种类型的页,
* 如果页的类型不是这3种类型,处理方法是:
* (1)如果是隔离类型的页,不需要添加到每处理器页集合,直接释放;
* (2)其他类型的页添加到可移动类型链表中,page->index保存真实的迁移类型。
*/
if (migratetype >= MIGRATE_PCPTYPES) {
if (unlikely(is_migrate_isolate(migratetype))) {
free_one_page(zone, page, pfn, 0, migratetype);
goto out;
}
migratetype = MIGRATE_MOVABLE;
}
/* 添加到对应迁移类型的链表中,如果是缓存热页,添加到首部,否则添加到尾部 */
pcp = &this_cpu_ptr(zone->pageset)->pcp;
if (! cold)
list_add(&page->lru, &pcp->lists[migratetype]);
else
list_add_tail(&page->lru, &pcp->lists[migratetype]);
pcp->count++;
/* 如果页集合中的页数量大于或等于高水线,那么批量返还给伙伴分配器 */
if (pcp->count >= pcp->high) {
unsigned long batch = READ_ONCE(pcp->batch);
free_pcppages_bulk(zone, batch, pcp);
pcp->count -= batch;
}
out:
local_irq_restore(flags);
}
3-__free_pages_ok
函数__free_pages_ok负责释放阶数大于0的页块,最终调用到释放页的核心函数__free_one_page,算法是:如果伙伴是空闲的,并且伙伴在同一个内存区域,那么和伙伴合并,注意隔离类型的页块和其他类型的页块不能合并。
算法还做了优化处理:假设最后合并成的页块阶数是order,如果order小于(MAX_ORDER−2),则检查(order+1)阶的伙伴是否空闲,如果空闲,那么order阶的伙伴可能正在释放,很快就可以合并成(order+2)阶的页块。为了防止当前页块很快被分配出去,把当前页块添加到空闲链表的尾部。
函数__free_pages_ok的代码如下:
mm/page_alloc.c
__free_pages_ok() -> free_one_page() -> __free_one_page()
static inline void __free_one_page(struct page *page,
unsigned long pfn,
struct zone *zone, unsigned int order,
int migratetype)
{
unsigned long combined_pfn;
unsigned long uninitialized_var(buddy_pfn);
struct page *buddy;
unsigned int max_order;
/* pageblock_order是按可移动性分组的阶数 */
max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1);
…
continue_merging:
/*如果伙伴是空闲的,和伙伴合并,重复这个操作直到阶数等于(max_order-1)。*/
while (order < max_order - 1) {
buddy_pfn = __find_buddy_pfn(pfn, order); /* 得到伙伴的起始物理页号 */
buddy = page + (buddy_pfn - pfn); /* 得到伙伴的第一页的page实例 */
if (! pfn_valid_within(buddy_pfn))
goto done_merging;
/* 检查伙伴是空闲的并且在相同的内存区域 */
if (! page_is_buddy(page, buddy, order))
goto done_merging;
/*
* 开启了调试页分配的配置宏CONFIG_DEBUG_PAGEALLOC,伙伴充当警戒页。
*/
if (page_is_guard(buddy)) {
clear_page_guard(zone, buddy, order, migratetype);
} else {/* 伙伴是空闲的,把伙伴从空闲链表中删除 */
list_del(&buddy->lru);
zone->free_area[order].nr_free--;
rmv_page_order(buddy);
}
combined_pfn = buddy_pfn & pfn;
page = page + (combined_pfn - pfn);
pfn = combined_pfn;
order++;
}
if (max_order < MAX_ORDER) {
/*
* 运行到这里,意味着阶数大于或等于分组阶数pageblock_order,
* 阻止把隔离类型的页块和其他类型的页块合并
*/
if (unlikely(has_isolate_pageblock(zone))) {
int buddy_mt;
buddy_pfn = __find_buddy_pfn(pfn, order);
buddy = page + (buddy_pfn - pfn);
buddy_mt = get_pageblock_migratetype(buddy);
/*如果一个是隔离类型的页块,另一个是其他类型的页块,不能合并 */
if (migratetype ! = buddy_mt
&& (is_migrate_isolate(migratetype) ||
is_migrate_isolate(buddy_mt)))
goto done_merging;
}
/* 如果两个都是隔离类型的页块,或者都是其他类型的页块,那么继续合并 */
max_order++;
goto continue_merging;
}
done_merging:
set_page_order(page, order);
/*
* 最后合并成的页块阶数是order,如果order小于(MAX_ORDER-2),
* 则检查(order+1)阶的伙伴是否空闲,如果空闲,那么order阶的伙伴可能正在释放,
* 很快就可以合并成(order+2)阶的页块。为了防止当前页块很快被分配出去,
* 把当前页块添加到空闲链表的尾部
*/
if ((order < MAX_ORDER-2) && pfn_valid_within(buddy_pfn)) {
struct page *higher_page, *higher_buddy;
combined_pfn = buddy_pfn & pfn;
higher_page = page + (combined_pfn - pfn);
buddy_pfn = __find_buddy_pfn(combined_pfn, order + 1);
higher_buddy = higher_page + (buddy_pfn - combined_pfn);
if (pfn_valid_within(buddy_pfn) &&
page_is_buddy(higher_page, higher_buddy, order + 1)) {
list_add_tail(&page->lru,
&zone->free_area[order].free_list[migratetype]);
goto out;
}
}
/* 添加到空闲链表的首部 */
list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
out:
zone->free_area[order].nr_free++;
}
参考来自前辈的书籍:《Linux内核深度解析》
1067

被折叠的 条评论
为什么被折叠?



