alloc_pages는 free list를 보기 전에 무엇을 결정합니까

alloc_pages()가 실패하면 먼저 free memory를 확인하게 됩니다. 하지만 allocator는 free list를 꺼내 보기 전에 요청 크기, task의 reclaim 제약, 허용 node, zone 종류와 fallback 정책을 정합니다. 이 단계에서 후보가 사라질 수도 있고, fault injection으로 실패할 수도 있습니다.

Linux v6.1의 일반 non-bulk 경로를 따라가며 요청이 alloc_context로 바뀌는 과정을 살펴보겠습니다. 이후 kernel의 wrapper 이름을 이 호출 관계와 그대로 일치시키지는 않겠습니다.

반환하는 것이 page인지 주소인지부터 확인합니다

__alloc_pages(gfp, order, preferred_nid, nodemask)는 page descriptor를 반환합니다. GFP는 허용 행동과 배치 조건, order는 연속 범위의 크기, preferred node는 탐색의 선호 시작점, nodemask는 허용 node 범위를 나타냅니다. 선호 node는 다른 node 사용을 무조건 금지하는 조건과는 다릅니다.

__get_free_pages() 계열은 kernel virtual address를 반환하는 interface입니다. descriptor를 반환하는 API와 직접 접근 가능한 주소를 반환하는 API는 HIGHMEM 등의 조건에서 계약이 달라집니다. __folio_alloc()은 __GFP_COMP를 더해 compound 준비를 요청합니다. order가 양수인 __alloc_pages() 호출이 모두 같은 compound metadata를 가진다고 가정할 수 없습니다.

get_zeroed_page()는 주소 반환 경로에 zeroing 요구를 더합니다. byte 크기를 받는 alloc_pages_exact()는 필요한 page 수를 충족할 order로 얻은 뒤 불필요한 뒤쪽 page를 반환합니다. 마지막 page 내부의 byte까지 다른 owner에게 나누어 주는 API는 아닙니다. __GFP_COMP·HIGHMEM을 허용하는 일반 compound API와 계약이 다르며 반환에는 free_pages_exact()를 사용해야 합니다.

node를 받는 alloc_pages_node() 계열은 선호 node 선택을 아래쪽으로 전달하고, slab의 backing allocation이나 large-kmalloc 경로도 page 공급원을 사용합니다. 이 글의 v6.1에서 실제로 확인하는 SLUB 함수는 alloc_slab_page(), large-kmalloc 내부 함수는 __kmalloc_large_node()입니다. 다른 판본의 함수명을 같은 tag의 호출 관계로 섞지 않아야 합니다. 작은 object를 관리하는 slab algorithm과 physical block을 공급하는 page allocator를 구별하면 어느 층의 일인지 읽기 쉬워집니다. 반환형·NUMA 분기·해제 짝과 각 wrapper 전문은 allocation API 계약 글에서 이어서 살펴봅니다.

bulk API는 여러 order-0 page를 모으는 용도입니다. 결과 page 여러 개가 서로 연속한 큰 block 하나라는 보장은 없습니다. node·memory policy를 받는 bulk wrapper도 있고 일반 allocation으로 내려가는 fallback도 있지만, 그 사실이 bulk 결과에 큰 연속 범위를 보장하지는 않습니다. “한 번 호출해서 여러 page를 받았다”는 성질과 “하나의 higher-order block을 받았다”는 성질을 나누어야 합니다.

이 호출 관계는 일반적인 page-backed allocation의 주요 경로를 설명합니다. early boot의 memblock, device 전용 공급원, 별도 huge-page 관리까지 모든 physical allocation이 반드시 같은 한 함수로 들어온다는 증명은 아닙니다. 또한 kmalloc()의 작은 object 요청이 매번 새 physical page allocation을 일으킨다는 뜻도 아닙니다. cache 안에서 충족할 수 없는 공급이 필요할 때의 층간 관계입니다.

