Buddy system之alloc_pages

本文深入解析Buddy系统中alloc_pages的实现,包括__rmqueue、__rmqueue_smallest、expand等关键函数,详细阐述了如何从内存区域分配和拆分页面,以及在不同迁移类型间进行页面调配的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

申请pages的基础知识在“Buddy system基础 ”这篇文章中已经讲过了,这里主要分析申请pages的代码。

代码框架


alloc_pages //alloc_pages_node(numa_node_id(), gfp_mask, order)
   |----->return __alloc_pages_node(nid, gfp_mask, order);
   |   |----->return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
   |   |   |----->return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
   |   |   |   |----->.high_zoneidx = gfp_zone(gfp_mask),
   |   |   |   |      根据传递进来的gfp_mask,选择从那个zone开始分配
   |   |   |   |----->.zonelist = zonelist,
   |   |   |   |      设置要从哪个zonelist分配,对于UMA来说,只有一个zonelist
   |   |   |   |----->.nodemask = nodemask,
   |   |   |   |----->.migratetype = gfpflags_to_migratetype(gfp_mask),
   |   |   |   |      根据传递进来的gfp_mask,选择要分配哪种迁移类型的page
   |   |   |   |----->ac.preferred_zoneref = first_zones_zonelist(ac.zonelist,
   |   |   |   |             ac.high_zoneidx, ac.nodemask);
   |   |   |   |      找到最先使用的zone
   |   |   |   |----->page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
   |   |   |   |      遍历所有符合条件的zone,分配pages
   |   |   |   |   |----->page = buffered_rmqueue(ac->preferred_zoneref->zone, zone, order,
   |   |   |   |   |                        gfp_mask, alloc_flags, ac->migratetype);
   |   |   |   |   |   |----->如果order为0,即分配一个page,则走如下逻辑
   |   |   |   |   |   |----->pcp = &this_cpu_ptr(zone->pageset)->pcp;
   |   |   |   |   |   |----->list = &pcp->lists[migratetype];
   |   |   |   |   |   |      获取当前cpu的pageset链表,为该cpu分配的free pages会加入该链表
   |   |   |   |   |   |----->pcp->count += rmqueue_bulk(zone, 0, pcp->batch, list, migratetype, cold);
   |   |   |   |   |   |   |----->struct page *page = __rmqueue(zone, order, migratetype);
   |   |   |   |   |   |   |      从所选的zone,迁移类型为migratetype的free_list
