源码基于:Linux5.15
约定:
- 芯片架构:ARM64
- 内存架构:UMA
- CONFIG_ARM64_VA_BITS:39
- CONFIG_ARM64_PAGE_SHIFT:12
- CONFIG_PGTABLE_LEVELS :3
1. 简介
复合页(Compound Page) 只是将两个或更多物理上连续的页面组合成一个单元,在许多方面可以将其视为单个更大的页面。它们最常用于创建大页面,在hugetlbfs或透明大页(transparent huge pages)子系统中使用,但它们也出现在其他场景中。复合页可以用作匿名内存或用作内核中的buffers;但是,它们不能出现在page cache中,page cache只能处理单个页面。
分配复合页面是调用 alloc_pages() 并设置 __GFP_COMP 分配标志和页帧数大于1, 即order至少为1。
2. 复合页初始化
mm/page_alloc.cstatic void prep_new_page(struct page *page, unsigned int order, gfp_t gfp_flags,unsigned int alloc_flags)
{post_alloc_hook(page, order, gfp_flags);if (order && (gfp_flags & __GFP_COMP))prep_compound_page(page, order);...
}
mm/page_alloc.cvoid prep_compound_page(struct page *page, unsigned int order)
{int i;int nr_pages = 1 << order;__SetPageHead(page);for (i = 1; i < nr_pages; i++) {struct page *p = page + i;p->mapping = TAIL_MAPPING;set_compound_head(p, page);}set_compound_page_dtor(page, COMPOUND_PAGE_DTOR);set_compound_order(page, order);atomic_set(compound_mapcount_ptr(page), -1);if (hpage_pincount_available(page))atomic_set(compound_pincount_ptr(page), 0);
}
复合页初始化可以用下图归纳:
注意:
-
第一个page 中的flag 会标记 PG_head,标记此为复合页头页;
-
之后所有page 都会配置两个属性:mapping 和 compound_head,并且通过 compound_head 确认是尾页还是头页;
-
第二个page 中会存放更多复合页的信息,这也是为什么复合页的 order 至少为 1 的原因;
-
prep_compound_page() 函数最后会进行 hpage_pincount_available() 判断,这个特殊场景,需要order 至少为2;
3. 常用接口
3.1 compound_head()
include/linux/page-flags.hstatic inline unsigned long _compound_head(const struct page *page)
{unsigned long head = READ_ONCE(page->compound_head);if (unlikely(head & 1))return head - 1;return (unsigned long)page;
}#define compound_head(page) ((typeof(page))_compound_head(page))
通过 page->compound_head 的最后一位是否设为 1 来判断该页是否为头页,如果不是头页,只需要减 1 即可获得头页地址。
3.2 PageCompound() 和 PageTail()
include/linux/page-flags.hstatic __always_inline int PageTail(struct page *page)
{return READ_ONCE(page->compound_head) & 1;
}static __always_inline int PageCompound(struct page *page)
{return test_bit(PG_head, &page->flags) || PageTail(page);
}
同上,通过 page->compound_head 的最后一位是否设为 1 来判断头页还是尾页。
3.3 destroy_compound_page()
inlucde/linux/mm.hstatic inline void destroy_compound_page(struct page *page)
{VM_BUG_ON_PAGE(page[1].compound_dtor >= NR_COMPOUND_DTORS, page);compound_page_dtors[page[1].compound_dtor](page);
}
该函数通过调用 page[1].compound_dtor 记录的析构函数来释放复合页。
在 prep_compound_page() 中将复合页的析构函数默认指向 COMPOUND_PAGE_DTOR,即 free_compound_page() 函数,如下:
3.3.1 free_compound_page()
mm/page_alloc.ccompound_page_dtor * const compound_page_dtors[NR_COMPOUND_DTORS] = {[NULL_COMPOUND_DTOR] = NULL,[COMPOUND_PAGE_DTOR] = free_compound_page,
#ifdef CONFIG_HUGETLB_PAGE[HUGETLB_PAGE_DTOR] = free_huge_page,
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE[TRANSHUGE_PAGE_DTOR] = free_transhuge_page,
#endif
};
对于COMPOUND_PAGE_DTOR 对应的是 free_compound_page() 函数。
mm/page_alloc.cvoid free_compound_page(struct page *page)
{mem_cgroup_uncharge(page);free_the_page(page, compound_order(page));
}
3.3.2 set_compound_page_dtro()
inlucde/linux/mm.hstatic inline void set_compound_page_dtor(struct page *page,enum compound_dtor_id compound_dtor)
{VM_BUG_ON_PAGE(compound_dtor >= NR_COMPOUND_DTORS, page);page[1].compound_dtor = compound_dtor;
}
通过set_compound_page_dtor() 函数页可以动态设置复合页的析构函数。
3.4 compound_order()
在 inlucde/linux/mm.h 中有很多关于复合页的使用:
inlucde/linux/mm.hstatic inline unsigned int compound_order(struct page *page)
{if (!PageHead(page))return 0;return page[1].compound_order;
}
通过该函数获取复合页的order 大小。
4. 使用
复合页的分配主要是通过 gfp flag:__GFP_COMP,很多模块在申请内存时都会带上该标志。
例如,ion_page_pool_create():
drivers/staging/android/ion/heaps/ion_page_pool.cstruct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order)
{struct ion_page_pool *pool = kmalloc(sizeof(*pool), GFP_KERNEL);...pool->gfp_mask = gfp_mask | __GFP_COMP;pool->order = order;...return pool;
}
在后期申请内存池时,会带入 pool->gfp_mask 调用alloc_pages() 函数。
再例如,kmalloc_order():
mm/slab_common.cvoid *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{...flags |= __GFP_COMP;page = alloc_pages(flags, order);...return ret;
}
当通过 kmalloc() 申请的内存超过 8K 时,会直接从 buddy 中分配,默认就是复合页。