bulk의 기존 array slot·부분 성공·단일-page fallback과 실제 함수 전문은 bulk 반환값 글에서 구별합니다. wrapper의 public header 선언과 실제 module-export 표시는 같은 집합이 아닙니다. 특히 CONFIG_NUMA의 folio_alloc()은 alloc_pages()를 거치고, 그 밖의 구성은 __folio_alloc_node()로 이어지므로 한 줄의 호출도를 모든 구성에 그대로 적용할 수 없습니다.

GFP를 그대로 믿지 않고 현재 context에 맞춥니다

진입부에서는 지원 범위를 벗어난 order를 거절하고 gfp_allowed_mask를 적용합니다. 이 global mask는 boot 단계나 power-management 전환 등에서 허용 행동을 제한할 수 있습니다. 그다음 current_gfp_context()가 task의 NOFS·NOIO·PIN 관련 scope를 반영합니다.

따라서 caller가 GFP_KERNEL을 썼다는 사실만으로 그 실행에서 filesystem reclaim이 허용되었다고 단정할 수 없습니다. 위쪽 호출자가 NOFS scope를 열었을 수 있습니다. debug 경로에서 기록된 mask를 읽을 때는 요청 원본과 실제 allocation에 쓴 alloc_gfp도 구별해야 합니다.

prepare_alloc_pages()는 후보 공간을 준비하고 fault-injection 여부 등도 확인합니다. fault injection을 켠 시험 환경에서는 free page가 충분해도 의도적으로 실패할 수 있습니다. 반대로 이 준비가 성공했다고 실제 free block이 확보된 것은 아닙니다.

alloc_context는 후보와 탐색 정책을 묶습니다

highest_zoneidx는 이번 요청이 사용할 수 있는 최고 zone 종류입니다. zonelist는 node와 zone의 탐색 순서를, nodemask는 그중 허용 node를 제한합니다. migratetype은 free-list grouping에 사용하고 preferred_zoneref는 탐색의 선호 entry를 가리킵니다. spread_dirty_pages는 쓰기 용도의 allocation을 특정 node에 지나치게 몰지 않으려는 fast-path hint입니다.

cpuset이 활성화된 task context에서 별도 nodemask가 없으면 fast path에서 현재 task의 mems_allowed를 활용하는 분기가 있습니다. 다른 경우에는 내부 ALLOC_CPUSET으로 zone별 허용 검사를 유지합니다. 검사 대상은 memory node입니다. 실행 중인 CPU가 어느 cpuset에 속하는지만 검사하는 알고리즘으로 설명하면 안 됩니다.

__GFP_HARDWALL 같은 GFP 조건과 내부 cpuset 검사는 관련되어 있지만 동일한 bit는 아닙니다. caller의 정책을 준비 과정에서 내부 표현으로 바꾸고 후보별로 적용하는 구조입니다. nodemask, cpuset, zonelist 중 하나만 보면 나머지 제한을 놓칠 수 있습니다.

내부 ALLOC flag는 GFP와 다른 mask입니다

첫 시도는 보통 ALLOC_WMARK_LOW를 기준으로 합니다. ALLOC_WMARK_MIN, LOW, HIGH는 watermark 종류를 선택하는 표현이고 ALLOC_NO_WATERMARKS는 특별한 reserve 접근 경로와 연결됩니다. HIGH·HARDER·OOM은 mark 완화에, CPUSET·CMA·KSWAPD는 각각 별도의 허용 조건에 관여합니다.

이 mask를 GFP bit와 섞어 전달할 수는 없습니다. 두 집합의 bit 위치가 우연히 비슷해 보이더라도 API의 입력이 다릅니다. ALLOC_NOFRAGMENT도 모든 다른 migrate type 사용을 절대 금지한다는 뜻이 아닙니다. whole-pageblock fallback을 허용하거나 locality 조건에 따라 제약을 완화하는 분기가 있습니다.

다음은 Linux v6.1의 내부 ALLOC 정의 전체 묶음입니다. mm/internal.h의 SPDX는 GPL-2.0-or-later이며 들여쓰기만 정리했습니다.

