内存管理-49-alloc_pages-2-内存分配防递归-PF_MEMALLOC

基于 Linux-6.1


一、简介

1. 不防护就会递归的原因

内存紧张时,任务分配失败会进入 direct reclaim(__alloc_pages_slowpath → __alloc_pages_direct_reclaim → __perform_reclaim → try_to_free_pages)。但回收过程本身也要分配内存:回写路径 pageout() → writepage 要分配 buffer head、bio、文件系统元数据;add_to_swap()、f2fs/ext4 的日志都要页。这些内嵌分配如果按普通语义走分配器,此时全系统都在 min 水线以下,又会进 slowpath、又触发 direct reclaim——递归就这么形成了:释放内存需要先分配内存。更糟的是每层都持有 LRU/zone 锁,嵌套会死锁;栈也会爆。min 水线(min_free_kbytes 对应的紧急储备)本来就是为两类人留的:原子上下文分配、以及回收者自用。PF_MEMALLOC 就是打开这个储备的钥匙。


二、实现原理

1. 回收前给自己贴上标志

1.1 direct reclaim
    __alloc_pages_slowpath
        get_page_from_freelist //换min水线还分配不到
        __alloc_pages_direct_compact(INIT_COMPACT_PRIORITY) //轻量级的
            memalloc_noreclaim_save //在尝试压缩页前调用
                current->flags |= PF_MEMALLOC;
            try_to_compact_pages()
            memalloc_noreclaim_restore //恢复
                current->flags &= ~PF_MEMALLOC;
        if (current->flags & PF_MEMALLOC) //储备拿不到时,硬性切断递归
            goto nopage;
        __alloc_pages_direct_reclaim //page_alloc.c
            __perform_reclaim //page_alloc.c
                memalloc_noreclaim_save //在尝试释放页前调用
                    current->flags |= PF_MEMALLOC;
                try_to_free_pages()
                memalloc_noreclaim_restore //恢复
                    current->flags &= ~PF_MEMALLOC;
        __alloc_pages_direct_compact
            memalloc_noreclaim_save //在尝试压缩页前调用
            try_to_compact_pages()
            memalloc_noreclaim_restore //恢复
                current->flags &= ~PF_MEMALLOC;

直接内存回收时,会带上 PF_MEMALLOC 标志,若回收过程中再次进入内存分配慢速路径,直接失败退出。

1.2 kswapd reclaim
    kswapd
        tsk->flags |= PF_MEMALLOC | PF_KSWAPD;
        balance_pgdat //vmscan.c

kswapd 异步回收,整个过程都是带着 PF_MEMALLOC 标志的。


2. 分配器看到标志就放行紧急储备

/* 返回 0 表示常规分配,返回非 0 表示要努力分配(ALLOC_NO_WATERMARKS/ALLOC_OOM) */
static inline int __gfp_pfmemalloc_flags(gfp_t gfp_mask)
{
    /* 此标志用于明确禁止访问紧急预留的那部分内存,优先级高于下面的 __GFP_MEMALLOC */
    if (unlikely(gfp_mask & __GFP_NOMEMALLOC))
        return 0;
    /* 此标志表示允许访问所有内存,只在分配是为了短时间释放更多内存时使用 */
    if (gfp_mask & __GFP_MEMALLOC)
        return ALLOC_NO_WATERMARKS;
    /*
     * 正在执行软中断回调,且使用 PF_MEMALLOC 标志表示当前线程正处于"为了释放内存而分配内存"的紧急上下文,
     * 允许它在分配路径上获得比普通线程更高的优先级和保留内存访问权。
     */
    if (in_serving_softirq() && (current->flags & PF_MEMALLOC))
        return ALLOC_NO_WATERMARKS;
    /*
     * 若不在硬中断、软中断、NMI中断回调上下文中,且为了释放内存而分配,则忽略水线; 
     * 若是OOM受害者,则只是 alloc_harder,多吃 1/2 的 mark 水线
     */
    if (!in_interrupt()) {
        if (current->flags & PF_MEMALLOC)
            return ALLOC_NO_WATERMARKS;
        else if (oom_reserves_allowed(current))
            return ALLOC_OOM;
    }

    return 0;
}

作用路径:

    __alloc_pages_slowpath
        gfp_pfmemalloc_allowed
        __alloc_pages_slowpath //page_alloc.c 返回非0则动用min水线以下的内存
            alloc_flags = __gfp_pfmemalloc_flags
            get_page_from_freelist(alloc_flags)
                if (alloc_flags & ALLOC_NO_WATERMARKS) //忽略水线进行分配
                    goto try_this_zone;
                try_this_zone:
                    page = rmqueue() //从伙伴系统拿页

