在用户态下程序中,我们可以通过malloc()动态申请内存空间。在内核空间中,专门有一个内核子系统处理对连续页框的内存分配请求,这个内核子系统即为管理区页框分配器(zoned page frame allocator)。该分配器包含六个专门用于分配页框的API,这些API都是基于伙伴算法而实现的,因此这些API申请的页框数只能为2的整数幂大小。
内存分配器API
1.alloc_pages()
该宏用来分配2的order次方个连续的页框,如果申请成功返回第一个所分配页框的描述符地址,申请失败的话返回NULL。
#define alloc_pages(gfp_mask, order) \ alloc_pages_node(numa_node_id(), gfp_mask, order)
2.alloc_page()
该函数用来分配一个单独的页框,它可以看作是alloc_pages()当order等于0时的特殊情况。
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)
3.__get_free_pages()
通过该函数可以申请长为2的order次方大小的连续页框,但是它返回的是这段连续页框中第一个页所对应的线性地址。从源码中可以看出,该函数内部仍然调用了alloc_pages函数,并利用page_address函数将页描述符地址转换为线性地址。
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) { struct page *page; VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0); page = alloc_pages(gfp_mask, order); if (!page) return 0; return (unsigned long) page_address(page); }
4.__get_free_page()
该宏可以看作是__get_free_pages函数的特殊情况,它用于申请一个单独的页框。
#define __get_free_page(gfp_mask) \ __get_free_pages((gfp_mask),0)
5.get_zeroed_page()
该函数用来获取一个填满0的页框,其中__GFP_ZERO参数用来体现这一点。
unsigned long get_zeroed_page(gfp_t gfp_mask) { return __get_free_pages(gfp_mask | __GFP_ZERO, 0); }
6.__get_dma_pages()
该宏获得的页框用于DMA操作。
#define (gfp_mask, order) \ __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的核心函数。