#define ALLOC_WMARK_MIN        WMARK_MIN
#define ALLOC_WMARK_LOW        WMARK_LOW
#define ALLOC_WMARK_HIGH    WMARK_HIGH
#define ALLOC_NO_WATERMARKS    0x04 /* don't check watermarks at all */

/* Mask to get the watermark bits */
#define ALLOC_WMARK_MASK    (ALLOC_NO_WATERMARKS-1)

/*
 * Only MMU archs have async oom victim reclaim - aka oom_reaper so we
 * cannot assume a reduced access to memory reserves is sufficient for
 * !MMU
 */
#ifdef CONFIG_MMU
#define ALLOC_OOM        0x08
#else
#define ALLOC_OOM        ALLOC_NO_WATERMARKS
#endif

#define ALLOC_HARDER         0x10 /* try to alloc harder */
#define ALLOC_HIGH         0x20 /* __GFP_HIGH set */
#define ALLOC_CPUSET         0x40 /* check for correct cpuset */
#define ALLOC_CMA         0x80 /* allow allocations from CMA areas */
#ifdef CONFIG_ZONE_DMA32
#define ALLOC_NOFRAGMENT    0x100 /* avoid mixing pageblock types */
#else
#define ALLOC_NOFRAGMENT      0x0
#endif
#define ALLOC_KSWAPD        0x800 /* allow waking of kswapd, __GFP_KSWAPD_RECLAIM set */

OOM의 별도 완화 bit는 CONFIG_MMU 조건입니다. NOMMU에서는 OOM이 NO_WATERMARKS와 같은 값으로 정의되므로 MMU용 watermark 비율 표를 그대로 적용할 수 없습니다. DMA32 구성이 없으면 NOFRAGMENT는 0입니다. 이름이 header에 있다고 모든 build에서 독립적인 유효 bit라는 뜻은 아닙니다. LOW·MIN·HIGH는 enum index를 선택하고 NO_WATERMARKS는 별도 우회 권한이라는 차이도 유지해야 합니다.

v6.1에서 최초 NOFRAGMENT의 조건을 만드는 함수는 다음과 같습니다. alloc_flags_nofragment()의 전문이며 SPDX는 GPL-2.0-only입니다.

static inline unsigned int
alloc_flags_nofragment(struct zone *zone, gfp_t gfp_mask)
{
    unsigned int alloc_flags;

    /*
     * __GFP_KSWAPD_RECLAIM is assumed to be the same as ALLOC_KSWAPD
     * to save a branch.
     */
    alloc_flags = (__force int) (gfp_mask & __GFP_KSWAPD_RECLAIM);

#ifdef CONFIG_ZONE_DMA32
    if (!zone)
        return alloc_flags;

    if (zone_idx(zone) != ZONE_NORMAL)
        return alloc_flags;

    /*
     * If ZONE_DMA32 exists, assume it is the one after ZONE_NORMAL and
     * the pointer is within zone->zone_pgdat->node_zones[]. Also assume
     * on UMA that if Normal is populated then so is DMA32.
     */
    BUILD_BUG_ON(ZONE_NORMAL - ZONE_DMA32 != 1);
    if (nr_online_nodes > 1 && !populated_zone(--zone))
        return alloc_flags;

    alloc_flags |= ALLOC_NOFRAGMENT;
#endif /* CONFIG_ZONE_DMA32 */
    return alloc_flags;
}

DMA32 구성에서 preferred zone이 NORMAL이어야 NOFRAGMENT를 더합니다. NUMA에서는 같은 node의 앞선 DMA32 zone이 populated인지도 확인하며, UMA에는 NORMAL이 populated이면 DMA32도 그렇다는 가정을 둡니다. HIGHMEM까지 같은 lower-zone 확산을 강제하면 lowmem 부담이 커질 수 있고 DMA는 너무 작을 수 있어, 모든 이웃 zone에 일률적으로 적용하는 정책이 아닙니다.