于是 get_page_from_freelist() 里水线检查失败后不再 continue,而是 goto try_this_zone 直接 rmqueue()——可以从 min 水线以下的储备里拿页。回收用的内嵌分配立刻成功,不会进 slowpath。


3. 备也拿不到时,硬性切断递归

如果储备也耗尽(或高阶分配凑不出块),slowpath 里有一道闸,见上面 1.1 小节中的:

if (current->flags & PF_MEMALLOC)
    goto nopage;

PF_MEMALLOC 持有者永远不再进入 direct reclaim,分配直接返回 NULL。所以递归被从两个方向同时掐死:内嵌分配要么从储备成功、要么干脆失败,两种结果都不会再次触发回收。代价是回收路径里的所有分配都必须容忍失败####——比如 writepage() 分配不到 bio 时只能把页标脏放回 LRU 等下一轮,而不是 sleep 等内存。


4. 辅助机制

(1) prep_new_page():凡是动用 ALLOC_NO_WATERMARKS 拿到的页都会 set_page_pfmemalloc()。网络收包路径看到这个标记就知道系统已经在吃储备,会主动丢包/收缩 socket 缓冲,尽快把借的页还回去——防止储备被“只借不还”的消费者长期占用。

(2) __GFP_NOMEMALLOC:显式拒绝碰储备。回收路径中“分配了但自己不释放内存”的分配应该带它,避免回收反而净吃储备。

(3) OOM 路径对 PF_MEMALLOC 直接拒绝并 WARN_ON_ONCE;

(4) TIF_MEMDIE 的 OOM victim 只给部分储备。


三、实际案例

1. 防递归异分配失败案例

