伙伴分配器
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内核深度解析》