이 함수는 KSWAPD 요청 bit도 전달합니다. 해당 bit의 위치를 의도적으로 공유하는 최적화이며 GFP 전체와 ALLOC 전체가 교환 가능한 mask라는 뜻은 아닙니다. CMA는 prepare_alloc_pages() 안의 별도 변환으로, CONFIG_CMA에서 실제 migratetype이 MOVABLE일 때 허용 flag를 더합니다.

Zone loop는 locality와 dirty 부담도 살핍니다

get_page_from_freelist()는 preferred_zoneref에서 시작해 highest zone과 nodemask에 맞는 후보를 순회합니다. 각 zone에서 cpuset 제약을 확인하고, dirty spreading이 켜졌다면 새 node마다 node_dirty_ok()를 확인합니다. 같은 node에 여러 zone이 있다고 같은 판단을 불필요하게 반복하지 않도록 처리하는 부분도 있습니다.

WRITE hint 때문에 dirty 한도를 넘은 node를 건너뛰는 것은 새로운 dirty 부담을 다른 곳에 배치하려는 정책입니다. 이 hint가 모든 allocation 실패보다 우선하는 절대 조건은 아닙니다. slow path에서는 해당 hint를 끄고 allocation 가능성을 다시 검토할 수 있습니다.

NOFRAGMENT를 유지한 채 preferred node 밖으로 넘어가게 되면 flag를 지우고 다시 시도하는 분기가 있습니다. 가까운 곳에서 fragmentation을 덜 만드는 후보를 먼저 찾다가 정책을 완화하는 것입니다. __GFP_THISNODE처럼 원격 node 사용 자체를 제한하는 조건과는 다릅니다.

후보를 통과하면 watermark를 검사하고 rmqueue()로 실제 block을 얻습니다. watermark가 참이어도 동시 allocation 때문에 list에서 얻지 못할 수 있습니다. 성공한 block은 prep_new_page()를 거쳐 호출자에게 쓸 수 있는 상태로 준비됩니다.

watermark 실패가 언제나 곧바로 다음 zone을 뜻하지는 않습니다. ALLOC_NO_WATERMARKS 권한이면 특별한 획득 시도로 진행할 수 있고, deferred descriptor 초기화를 쓰는 configuration에서는 아직 준비하지 않은 범위를 확장해 다시 시도할 수 있습니다. node reclaim이 켜져 있고 거리 등 허용 조건이 맞으면 node_reclaim()도 검토합니다. scan하지 않았거나 회수 불가능하면 다음 후보로 가고, 회수 후에도 watermark를 다시 만족해야 이 zone에서 획득을 시도합니다.

후보를 모두 소진했어도 NOFRAGMENT 제한을 유지한 시도였다면 이를 지우고 재시도하는 경로가 있습니다. 단일 node에서도 fragmentation 제한 때문에 놓친 후보를 다시 볼 수 있는 것입니다. 이런 지역적인 재시도와 모든 fast 후보를 사용한 뒤의 __alloc_pages_slowpath()는 별개입니다.

Fast path 실패 뒤에는 제약과 상태를 다시 정리합니다

아래는 개념적 호출 관계이며 실행 가능한 kernel 구현이 아닙니다. fast path가 성공하면 slow path를 실행하지 않습니다.

__alloc_pages()
  current_gfp_context()
  prepare_alloc_pages()
  get_page_from_freelist()       [fast attempt]
    zone_watermark_fast()
    rmqueue()
    prep_new_page()              [only after obtaining a block]
  __alloc_pages_slowpath()       [only if the fast attempt failed]

첫 시도가 실패하면 실제 사용 GFP와 nodemask 등을 slow-path 조건에 맞게 정리하고 dirty spreading을 끕니다. reclaim, compaction, retry, OOM 관련 판단이 이어질 수 있지만 caller가 허용한 행동을 넘어 무조건 sleep하는 것은 아닙니다. direct reclaim을 허용하지 않는 요청과 일반 sleepable 요청의 실패 경로는 같을 수 없습니다.