26436311.141328 1010145 24413 24413 W Background_1: page allocation failure: order:0, mode:0x8000c0a(GFP_NOIO|__GFP_HIGHMEM|__GFP_MOVABLE|__GFP_CMA), nodemask=(null),cpuset=foreground,mems_allowed=0
526436311.141349 1010145 24413 24413 W         : CPU: 6 PID: 24413 Comm: Background_1 Tainted: G S      W  OE      6.1.115-android14-11-g79b67c6763bf #1
526436311.141352 1010145 24413 24413 W Hardware name: MT8888 (DT)
526436311.141355 1010145 24413 24413 W Call trace:
526436311.141356 1010145 24413 24413 W         : dump_backtrace+0xf4/0x118
526436311.141363 1010145 24413 24413 W         : show_stack+0x18/0x24
526436311.141366 1010145 24413 24413 W         : dump_stack_lvl+0x60/0x7c
526436311.141372 1010145 24413 24413 W         : dump_stack+0x18/0x3c
526436311.141374 1010145 24413 24413 W         : warn_alloc+0xf4/0x160
526436311.141380 1010145 24413 24413 W         : __alloc_pages_slowpath+0x1090/0x11bc //再次进慢速路径
526436311.141383 1010145 24413 24413 W         : __alloc_pages+0x1f4/0x22c
526436311.141386 1010145 24413 24413 W         : alloc_zspage+0x9c/0x49c [zsmalloc]
526436311.141397 1010145 24413 24413 W         : zs_malloc+0x12c/0x248 [zsmalloc]
526436311.141403 1010145 24413 24413 W         : zram_bvec_rw+0x25c/0x86c [zram]
526436311.141411 1010145 24413 24413 W         : zram_rw_page+0xac/0x170 [zram]
526436311.141417 1010145 24413 24413 W         : bdev_write_page+0x84/0xd8
526436311.141428 1010145 24413 24413 W         : __swap_writepage+0x60/0x570
526436311.141431 1010145 24413 24413 W         : swap_writepage+0x50/0xa8
526436311.141433 1010145 24413 24413 W         : shrink_folio_list+0x9f0/0x12a4
526436311.141440 1010145 24413 24413 W         : evict_folios+0x1568/0x18ac
526436311.141445 1010145 24413 24413 W         : try_to_shrink_lruvec+0x24c/0x2d0
526436311.141448 1010145 24413 24413 W         : shrink_one+0xb0/0x1f8
526436311.141451 1010145 24413 24413 W         : shrink_node+0xd58/0x10e4
526436311.141454 1010145 24413 24413 W         : do_try_to_free_pages+0x240/0x5b4
526436311.141456 1010145 24413 24413 W         : try_to_free_pages+0x2dc/0x50c
526436311.141459 1010145 24413 24413 W         : __alloc_pages_slowpath+0x5b4/0x11bc //首次进慢速路径
526436311.141461 1010145 24413 24413 W         : __alloc_pages+0x1f4/0x22c
526436311.141464 1010145 24413 24413 W         : __pte_alloc+0x3c/0x1a8
526436311.141474 1010145 24413 24413 W         : copy_page_range+0xecc/0x1224
526436311.141478 1010145 24413 24413 W         : copy_mm+0x4b8/0x7b8
526436311.141481 1010145 24413 24413 W         : copy_process+0x4d0/0xc5c
526436311.141484 1010145 24413 24413 W         : kernel_clone+0xb0/0x43c
526436311.141485 1010145 24413 24413 W         : __arm64_sys_clone+0x5c/0x8c
526436311.141487 1010145 24413 24413 W         : invoke_syscall+0x58/0x11c
526436311.141496 1010145 24413 24413 W         : el0_svc_common+0x88/0xf4
526436311.141499 1010145 24413 24413 W         : do_el0_svc+0x2c/0xb0
526436311.141503 1010145 24413 24413 W         : el0_svc+0x2c/0x90
526436311.141505 1010145 24413 24413 W         : el0t_64_sync_handler+0x68/0xb4
526436311.141508 1010145 24413 24413 W         : el0t_64_sync+0x1a4/0x1a8
526436311.141511 1010145 24413 24413 W Mem-Info:
526436311.141513 1010145 24413 24413 W active_anon: 1163580 inactive_anon:268858 isolated_anon:0
526436311.141513 1010145 24413 24413 W active_file: 316094 inactive_file:393281 isolated_file:0
526436311.141513 1010145 24413 24413 W unevictable: 7858 dirty:3588 writeback:0
526436311.141513 1010145 24413 24413 W slab_reclaimable: 125487 slab_unreclaimable:609811
526436311.141513 1010145 24413 24413 W mapped  : 316429 shmem:22645 pagetables:39174
526436311.141513 1010145 24413 24413 W sec_pagetables: 0 bounce:0
526436311.141513 1010145 24413 24413 W kernel_misc_reclaimable: 113360
526436311.141513 1010145 24413 24413 W free    : 51906 free_pcp:126 free_cma:0
526436311.141519 1010145 24413 24413 W         : Node 0 active_anon:4654320kB inactive_anon:1075432kB active_file:1264376kB inactive_file:1573124kB unevictable:31432kB isolated(anon):0kB isolated(file):0kB 
mapped:1265716kB dirty:14352kB writeback:0kB shmem:90580kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 2238464kB writeback_tmp:0kB kernel_stack:101440kB shadow_call_stack:0kB pagetables:156696kB
sec_pagetables:0kB all_unreclaimable? no 526436311.141524 1010145 24413 24413 W Normal free: 207624kB boost:0kB min:73728kB low:136820kB high:199912kB reserved_highatomic:126976KB active_anon:4654036kB inactive_anon:1075432kB active_file:1264668kB
inactive_file:1573124kB unevictable:31432kB writepending:14352kB present:19614720kB managed:19119836kB mlocked:31432kB bounce:0kB free_pcp:504kB local_pcp:0kB free_cma:0kB
526436311.141528 1010145 24413 24413 W lowmem_reserve[]: 0 0 0 0 526436311.141532 1010145 24413 24413 W Normal : 9455*4kB (UMEH) 14965*8kB (UMEH) 2110*16kB (UMEH) 419*32kB (UMH) 13*64kB (UH) 5*128kB (UH) 3*256kB (H) 0*512kB 0*1024kB 0*2048kB 0*4096kB = 206948kB 526436311.141543 1010145 24413 24413 W : 736708 total pagecache pages 526436311.141545 1010145 24413 24413 W : 2035 pages in swap cache 526436311.141546 1010145 24413 24413 W : Free swap = 8050624kB 526436311.141548 1010145 24413 24413 W : Total swap = 10515904kB 526436311.141549 1010145 24413 24413 W : 4903680 pages RAM 526436311.141551 1010145 24413 24413 W : 0 pages HighMem/MovableOnly 526436311.141552 1010145 24413 24413 W : 123721 pages reserved 526436311.141553 1010145 24413 24413 W : 4096 pages cma reserved

 

posted on 2026-10-02 14:53  Hello-World3  阅读(4)  评论(0)    收藏  举报

导航