目录
页分配器API
如何分配页面
分配一个页面情况
分配多个页面情况
页分配器API
? ? ? ? 物理内存管理伙伴系统只能分配2的整数幂个页,即2 的order幂次页
申请页
- alloc_pages(mask, order)分配2 的order幂次页并返回一个struct page的实例,表示分配的内存块的起始页。alloc_page(mask)是前者在order = 0情况下的简化形式,只分配一页。
- get_zeroed_page(mask)分配一页并返回一个page实例,页对应的内存填充0(所有其他函数,分配之后页的内容是未定义的)。
- __get_free_pages(mask, order)和__get_free_page(mask)的工作方式与上述函数相同,但返回分配内存块的虚拟地址,而不是page实例。
- get_dma_pages(gfp_mask, order)用来获得适用于DMA的页。
释放页
- free_page(struct page *)和free_pages(struct page *, order)用于将一个或2order页返 回给内存管理子系统。内存区的起始地址由指向该内存区的第一个page实例的指针表示。
- __free_page(addr)和__free_pages(addr, order)的语义类似于前两个函数,但在表示需要释放的内存区时,使用了虚拟内存地址而不是page实例
如何分配页面
????????实际完成分配任务的是 rmqueue 函数
static inline
struct page *rmqueue(struct zone *preferred_zone,
struct zone *zone, unsigned int order,
gfp_t gfp_flags, unsigned int alloc_flags,
int migratetype)
{
unsigned long flags;
struct page *page;
if (likely(order == 0)) {
//order=0 是一个页面,从pclist中分配
page = rmqueue_pcplist(preferred_zone, zone, gfp_flags,
migratetype, alloc_flags);
goto out;
}
WARN_ON_ONCE((gfp_flags & __GFP_NOFAIL) && (order > 1));
//加锁关中断
spin_lock_irqsave(&zone->lock, flags);
do {
page = NULL;
if (alloc_flags & ALLOC_HARDER) {
//多个内存页面从free_area中分配
page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);
if (page)
trace_mm_page_alloc_zone_locked(page, order, migratetype);
}
if (!page)
//最后调用__rmqueue_smallest函数
page = __rmqueue(zone, order, migratetype, alloc_flags);
} while (page && check_new_pages(page, order));
spin_unlock(&zone->lock);
if (!page)
goto failed;
...
local_irq_restore(flags);
out:
...
failed:
local_irq_restore(flags);
return NULL;
}
分配一个页面情况
/* Lock and remove page from the per-cpu list */
static struct page *rmqueue_pcplist(struct zone *preferred_zone,
struct zone *zone, gfp_t gfp_flags,
int migratetype, unsigned int alloc_flags)
{
struct per_cpu_pages *pcp;
struct list_head *list;
struct page *page;
unsigned long flags;
//关中断
local_irq_save(flags);
//获取当前CPU下的pcp
pcp = &this_cpu_ptr(zone->pageset)->pcp;
//获取pcp下迁移的list链表
list = &pcp->lists[migratetype];
//摘取list上的pages结构
page = __rmqueue_pcplist(zone, migratetype, alloc_flags, pcp, list);
if (page) {
__count_zid_vm_events(PGALLOC, page_zonenum(page), 1);
zone_statistics(preferred_zone, zone);
}
//开中断
local_irq_restore(flags);
return page;
}
static struct page *__rmqueue_pcplist(struct zone *zone, int migratetype,
unsigned int alloc_flags,
struct per_cpu_pages *pcp,
struct list_head *list)
{
struct page *page;
do {
if (list_empty(list)) {
//如果list为空,就从这个内存区域中分配一部分页面到pcp中来
pcp->count += rmqueue_bulk(zone, 0,
pcp->batch, list,
migratetype, alloc_flags);
if (unlikely(list_empty(list)))
return NULL;
}
//获取list上第一个page结构
page = list_first_entry(list, struct page, lru);
list_del(&page->lru);
//减少pcp页面计数
pcp->count--;
} while (check_new_pcp(page));
return page;
}
分配多个页面情况
调用 __rmqueue_smallest 函数,从 free_area 数组中分配。
static __always_inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
int migratetype)
{
unsigned int current_order;
struct free_area *area;
struct page *page;
/* Find a page of the appropriate size in the preferred list */
for (current_order = order; current_order < MAX_ORDER; ++current_order) {
//获取current_order对应的free_area
area = &(zone->free_area[current_order]);
//获取free_area中对应migratetype为下标的free_list中的page
page = get_page_from_free_area(area, migratetype);
if (!page)
continue;
//拖链page
del_page_from_free_area(page, area);
//分割伙伴
expand(zone, page, order, current_order, area, migratetype);
set_pcppage_migratetype(page, migratetype);
return page;
}
return NULL;
}
????????首先要取得 current_order 对应的 free_area 区中 page,若没有,就继续增加current_order,直到最大的 MAX_ORDER。要是得到一组连续 page 的首地址,就对其脱链,然后调用 expand 函数分割伙伴。
static inline void expand(struct zone *zone, struct page *page,
int low, int high, struct free_area *area,
int migratetype)
{
unsigned long size = 1 << high;
while (high > low) {
area--;
high--;
size >>= 1;//每次循环左移一位
VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);
//标记为保护页,当其伙伴被释放时,允许合并
if (set_page_guard(zone, &page[size], high, migratetype))
continue;
//把另一半pages加入对应的free_area中
add_to_free_area(&page[size], area, migratetype);
//设置伙伴
set_page_order(&page[size], high);
}
}
参考
《深入linux内核架构》
剖析Linux内核物理内存管理-大学生教程-腾讯课堂
|