마지막에는 memcg charge와 trace·sanitizer hook도 남습니다. “후보 zone을 찾았다”, “watermark를 통과했다”, “block을 얻었다”, “API가 성공했다”는 네 사건을 구별하면 실패 지점을 훨씬 정확하게 읽을 수 있습니다. v6.18의 stack trace에서는 allocation profiling wrapper와 _noprof 이름이 나타날 수 있으므로 public header도 해당 tag에서 확인해야 합니다.

alloc_context의 실제 선언과 변경 지점을 확인합니다

다음은 Linux v6.1의 mm/internal.h에 있는 struct alloc_context 전문입니다. SPDX는 GPL-2.0-or-later이며 들여쓰기만 정리했습니다.

struct alloc_context {
    struct zonelist *zonelist;
    nodemask_t *nodemask;
    struct zoneref *preferred_zoneref;
    int migratetype;

    /*
     * highest_zoneidx represents highest usable zone index of
     * the allocation request. Due to the nature of the zone,
     * memory on lower zone than the highest_zoneidx will be
     * protected by lowmem_reserve[highest_zoneidx].
     *
     * highest_zoneidx is also used by reclaim/compaction to limit
     * the target zone since higher zone than this index cannot be
     * usable for this allocation request.
     */
    enum zone_type highest_zoneidx;
    bool spread_dirty_pages;
};

상위 주석의 'mostly immutable'은 호출이 끝날 때까지 모든 field가 불변이라는 뜻이 아닙니다. 특히 아래 구현에서 nodemask는 fast path용 cpuset mask로 바뀌었다가 원래 인수로 돌아오며 spread_dirty_pages도 꺼집니다. slow path가 정책을 완화하거나 선호 zone을 다시 잡는 경우도 있습니다. 중간 함수에 const struct alloc_context *로 전달한다는 사실과 전체 allocation 과정의 불변성을 구별해야 합니다.

prepare_alloc_pages의 전체 분기입니다

다음은 mm/page_alloc.c의 prepare_alloc_pages() 전문입니다. SPDX는 GPL-2.0-only입니다. CMA 권한 변환까지 포함한 해당 tag의 구현입니다.

static inline bool prepare_alloc_pages(gfp_t gfp_mask, unsigned int order,
        int preferred_nid, nodemask_t *nodemask,
        struct alloc_context *ac, gfp_t *alloc_gfp,
        unsigned int *alloc_flags)
{
    ac->highest_zoneidx = gfp_zone(gfp_mask);
    ac->zonelist = node_zonelist(preferred_nid, gfp_mask);
    ac->nodemask = nodemask;
    ac->migratetype = gfp_migratetype(gfp_mask);

    if (cpusets_enabled()) {
        *alloc_gfp |= __GFP_HARDWALL;
        /*
         * When we are in the interrupt context, it is irrelevant
         * to the current task context. It means that any node ok.
         */
        if (in_task() && !ac->nodemask)
            ac->nodemask = &cpuset_current_mems_allowed;
        else
            *alloc_flags |= ALLOC_CPUSET;
    }

    might_alloc(gfp_mask);

    if (should_fail_alloc_page(gfp_mask, order))
        return false;

    *alloc_flags = gfp_to_alloc_flags_cma(gfp_mask, *alloc_flags);

    /* Dirty zone balancing only done in the fast path */
    ac->spread_dirty_pages = (gfp_mask & __GFP_WRITE);

    /*
     * The preferred zone is used for statistics but crucially it is
     * also used as the starting point for the zonelist iterator. It
     * may get reset for allocations that ignore memory policies.
     */
    ac->preferred_zoneref = first_zones_zonelist(ac->zonelist,
                    ac->highest_zoneidx, ac->nodemask);

    return true;
}

in_task()이면서 별도 nodemask가 없는 경우에만 현재 cpuset mask를 빠른 후보 필터로 빌립니다. interrupt context이거나 명시 mask가 있으면 ALLOC_CPUSET으로 후보별 검사를 남깁니다. should_fail_alloc_page()는 시험용 allocation failure 주입이지 CPU의 page fault를 처리하는 함수가 아닙니다. false 반환으로 준비가 중단되면 뒤쪽 free-list 탐색도 실행하지 않습니다.