<think>嗯,用户给了一个关于内核内存分配的问题,里面有两次调用堆栈,分别是3次和736次分配请求。用户希望了解这两者的区别和可能的原因。首先,我需要理解这些堆栈跟踪的含义。 首先,看到这些堆栈中的函数,比如post_alloc_hook、prep_new_page,这些都是Linux内核内存管理中的函数,属于伙伴系统和SLAB分配器的一部分。PID是-1,可能表示这些分配不是由具体的用户进程发起的,而是内核线程或某些后台机制触发的。 然后,两次的调用堆栈几乎相同,但次数和页面数差别很大。3次和736次,这可能意味着某个操作被重复执行了多次。比如在文件系统查找路径时,频繁分配dentry结构,导致多次内存分配。而736次可能对应更复杂或频繁的操作,比如大量文件访问或缓存操作。 接下来,我需要分析堆栈中的关键函数。比如__d_alloc和d_alloc_parallel,这些和dentry缓存有关。如果用户在使用OverlayFS(ovl_lookup_single等函数),那么可能是在挂载点或文件查找时触发了这些分配。当路径查找需要创建新的dentry时,就会调用这些函数,进而通过kmalloc分配内存。 可能的原因包括:系统负载高导致频繁路径查找,或者应用程序在短时间内大量访问文件,导致dentry缓存不断扩展。此外,SLAB分配器在需要新slab时会调用alloc_slab_page,这可能是因为现有slab不足,需要从伙伴系统获取页面。 另外,PID为-1可能意味着这些分配来自内核线程,比如kswapd或其他后台进程。需要检查系统日志或使用其他工具(如ftrace)来确认具体上下文。 用户可能需要进一步检查系统内存使用情况,查看是否有内存泄漏,或者优化文件访问模式,减少不必要的路径查找。也可以调整SLAB分配器的参数,或者检查OverlayFS的配置是否合理。</think>#### 问题背景分析 这段日志属于Linux内核内存分配路径的堆栈跟踪(stack trace),涉及**伙伴系统(Buddy System)**和**SLAB/SLUB分配器**的协作过程。日志中两次记录的调用路径完全一致,但执行次数(3次 vs. 736次)和分配的页数(3页 vs. 736页)存在显著差异,需要结合调用链上下文分析差异原因。 --- ### 调用链逐层解读 #### 1. **核心内存分配流程** $$ \text{__alloc_pages_nodemask} \rightarrow \text{get_page_from_freelist} \rightarrow \text{prep_new_page} \rightarrow \text{post_alloc_hook} $$ - **`__alloc_pages_nodemask`**:伙伴系统的核心入口,负责按NUMA节点和掩码分配物理页。 - **`get_page_from_freelist`**:遍历内存区域(zone)链表,尝试从空闲列表获取可用页。 - **`prep_new_page`**:初始化新分配的页(如清除脏标记、设置迁移类型)。 - **`post_alloc_hook`**:触发内存分配的后期处理(如内存检测工具KASAN的标记)。 --- #### 2. **SLAB分配器关联路径** $$ \text{alloc_slab_page} \rightarrow \text{new_slab} \rightarrow \text{___slab_alloc.isra.0} \rightarrow \text{__kmalloc} $$ - **`alloc_slab_page`**:从伙伴系统申请一个完整的SLAB页(通常为$2^{\text{order}}$个连续页)。 - **`new_slab`**:初始化新的SLAB对象池,划分出多个等大小对象。 - **`___slab_alloc.isra.0`**:SLAB分配器的核心逻辑,分配单个对象。 - **`__kmalloc`**:内核通用内存分配接口,最终通过SLAB分配器实现。 --- #### 3. **文件系统相关调用** $$ \text{__d_alloc} \rightarrow \text{d_alloc_parallel} \rightarrow \text{lookup_slow} \rightarrow \text{ovl_lookup_single} $$ - **`__d_alloc`**:分配目录项(dentry)结构,用于文件系统路径缓存。 - **`d_alloc_parallel`**:并发安全的dentry分配函数。 - **`ovl_lookup_single`**:OverlayFS(联合文件系统)的路径查找函数,用于合并多层目录。 --- ### 关键差异点解析 | **特征** | **3次分配** | **736次分配** | |----------------------|-------------------|---------------------| | **触发场景** | 偶发路径查找 | 高频路径遍历 | | **内存压力** | 低压力 | 可能内存碎片化 | | **SLAB状态** | 现有SLAB有空闲 | 需频繁创建新SLAB | | **性能影响** | 可忽略 | 可能引发延迟波动 | --- #### 可能原因推测 1. **OverlayFS路径查找激增** - 当用户频繁访问OverlayFS挂载点(如容器镜像层)时,`ovl_lookup_layer`会递归调用路径解析,导致`d_alloc`高频分配dentry对象。 2. **SLAB缓存不足** - 若dentry的SLAB缓存(`dentry_cache`)空闲对象不足,触发`alloc_slab_page`从伙伴系统批量申请新页(每次申请一个SLAB,包含多个对象)。 3. **内核线程触发分配** - `PID=-1`表示分配请求来自内核线程(如kswapd、文件系统后台任务),而非用户进程。 --- ### 调试建议 1. **监控工具验证** ```bash # 查看dentry缓存统计 grep "dentry" /proc/slabinfo # 跟踪kmalloc调用频率 perf probe --add '__kmalloc' perf record -e probe:__kmalloc -a -g ``` 2. **优化方向** - **调整SLAB参数**:通过`slab_nomerge`或`slab_max_order`限制SLAB大小。 - **减少路径深度**:优化OverlayFS层数,避免冗余路径查找。 - **排查内存泄漏**:检查dentry是否被正确释放(`dput()`调用链)。 --- ### 总结 这两次堆栈反映了同一内存分配路径在不同负载下的表现:**3次分配对应轻度操作,736次分配可能由大规模路径遍历或SLAB缓存频繁扩展引起**。需结合系统实际负载和内存状态进一步分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值