Buddy相关API
继《linux内核那些事之buddy》,buddy实现主要位于mm\page_alloc.c文件中,对外提供了一系列API:
API | 作用 |
struct page * __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid, nodemask_t *nodemask) | 按照指定的gfp_mask,申请order级别数量的page,并且指定从preferred_id node节点中申请物理内存。在NUMA系统中可以通过指定node id来优化内存,是申请的内存位于该进程运行的numa节点中。 |
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) | 按照gfp_mask和order申请物理内存,注意gfp_mask中的__GFP_HIGHMEM标记位会被清除无效。 |
unsigned long get_zeroed_page(gfp_t gfp_mask) | 获取zero page |
void *page_frag_alloc(struct page_frag_cache *nc, unsigned int fragsz, gfp_t gfp_mask) | page fragment特性,该特性主要从申请过page中进行偏移来获取内存,优点是将0阶或者order阶compound page中进行偏移获取内存不需要重新申请新的页。 |
int alloc_contig_range(unsigned long start, unsigned long end, unsigned migratetype, gfp_t gfp_mask) | 申请一段连续范围的物理内存,start为指定申请的物理内存开始PFN,end为指定申请的物理内存结束PFN。 |
void *alloc_pages_exact(size_t size, gfp_t gfp_mask) | 是对malloc_pages的扩展函数,支持通过指定size大小来申请物理内存页,函数内部会将size转换成合适的order申请物理内存页 |
void __free_pages(struct page *page, unsigned int order) | 按照指定page释放order级别数量连续物理内存页 |
void free_pages(unsigned long addr, unsigned int order) | 按照指定addr释放order级别的物理页。注意此时addr的虚拟内存地址与物理地址是线性映射,故可以根据addr直接转换成物理地址从而得知所需要释放的物理页pfn,所以该函数使用存在使用限制只能使用虚拟地址与物理地址线性映射区域。 |
void page_frag_free(void *addr) | 使用page_frag_alloc申请过内存 使用 page_frag_free释放 |
void __page_frag_cache_drain(struct page *page, unsigned int count) | 释放fragement page,与page_frag_free不同的是,指定count计数,如果page中引用计数减去count之后为0则将page释放 |
void free_pages_exact(void *virt, size_t size) | 释放由alloc_pages_exact申请的内存 |
void __free_pages(struct page *page, unsigned int order) | 按照指定page释放order级别数量连续物理内存页 |
unsigned long nr_free_buffer_pages(void) | 计算超过high watermark 内存物理页 |
void free_contig_range(unsigned long pfn, unsigned int nr_pages) | 释放一段连续物理内存,pfn为释放物理内存起始PFN,nr_pages为释放的物理内存页数目 |
long si_mem_available(void) | 可以被用户空间使用的内存 |
void si_meminfo(struct sysinfo *val) | 内存详细信息 包含总大小以及已经使用的内存数量等信息 |
void adjust_managed_page_count(struct page *page, long count) | 调整buddy可以管理的物理内存 |
gfp(get free page) mask
gfp mask是用于控制对物理内存申请和释放,用于决定在申请过程中物理内存行为以及如何进行申请,从哪个区域申请等信息,贯穿整个内存处理过程,整个flag经过不断演进最后大概分为四个部分:
- zone modifiers: 管理区修饰符
- page mobility and placement hints:页面移动属性和放置提示
- watermark modifiers: 内存水位修饰符
- reclaim modifiers:内存回收修饰符
- 剩余其他标记位
zone modifiers
管理区修饰符用于尽可能从指定的管理区中申请物理内存,其支持的flag为:
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u
- ___GFP_DMA:用于从DMA zone中申请物理内存。
- ___GFP_HIGHMEM:用于从hige zone中申请内存。
- ___GFP_DMA32:用于从DMA32 zone中申请内存。
- ___GFP_MOVABLE : 是一个双重意义标识符,即表示申请内存移动属性,也表明是从zone中可移动migratetype中申请内存。
gfp_zone()
gfp_zone()函数内核常用于在申请内存时从gfp mask中提取出zone type,从指定的zone中申请内存:
static inline enum zone_type gfp_zone(gfp_t flags)
{
enum zone_type z;
int bit = (__force int) (flags & GFP_ZONEMASK);
z = (GFP_ZONE_TABLE >> (bit * GFP_ZONES_SHIFT)) &
((1 << GFP_ZONES_SHIFT) - 1);
VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
return z;
}
page mobility and placement hints(页面移动属性)
该属性标识符主要用于修饰页面移动属性
#define ___GFP_MOVABLE 0x08u
#define ___GFP_RECLAIMABLE 0x10u
#define ___GFP_WRITE 0x1000u
#define ___GFP_HARDWALL 0x100000u
#define ___GFP_THISNODE 0x200000u
#define ___GFP_ACCOUNT 0x400000u
- ___GFP_MOVABLE:当用于表示页面移动属性时,表明在内存规整或者页面回收过程中,该页面可以被page migration移动,以防止有较大内存碎片。
- ___GFP_RECLAIMABLE:当slab 申请时,指定SLAB_RECLAIM_ACCOUNT时,该页面可以通过shrinker回收释放。
- ___GFP_WRITE:意味着该页面会被设置成dirty页,如果设置该页面则在内存申请时,会基于公平申请策略(fairy zone allocation policy)尽量将这些设置dirty page分布在zone之间,以防止所有dirt page都设置在同一个zone中。
- ___GFP_HARDWALL:强制使用cpuset 内存分配策略。
- ___GFP_THISNODE:强制从指定的要求的节点中分配内存,并且没有内存回退和放置策略(no fallbacks or placement policy enforcement)。
- ___GFP_ACCOUNT:内存分配过程中会被kmemcg记录。
watermark modifiers
水位修饰符在申请内存中内存不足时用于控制对紧急预留内存(energency reserves memory)使用:
#define ___GFP_HIGH 0x20u
#define ___GFP_ATOMIC 0x200u
#define ___GFP_MEMALLOC 0x20000u
#define ___GFP_NOMEMALLOC 0x80000u
- ___GFP_HIGH:设置该标志位表明申请者权限比较高,要保证该分配请求。预示着该请求可以使用预留内存。
- ___GFP_ATOMIC:表明该内存申请优先级比较高,内存申请过程中不能执行内存回收或者休眠。通常用于中断处理函数中,并且一般是和 ___GFP_HIGH一起使用。
- ___GFP_MEMALLOC:允许从所有内存(包括预留内存)申请内存。使用该标志位需要调用者很快就会被释放,因此有可能会占用预测内存。因此使用该标记位申请者需要非常小心,并且如果有可能需要申请者使用节流机制(throtting mechanism)比如 pre-alloced pool(mempool等)包做预留内存不能被用尽。
- ___GFP_NOMEMALLOC:禁止使用预留内存。
reclaim modifiers
内存回收标识符主要用于内存回收行为:
#define ___GFP_IO 0x40u
#define ___GFP_FS 0x80u
#define ___GFP_DIRECT_RECLAIM 0x400u
#define ___GFP_KSWAPD_RECLAIM 0x800u
#define ___GFP_RETRY_MAYFAIL 0x4000u
#define ___GFP_NOFAIL 0x8000u
#define ___GFP_NORETRY 0x10000u
#define __GFP_RECLAIM (__GFP_DIRECT_RECLAIM|__GFP_KSWAPD_RECLAIM)
- ___GFP_IO:开启IO
- ___GFP_FS:允许调用底层FS文件系统。清除该标记可以避免造成文件可能死锁问题。如果相应文件已经被上锁,分配过程中层层递归调用这个文件系统有可能造成死锁问题。
- ___GFP_DIRECT_RECLAIM:表明在申请内存过程中允许直接使用内存回收机制。清除该标记位,可以因内存回收造成的避免不必要延迟。
- __GFP_KSWAPD_RECLAIM:当内存水位达到low watermark低水位时 唤醒kswapd内核线程,该线程会启动内存回收,当内存水位达到highe watermark时将停止。
- __GFP_RECLAIM:该标记位是一个组合标记位,用于允许或禁止直接内存回收和kswap回收。
- ___GFP_NORETRY:内存失败时不尝试重新申请。当申请内存时内存压力较大时为避免触发OOM等行为会先触发内存回收规整等机制。内存压力过大时,申请过大内存很容易失败,该标记位表明内存失败时是否需要重新申请。该标记位比较适用较小内存申请。
- ___GFP_RETRY_MAYFAIL:内存申请失败时,是否已经尝试重新申请内存。内存申请第一失败时候,将重新尝试内存回收等机制或者等待其他进程尝试更高级回收方法之后再次尝试申请内存,再次尝试主要是为了避免触发OOM。
- ___GFP_NOFAIL:不允许申请内存失败。申请内存失败将会一直尝试下去。
剩余其他标记位
剩余其他标记位:
#define ___GFP_ZERO 0x100u
#ifndef __GFP_NOTRACK
#define __GFP_NOTRACK 0
#endif
- ___GFP_ZERO :申请物理页内存全部为0.
- __GFP_NOTRACK:不被kmemcheck机制跟踪。
废弃标记位
- ___GFP_REPEAT标记位已经废弃。
常用组合flags
对gfp flags使用常常需要使用多个标识符使用,为了简化使用内核中预先定义好了一些常用组合flags方便使用,常用flags如下:
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO (__GFP_RECLAIM)
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
__GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM)
#define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)
/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
- GFP_ATOMIC:申请内存必须申请成功,中间不能有休眠sleep处理,当物理内存低于water mark时允许从预留内存中申请同时启动kswapd线程进行内存回收等处理。
- GFP_KERNEL:内核内部申请使用内存常用标识,从NORMAL ZONE中申请内存,或者更低ZONE中分配内存,可以直接触发内存回收,有可能会被休眠,因此不能用于中断上下文中。
- GFP_KERNEL_ACCOUNT: 和GFP_KERNEL功能一样,只是开启可kmemcg功能 。
- GFP_NOWAIT:不允许休眠,可以启动IO或文件系统回调,不能休眠,不允许因为启动直接回收而停止。
- GFP_NOIO:不需要启动IO操作,直接启动内存回收以丢弃干净页面或者slab页面。一般建议不直接使用该标记位。
- GFP_NOFS:不允许使用文件系统接口,允许直接启动回收机制。
- GFP_USER:通常用于用户空间分配内存,并且这些内存可以直接被内核和硬件使用。可以用来作为硬件的buffer直接映射到用户空间中使用,例如DMAbuffer。并且使用cpuset内存分配策略。
- GFP_DMA:该标记位主要时历史原因,早期DMA寻址由于只能到16M,因此用于DMA内存只能从16M申请。
- GFP_DMA32:类似GFP_DMA作用,主要用于32位地址,兼容32位系统。
- GFP_HIGHUSER:用户内存申请内存,优先从high zone中申请,内核不会直接使用,不支持内存迁移功能。
- GFP_HIGHUSER_MOVABLE:与GFP_HIGHUSER类似,但是内核可以直接使用kmap访问,并且支持内存迁移
- GFP_TRANSHUGE/GFP_TRANSHUGE_LIGHT:用于transport huge page(THP)内存申请,在失败时灰灰唤醒kswapd/kcompacted功能。_LIGHT版本不会尝试内存回收和压缩compaction功能,而GFP_TRANSHUGE可以使用khugepaged。
alloc_pages()
alloc_pages ()为常用的申请内存接口,实现位于include\linux\gfp.h文件中:
#ifdef CONFIG_NUMA
extern struct page *alloc_pages_current(gfp_t gfp_mask, unsigned order);
static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{
return alloc_pages_current(gfp_mask, order);
}
extern struct page *alloc_pages_vma(gfp_t gfp_mask, int order,
struct vm_area_struct *vma, unsigned long addr,
int node, bool hugepage);
#define alloc_hugepage_vma(gfp_mask, vma, addr, order) \
alloc_pages_vma(gfp_mask, order, vma, addr, numa_node_id(), true)
#else
#define alloc_pages(gfp_mask, order) \
alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_pages_vma(gfp_mask, order, vma, addr, node, false)\
alloc_pages(gfp_mask, order)
#define alloc_hugepage_vma(gfp_mask, vma, addr, order) \
alloc_pages(gfp_mask, order)
#endif
分别为numa系统和smp系统两个版本,最终是调用 __alloc_pages_nodemask接口从buffy中获取内存。
__alloc_pages_nodemask
__alloc_pages_nodemask函数主要流程如下:
__alloc_pages_nodemask源码
结合上述流程和源码分析内存申请过程:
/*
* This is the 'heart' of the zoned buddy allocator.
*/
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
nodemask_t *nodemask)
{
struct page *page;
unsigned int alloc_flags = ALLOC_WMARK_LOW;
gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
struct alloc_context ac = { };
/*
* There are several places where we assume that the order value is sane
* so bail out early if the request is out of bound.
*/
if (unlikely(order >= MAX_ORDER)) {
WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
return NULL;
}
gfp_mask &= gfp_allowed_mask;
alloc_mask = gfp_mask;
if (!prepare_alloc_pages(gfp_mask, order, preferred_nid, nodemask, &ac, &alloc_mask, &alloc_flags))
return NULL;
finalise_ac(gfp_mask, &ac);
/*
* Forbid the first pass from falling back to types that fragment
* memory until all local zones are considered.
*/
alloc_flags |= alloc_flags_nofragment(ac.preferred_zoneref->zone, gfp_mask);
/* First allocation attempt */
page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
if (likely(page))
goto out;
/*
* Apply scoped allocation constraints. This is mainly about GFP_NOFS
* resp. GFP_NOIO which has to be inherited for all allocation requests
* from a particular context which has been marked by
* memalloc_no{fs,io}_{save,restore}.
*/
alloc_mask = current_gfp_context(gfp_mask);
ac.spread_dirty_pages = false;
/*
* Restore the original nodemask if it was potentially replaced with
* &cpuset_current_mems_allowed to optimize the fast-path attempt.
*/
ac.nodemask = nodemask;
page = __alloc_pages_slowpath(alloc_mask, order, &ac);
out:
if (memcg_kmem_enabled() && (gfp_mask & __GFP_ACCOUNT) && page &&
unlikely(__memcg_kmem_charge_page(page, gfp_mask, order) != 0)) {
__free_pages(page, order);
page = NULL;
}
trace_mm_page_alloc(page, order, alloc_mask, ac.migratetype);
return page;
}
- (order >= MAX_ORDER):对order检查,超过系统所支持的MAX_ORDER 直接返回。
- prepare_alloc_pages:对alloc_context结果填充,alloc_context结果为后续内存申请所使用接口,该结构如下:
- alloc_flags_nofragment: 根据gfp mask中标识得到alloc flga,注意进行fagment处理,提前避免较严重的内存碎片化,当发生外碎片化时,尽量使用本地zone进行碎片化整理。
- get_page_from_freelist: 尝试快速申请内存通道中申请内存,如果申请成功则避免进入慢速通道。
- 如果内存申请失败,则进入__alloc_pages_slowpath慢速通道。
- 最后进入out阶段,如果开启kmem功能,内存申请成功,且gfp mask中设置__GFP_ACCOUNT,则将申请的内存加入kmem中。
- 将申请的内存返回page。
参考资料:
《understanding the linux Virtual Memory Manager》
Memory Management APIs — The Linux Kernel documentation
Ideas for rationalizing GFP flags [LWN.net]