일반 allocation 한 번의 진입부터 반환까지입니다

다음은 같은 파일의 __alloc_pages() 전문입니다. SPDX는 GPL-2.0-only입니다. bulk 구현이나 모든 wrapper까지 붙인 번역 단위가 아니라 이 함수의 온전한 scope입니다.

struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid,
                            nodemask_t *nodemask)
{
    struct page *page;
    unsigned int alloc_flags = ALLOC_WMARK_LOW;
    gfp_t alloc_gfp; /* 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 (WARN_ON_ONCE_GFP(order >= MAX_ORDER, gfp))
        return NULL;

    gfp &= gfp_allowed_mask;
    /*
     * 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}. And PF_MEMALLOC_PIN which ensures
     * movable zones are not used during allocation.
     */
    gfp = current_gfp_context(gfp);
    alloc_gfp = gfp;
    if (!prepare_alloc_pages(gfp, order, preferred_nid, nodemask, &ac,
            &alloc_gfp, &alloc_flags))
        return NULL;

    /*
     * 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);

    /* First allocation attempt */
    page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac);
    if (likely(page))
        goto out;

    alloc_gfp = gfp;
    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_gfp, order, &ac);

out:
    if (memcg_kmem_enabled() && (gfp & __GFP_ACCOUNT) && page &&
        unlikely(__memcg_kmem_charge_page(page, gfp, order) != 0)) {
        __free_pages(page, order);
        page = NULL;
    }

    trace_mm_page_alloc(page, order, alloc_gfp, ac.migratetype);
    kmsan_alloc_page(page, order, alloc_gfp);

    return page;
}

fast path의 page가 있으면 바로 out으로 가므로 slow path와 dirty hint 해제는 실행하지 않습니다. fast path가 실패했을 때만 alloc_gfp와 nodemask를 되돌려 slow path를 호출합니다. 끝의 kmemcg charge는 양쪽 성공 경로에 공통이며 실패하면 __free_pages()로 반환하고 NULL로 바꿉니다. trace와 KMSAN hook도 이 반환 경계에 있으므로 list 획득만을 API 성공과 같게 보면 안 됩니다.

Zone 탐색의 재시도까지 포함한 구현입니다

마지막으로 get_page_from_freelist() 전문을 보겠습니다. SPDX는 GPL-2.0-only입니다. retry는 함수 전체의 zone 탐색을 다시 시작하고 try_this_zone은 같은 zone에서 획득을 시도하는 label입니다. 둘은 같은 재시도가 아닙니다.

