转自edsionte's TechBlog
在用户态下程序中,我们可以通过malloc()动态申请内存空间。在内核空间中,专门有一个内核子系统处理对连续页框的内存分配请求,这个内核子系统即为管理区页框分配器(zoned page frame allocator)。该分配器包含六个专门用于分配页框的API,这些API都是基于伙伴算法而实现的,因此这些API申请的页框数只能为2的整数幂大小。
内存分配器API
1.alloc_pages()
该宏用来分配2的order次方个连续的页框,如果申请成功返回第一个所分配页框的描述符地址,申请失败的话返回NULL。
1 | #define alloc_pages(gfp_mask, order) \ |
2 | alloc_pages_node(numa_node_id(), gfp_mask, order) |
2.alloc_page()
该函数用来分配一个单独的页框,它可以看作是alloc_pages()当order等于0时的特殊情况。
1 | #define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0) |
3.__get_free_pages()
通过该函数可以申请长为2的order次方大小的连续页框,但是它返回的是这段连续页框中第一个页所对应的线性地址。从源码中可以看出,该函数内部仍然调用了alloc_pages函数,并利用page_address函数将页描述符地址转换为线性地址。
01 | unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) |
05 | VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0); |
07 | page = alloc_pages(gfp_mask, order); |
10 | return (unsigned long ) page_address(page); |
4.__get_free_page()
该宏可以看作是__get_free_pages函数的特殊情况,它用于申请一个单独的页框。
1 | #define __get_free_page(gfp_mask) \ |
2 | __get_free_pages((gfp_mask),0) |
5.get_zeroed_page()
该函数用来获取一个填满0的页框,其中__GFP_ZERO参数用来体现这一点。
1 | unsigned long get_zeroed_page(gfp_t gfp_mask) |
3 | return __get_free_pages(gfp_mask | __GFP_ZERO, 0); |
6.__get_dma_pages()
该宏获得的页框用于DMA操作。
1 | #define (gfp_mask, order) \ |
2 | __get_free_pages((gfp_mask) | GFP_DMA,(order)) |
请求页框的标志
从上述几个分配器API中可以看到,除了用于指示请求页框大小的order参数外,还包括一组标志gfp_mask,它指明了如何寻找空闲的页框。下面仅说明几个常见的分配标志。
__GFP_DMA:该标志指明只能从ZONE_DMA内存管理区获得页框。
__GFP_HIGHMEM:如果该标志被设置,则按照ZONE_HIGHMEM,ZONE_NORMAL和ZONE_DMA的请求顺序获得页框,既首先在ZONE_HIGHMEME区请求所需大小的页框,如果该区无法满足请求页框的大小,则再向ZONE_DMA区发出请求。如果该标志没有被设置,则按照默认的ZONE_NORMAL和ZONE_DMA内存管理区的顺序获取页框。
__GFP_ZERO:如果设置了该标志,那么所申请的页框必须被填满0。
API关系图
本文所介绍的这几个API本质上都调用了alloc_pages(),而alloc_pages()又在其内部调用了alloc_pages_node(),它们之间的关系如下图所示:

从图中可以看出,alloc_pages_node()是所有分配器API的核心函数。