/*
 * get_page_from_freelist goes through the zonelist trying to allocate
 * a page.
 */
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
                        const struct alloc_context *ac)
{
    struct zoneref *z;
    struct zone *zone;
    struct pglist_data *last_pgdat = NULL;
    bool last_pgdat_dirty_ok = false;
    bool no_fallback;

retry:
    /*
     * Scan zonelist, looking for a zone with enough free.
     * See also __cpuset_node_allowed() comment in kernel/cgroup/cpuset.c.
     */
    no_fallback = alloc_flags & ALLOC_NOFRAGMENT;
    z = ac->preferred_zoneref;
    for_next_zone_zonelist_nodemask(zone, z, ac->highest_zoneidx,
                    ac->nodemask) {
        struct page *page;
        unsigned long mark;

        if (cpusets_enabled() &&
            (alloc_flags & ALLOC_CPUSET) &&
            !__cpuset_zone_allowed(zone, gfp_mask))
                continue;
        /*
         * When allocating a page cache page for writing, we
         * want to get it from a node that is within its dirty
         * limit, such that no single node holds more than its
         * proportional share of globally allowed dirty pages.
         * The dirty limits take into account the node's
         * lowmem reserves and high watermark so that kswapd
         * should be able to balance it without having to
         * write pages from its LRU list.
         *
         * XXX: For now, allow allocations to potentially
         * exceed the per-node dirty limit in the slowpath
         * (spread_dirty_pages unset) before going into reclaim,
         * which is important when on a NUMA setup the allowed
         * nodes are together not big enough to reach the
         * global limit.  The proper fix for these situations
         * will require awareness of nodes in the
         * dirty-throttling and the flusher threads.
         */
        if (ac->spread_dirty_pages) {
            if (last_pgdat != zone->zone_pgdat) {
                last_pgdat = zone->zone_pgdat;
                last_pgdat_dirty_ok = node_dirty_ok(zone->zone_pgdat);
            }

            if (!last_pgdat_dirty_ok)
                continue;
        }

        if (no_fallback && nr_online_nodes > 1 &&
            zone != ac->preferred_zoneref->zone) {
            int local_nid;

            /*
             * If moving to a remote node, retry but allow
             * fragmenting fallbacks. Locality is more important
             * than fragmentation avoidance.
             */
            local_nid = zone_to_nid(ac->preferred_zoneref->zone);
            if (zone_to_nid(zone) != local_nid) {
                alloc_flags &= ~ALLOC_NOFRAGMENT;
                goto retry;
            }
        }

        mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK);
        if (!zone_watermark_fast(zone, order, mark,
                       ac->highest_zoneidx, alloc_flags,
                       gfp_mask)) {
            int ret;

#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
            /*
             * Watermark failed for this zone, but see if we can
             * grow this zone if it contains deferred pages.
             */
            if (static_branch_unlikely(&deferred_pages)) {
                if (_deferred_grow_zone(zone, order))
                    goto try_this_zone;
            }
#endif
            /* Checked here to keep the fast path fast */
            BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);
            if (alloc_flags & ALLOC_NO_WATERMARKS)
                goto try_this_zone;

            if (!node_reclaim_enabled() ||
                !zone_allows_reclaim(ac->preferred_zoneref->zone, zone))
                continue;

            ret = node_reclaim(zone->zone_pgdat, gfp_mask, order);
            switch (ret) {
            case NODE_RECLAIM_NOSCAN:
                /* did not scan */
                continue;
            case NODE_RECLAIM_FULL:
                /* scanned but unreclaimable */
                continue;
            default:
                /* did we reclaim enough */
                if (zone_watermark_ok(zone, order, mark,
                    ac->highest_zoneidx, alloc_flags))
                    goto try_this_zone;

                continue;
            }
        }

try_this_zone:
        page = rmqueue(ac->preferred_zoneref->zone, zone, order,
                gfp_mask, alloc_flags, ac->migratetype);
        if (page) {
            prep_new_page(page, order, gfp_mask, alloc_flags);

            /*
             * If this is a high-order atomic allocation then check
             * if the pageblock should be reserved for the future
             */
            if (unlikely(order && (alloc_flags & ALLOC_HARDER)))
                reserve_highatomic_pageblock(page, zone, order);

            return page;
        } else {
#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
            /* Try again if zone has deferred pages */
            if (static_branch_unlikely(&deferred_pages)) {
                if (_deferred_grow_zone(zone, order))
                    goto try_this_zone;
            }
#endif
        }
    }

    /*
     * It's possible on a UMA machine to get through all zones that are
     * fragmented. If avoiding fragmentation, reset and try again.
     */
    if (no_fallback) {
        alloc_flags &= ~ALLOC_NOFRAGMENT;
        goto retry;
    }

    return NULL;
}

NOFRAGMENT는 다른 node로 넘어가기 전에 완화되거나, 모든 zone을 살핀 뒤 다시 완화될 수 있습니다. deferred page가 늘어나면 같은 zone의 획득으로 이어질 수 있고, node reclaim은 결과별로 다음 후보 또는 watermark 재검사로 갈립니다. rmqueue()가 반환한 뒤에만 prep_new_page()와 필요한 highatomic reserve 관리가 실행됩니다. 이 순서 때문에 단순한 'zone 선택 → allocation' 두 칸만으로는 실패와 재시도를 설명하기 어렵습니다.

참고 자료