Zonned Allocator -2- (페이지 할당-Slowpath)



NUMA의 경우 처음 주어진 메모리 정책에 따른 zonelist에서 할당이 실패한 경우 __GFP_IO 및 GFP_FS를 제거하고 slowpath 단계를 진행한다. slowpath 단계를 진행하는 동안 __GFP_NO_SWAPD 요청이 없으면 kswapd를 깨워 백그라운드에서 페이지 회수도 동시에 진행한다. 페이지 할당으 시도하는 동안 __GFP_WAIT 요청이 있는 경우 page compaction이나 direct reclaim을 통해 할당 할 수 있는 페이지를 확보한다. 마지막으로 __GFP_NOFAIL 이나 __GFP_REPEAT 등이 허용되는 경우 페이지 할당이 성공할 때까지 재시도를 한다







static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
                                                struct alloc_context *ac)
        const gfp_t wait = gfp_mask & __GFP_WAIT;
        struct page *page = NULL;
        int alloc_flags;
        unsigned long pages_reclaimed = 0;
        unsigned long did_some_progress;
        enum migrate_mode migration_mode = MIGRATE_ASYNC;
        bool deferred_compaction = false;
        int contended_compaction = COMPACT_CONTENDED_NONE;

         * In the slowpath, we sanity check order to avoid ever trying to
         * reclaim >= MAX_ORDER areas which will never succeed. Callers may
         * be using allocators in order of preference for an area that is
         * too large.
        if (order >= MAX_ORDER) {
                WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
                return NULL;

         * GFP_THISNODE (meaning __GFP_THISNODE, __GFP_NORETRY and
         * __GFP_NOWARN set) should not cause reclaim since the subsystem
         * (f.e. slab) using GFP_THISNODE may choose to trigger reclaim
         * using a larger set of nodes after it has established that the
         * allowed per node queues are empty and that nodes are
         * over allocated.
        if (IS_ENABLED(CONFIG_NUMA) &&
            (gfp_mask & GFP_THISNODE) == GFP_THISNODE)
                goto nopage;
  • const gfp_t wait = gfp_mask & __GFP_WAIT;
    • __GFP_WAIT 플래그를 요청했는지 여부를 보관한다.
  • enum migrate_mode migration_mode = MIGRATE_ASYNC;
    • migration_mode를 MIGRATE_ASYNC로 설정하여 block 되지 않도록 한다.
  • if (order >= MAX_ORDER) { return NULL; }
    • MAX_order 이상을 요구하는 경우 할당을 포기하고 리턴한다.
  • if (IS_ENABLED(CONFIG_NUMA) && (gfp_mask & GFP_THISNODE) == GFP_THISNODE) goto nopage;
    • NUMA 시스템이면서 현재 노드로 제한 요청을 한 경우 페이지 할당을 포기한다.


        if (!(gfp_mask & __GFP_NO_KSWAPD))
                wake_all_kswapds(order, ac);

         * OK, we're below the kswapd watermark and have kicked background
         * reclaim. Now things get more complex, so set up alloc_flags according
         * to how we want to proceed.
        alloc_flags = gfp_to_alloc_flags(gfp_mask);

         * Find the true preferred zone if the allocation is unconstrained by
         * cpusets.
        if (!(alloc_flags & ALLOC_CPUSET) && !ac->nodemask) {
                struct zoneref *preferred_zoneref;
                preferred_zoneref = first_zones_zonelist(ac->zonelist,
                                ac->high_zoneidx, NULL, &ac->preferred_zone);
                ac->classzone_idx = zonelist_zone_idx(preferred_zoneref);

        /* This is the last chance, in general, before the goto nopage. */
        page = get_page_from_freelist(gfp_mask, order,
                                alloc_flags & ~ALLOC_NO_WATERMARKS, ac);
        if (page)
                goto got_pg;

        /* Allocate without watermarks if the context allows */
        if (alloc_flags & ALLOC_NO_WATERMARKS) {
                 * Ignore mempolicies if ALLOC_NO_WATERMARKS on the grounds
                 * the allocation is high priority and these type of
                 * allocations are system rather than user orientated
                ac->zonelist = node_zonelist(numa_node_id(), gfp_mask);

                page = __alloc_pages_high_priority(gfp_mask, order, ac);

                if (page) {
                        goto got_pg;

        /* Atomic allocations - we can't balance anything */
        if (!wait) {
                 * All existing users of the deprecated __GFP_NOFAIL are
                 * blockable, so warn of any new users that actually allow this
                 * type of allocation to fail.
                WARN_ON_ONCE(gfp_mask & __GFP_NOFAIL);
                goto nopage;

        /* Avoid recursion of direct reclaim */
        if (current->flags & PF_MEMALLOC)
                goto nopage;

        /* Avoid allocations with no watermarks from looping endlessly */
        if (test_thread_flag(TIF_MEMDIE) && !(gfp_mask & __GFP_NOFAIL))
                goto nopage;
  • retry:
    • 페이지 할당을 재시도할 때 retry 레이블로 이동되어 온다.
  • if (!(gfp_mask & __GFP_NO_KSWAPD)) wake_all_kswapds(order, ac);
    • __GFP_NO_KSWAPD 플래그가 사용되지 않은 경우 zonelist 중 현재 alloc context에 관계된 노드와 zone의 모든 kswpad들을 모두 깨운다.
  • alloc_flags = gfp_to_alloc_flags(gfp_mask);
    • gfp_mask에 따라 다음 항목을 alloc_flags에 추가할 수 있다.
      • __GFP_HIGH
      • ALLOC_CMA
  • if (!(alloc_flags & ALLOC_CPUSET) && !ac->nodemask) {
    • cpuset을 사용하지 않으면서 전체 노드에 대한 요청인 경우
  • preferred_zoneref = first_zones_zonelist(ac->zonelist, ac->high_zoneidx, NULL, &ac->preferred_zone); ac->classzone_idx = zonelist_zone_idx(preferred_zoneref);
    • zonelist에서 모든 노드와 high_zoneidx 이하의 zone에 대해 처음 발견된 zone
  • page = get_page_from_freelist(gfp_mask, order, alloc_flags & ~ALLOC_NO_WATERMARKS, ac);
    • alloc_flags에서 ALLOC_NO_WATERMARKS를 제거하고 페이지 할당을 요청한다.
  • if (page) goto got_pg;
    • 할당이 성공된 경우 got_pg로 이동한다.
  • if (alloc_flags & ALLOC_NO_WATERMARKS) {
    • 페이지 할당이 실패한 경우 ALLOC_NO_WATERMARKS 플래그가 설정된 경우
  • ac->zonelist = node_zonelist(numa_node_id(), gfp_mask); page = __alloc_pages_high_priority(gfp_mask, order, ac);
    • 워터 마크 제한 없이 페이지 할당을 시도하며 실패한 경우 __GFP_NOFAIL 요청을 사용한 경우 20ms씩 쉬어가며 성공할 때까지 시도한다.
  • if (!wait) { goto nopage; }
    • 요청이 sleep을 허용하지 않는 경우 compaction 등 다음 단계를 수행하지 않고 페이지 할당이 실패되어 함수를 빠져나간다.
  • if (current->flags & PF_MEMALLOC) goto nopage;
    • 현재 태스크가 PF_MEMALLOC 플래그를 가진 경우 compaction 등 다음 단계를 수행하지 않고 페이지 할당이 실패되어 함수를 빠져나간다.
  • if (test_thread_flag(TIF_MEMDIE) && !(gfp_mask & __GFP_NOFAIL)) goto nopage;
    • 현재 태스크가 TIF_MEMDIE 플래그를 가진 경우 compaction 등 다음 단계를 수행하지 않고 페이지 할당이 실패되어 함수를 빠져나간다.


         * Try direct compaction. The first pass is asynchronous. Subsequent
         * attempts after direct reclaim are synchronous
        page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,
        if (page)
                goto got_pg;

        /* Checks for THP-specific high-order allocations */
        if ((gfp_mask & GFP_TRANSHUGE) == GFP_TRANSHUGE) {
                 * If compaction is deferred for high-order allocations, it is
                 * because sync compaction recently failed. If this is the case
                 * and the caller requested a THP allocation, we do not want
                 * to heavily disrupt the system, so we fail the allocation
                 * instead of entering direct reclaim.
                if (deferred_compaction)
                        goto nopage;

                 * In all zones where compaction was attempted (and not
                 * deferred or skipped), lock contention has been detected.
                 * For THP allocation we do not want to disrupt the others
                 * so we fallback to base pages instead.
                if (contended_compaction == COMPACT_CONTENDED_LOCK)
                        goto nopage;

                 * If compaction was aborted due to need_resched(), we do not
                 * want to further increase allocation latency, unless it is
                 * khugepaged trying to collapse.
                if (contended_compaction == COMPACT_CONTENDED_SCHED
                        && !(current->flags & PF_KTHREAD))
                        goto nopage;
  • page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac, migration_mode, &contended_compaction, &deferred_compaction);
    • page compaction 수행 후 페이지 할당을 시도한다.
  • if ((gfp_mask & GFP_TRANSHUGE) == GFP_TRANSHUGE) {
    • GFP_TRANSHUGE 플래그가 사용된 경우
  • if (deferred_compaction) goto nopage;
    • 최근에 high order 페이지 요청에 대해 compaction이 한 번 실패되어 compaction이 유예된 경우 할당을 포기하고 함수를 빠져나간다.
  • if (contended_compaction == COMPACT_CONTENDED_LOCK) goto nopage;
    • 모든 zone들이 compaction이 시도 되면서 lock contension(혼잡)이 발생하면 후보(fallback) zone의 사용을 포기하고 함수를 빠져나간다.
  • if (contended_compaction == COMPACT_CONTENDED_SCHED && !(current->flags & PF_KTHREAD)) goto nopage;
    • compaction 중 리스케쥴링되었던 경우 이면서 현재 태스크가 커널 스레드가 아닌 경우 할당을 포기하고 함수를 빠져나간다.


         * It can become very expensive to allocate transparent hugepages at
         * fault, so use asynchronous memory compaction for THP unless it is
         * khugepaged trying to collapse.
        if ((gfp_mask & GFP_TRANSHUGE) != GFP_TRANSHUGE ||
                                                (current->flags & PF_KTHREAD))
                migration_mode = MIGRATE_SYNC_LIGHT;

        /* Try direct reclaim and then allocating */
        page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac,
        if (page)
                goto got_pg;

        /* Check if we should retry the allocation */
        pages_reclaimed += did_some_progress;
        if (should_alloc_retry(gfp_mask, order, did_some_progress,
                                                pages_reclaimed)) {
                 * If we fail to make progress by freeing individual
                 * pages, but the allocation wants us to keep going,
                 * start OOM killing tasks.
                if (!did_some_progress) {
                        page = __alloc_pages_may_oom(gfp_mask, order, ac,
                        if (page)
                                goto got_pg;
                        if (!did_some_progress)
                                goto nopage;
                /* Wait for some write requests to complete then retry */
                wait_iff_congested(ac->preferred_zone, BLK_RW_ASYNC, HZ/50);
                goto retry;
        } else {
                 * High-order allocations do not necessarily loop after
                 * direct reclaim and reclaim/compaction depends on compaction
                 * being called after reclaim so call directly if necessary
                page = __alloc_pages_direct_compact(gfp_mask, order,
                                        alloc_flags, ac, migration_mode,
                if (page)
                        goto got_pg;

        warn_alloc_failed(gfp_mask, order, NULL);
        return page;
  • if ((gfp_mask & GFP_TRANSHUGE) != GFP_TRANSHUGE || (current->flags & PF_KTHREAD)) migration_mode = MIGRATE_SYNC_LIGHT;
    • GFP_TRANSHUGE 요청이 아니거나 현재 태스크가 커널 스레드인 경우 migration 모드를 MIGRATE_SYNC_LIGHT로 설정하여 dirty page를 제외하고 reclaim을 시도할 수 있도록 설정한다.
  • page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac, &did_some_progress);
    • 일단 먼저 direct reclaim을 통해 페이지 회수 후 할당을 시도한다.
  • pages_reclaimed += did_some_progress; if (should_alloc_retry(gfp_mask, order, did_some_progress, pages_reclaimed)) {
    • 재시도 할 수 있는지 확인한다.
  • if (!did_some_progress) { page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress);
    • 회수된 페이지가 없으면 마지막으로 할당을 다시 시도하고 없는 경우 oom을 발생시킨다.
  • if (page) goto got_pg; if (!did_some_progress) goto nopage;
    • 페이지가 할당된 경우 성공적으로 완료되며 그렇지 않고 페이지 회수가 전혀 되지 않은 경우 할당 실패로 함수를 빠져나간다.
  • wait_iff_congested(ac->preferred_zone, BLK_RW_ASYNC, HZ/50); goto retry;
    • 회수된 페이지가 일부 있었던 경우 할당에 각 태스크들이 경쟁하여 혼잡한 경우 약 20ms 정도 잠시 쉬었다가 다시 시도한다.
  • } else { page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac, migration_mode, &contended_compaction, &deferred_compaction);
    • 최종적으로 direct compact 후에 페이지 할당을 시도한다. 할당이 실패하면 함수를 빠져나간다.




static inline int
gfp_to_alloc_flags(gfp_t gfp_mask)
        int alloc_flags = ALLOC_WMARK_MIN | ALLOC_CPUSET;
        const bool atomic = !(gfp_mask & (__GFP_WAIT | __GFP_NO_KSWAPD));

        /* __GFP_HIGH is assumed to be the same as ALLOC_HIGH to save a branch. */
        BUILD_BUG_ON(__GFP_HIGH != (__force gfp_t) ALLOC_HIGH);

         * The caller may dip into page reserves a bit more if the caller
         * cannot run direct reclaim, or if the caller has realtime scheduling
         * policy or is asking for __GFP_HIGH memory.  GFP_ATOMIC requests will
         * set both ALLOC_HARDER (atomic == true) and ALLOC_HIGH (__GFP_HIGH).
        alloc_flags |= (__force int) (gfp_mask & __GFP_HIGH);

        if (atomic) {
                 * Not worth trying to allocate harder for __GFP_NOMEMALLOC even
                 * if it can't schedule.
                if (!(gfp_mask & __GFP_NOMEMALLOC))
                        alloc_flags |= ALLOC_HARDER;
                 * Ignore cpuset mems for GFP_ATOMIC rather than fail, see the
                 * comment for __cpuset_node_allowed().
                alloc_flags &= ~ALLOC_CPUSET;
        } else if (unlikely(rt_task(current)) && !in_interrupt())
                alloc_flags |= ALLOC_HARDER;

        if (likely(!(gfp_mask & __GFP_NOMEMALLOC))) {
                if (gfp_mask & __GFP_MEMALLOC)
                        alloc_flags |= ALLOC_NO_WATERMARKS;
                else if (in_serving_softirq() && (current->flags & PF_MEMALLOC))
                        alloc_flags |= ALLOC_NO_WATERMARKS;
                else if (!in_interrupt() &&
                                ((current->flags & PF_MEMALLOC) ||
                        alloc_flags |= ALLOC_NO_WATERMARKS;
        if (gfpflags_to_migratetype(gfp_mask) == MIGRATE_MOVABLE)
                alloc_flags |= ALLOC_CMA;
        return alloc_flags;

gfp_mask에 따라 alloc_flags를 설정한다. 추가 설정될 수 있는 할당 플래그는 다음과 같다.

  • __GFP_HIGH


  • int alloc_flags = ALLOC_WMARK_MIN | ALLOC_CPUSET;
    • alloc_flags에 min 워터마크 할당과 cpuset 사용을 하도록 한다.
  • const bool atomic = !(gfp_mask & (__GFP_WAIT | __GFP_NO_KSWAPD));
    • __GFP_WAIT 또는 __GFP_NO_KSWAPD 요청이 없는 경우 atomic을 true로 설정한다.
  •  alloc_flags |= (__force int) (gfp_mask & __GFP_HIGH);
    • gfp_mask에 __GFP_HIGH가 요청된 경우 alloc_flags에도 추가한다.
  • if (atomic) { if (!(gfp_mask & __GFP_NOMEMALLOC)) alloc_flags |= ALLOC_HARDER; alloc_flags &= ~ALLOC_CPUSET;
    • atomic 처리가 가능한 경우이면 ALLOC_CPUSET을 제거하고 또한 __GFP_NOMEMALLOC 요청이 없으면 alloc_flags에 ALLOC_HARDER를 추가한다.
  • } else if (unlikely(rt_task(current)) && !in_interrupt()) alloc_flags |= ALLOC_HARDER;
    • atomic 처리가 가능한 경우가 아니지만 적은 확률로 현재 태스크가 100이하의 높은 우선 순위를 가진 태스이면서 인터럽트 핸들러에서 호출된 경우가 아니면 ALLOC_HARDER를 추가한다.
  • if (likely(!(gfp_mask & __GFP_NOMEMALLOC))) {
    • 높은 확률로 __GFP_NOMEMALLOC 요청이 없는 경우
  • if (gfp_mask & __GFP_MEMALLOC) alloc_flags |= ALLOC_NO_WATERMARKS;
    • __GFP_MEMALLOC 요청이 있는 경우 ALLOC_NO_WATERMARKS를 추가하여 워터마크를 사용하지 않게 한다.
  • else if (in_serving_softirq() && (current->flags & PF_MEMALLOC)) alloc_flags |= ALLOC_NO_WATERMARKS;
    • softirq에서 호출된 경우이면서 PF_MEMALLOC 요청이 있는 경우 ALLOC_NO_WATERMARKS를 추가하여 워터마크를 사용하지 않게 한다.
  • else if (!in_interrupt() && ((current->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))) alloc_flags |= ALLOC_NO_WATERMARKS;
    • 인터럽트 핸들러에서 호출된 경우가 아니면서 현재 태스크의 플래그에 PF_MEMALLOC를 가진 경우 또는 작은 확률로 현재 태스크가 TIF_MEMDIE 플래그를 가진 경우 ALLOC_NO_WATERMARKS를 추가하여 워터마크를 사용하지 않게 한다.
  • if (gfpflags_to_migratetype(gfp_mask) == MIGRATE_MOVABLE) alloc_flags |= ALLOC_CMA;
    • gfp 플래그로 변환한 migrate 타입이 MIGRATE_MOVABLE인 경우 alloc_flags에 ALLOC_CMA를 추가한다.




/* Convert GFP flags to their corresponding migrate type */
static inline int gfpflags_to_migratetype(const gfp_t gfp_flags)
        WARN_ON((gfp_flags & GFP_MOVABLE_MASK) == GFP_MOVABLE_MASK);

        if (unlikely(page_group_by_mobility_disabled))
                return MIGRATE_UNMOVABLE;

        /* Group based on mobility */
        return (((gfp_flags & __GFP_MOVABLE) != 0) << 1) |
                ((gfp_flags & __GFP_RECLAIMABLE) != 0);

GFP 플래그에 대응하는 migrate 타입을 다음 중 하나로 변환한다.



  • if (unlikely(page_group_by_mobility_disabled)) return MIGRATE_UNMOVABLE;
    • 적은 확률로 mobility 타입별로 페이지 그루핑을 하지 않는 경우 MIGRATE_UNVMOVABLE 한 가지만을 사용한다.
  • return (((gfp_flags & __GFP_MOVABLE) != 0) << 1) | ((gfp_flags & __GFP_RECLAIMABLE) != 0);
    • gfp_flags의 mobility 타입으로 migrate 타입을 다음 중 하나로 결정한다.




 * This is called in the allocator slow-path if the allocation request is of
 * sufficient urgency to ignore watermarks and take other desperate measures
static inline struct page *
__alloc_pages_high_priority(gfp_t gfp_mask, unsigned int order,
                                const struct alloc_context *ac)
        struct page *page;

        do {
                page = get_page_from_freelist(gfp_mask, order,
                                                ALLOC_NO_WATERMARKS, ac);

                if (!page && gfp_mask & __GFP_NOFAIL)
                        wait_iff_congested(ac->preferred_zone, BLK_RW_ASYNC,
        } while (!page && (gfp_mask & __GFP_NOFAIL));

        return page;

워터 마크 제한 없이 페이지 할당을 시도하며 실패한 경우 __GFP_NOFAIL 요청을 사용한 경우 20ms씩 쉬어가며 성공할 때까지 시도한다.

  • page = get_page_from_freelist(gfp_mask, order, ALLOC_NO_WATERMARKS, ac);
    • 워터 마크 제한 없이 페이지 할당을 시도한다.
  • if (!page && gfp_mask & __GFP_NOFAIL) wait_iff_congested(ac->preferred_zone, BLK_RW_ASYNC, HZ/50);
    • 페이지가 할당되지 않고 __GFP_NOFAIL 옵션이 있는 경우 잠시 20ms 정도 쉰다.
  • } while (!page && (gfp_mask & __GFP_NOFAIL));
    • 페이지가 할당되지 않고 __GFP_NOFAIL 옵션이 있는 경우 다시 시도한다.




static inline int
should_alloc_retry(gfp_t gfp_mask, unsigned int order,
                                unsigned long did_some_progress,
                                unsigned long pages_reclaimed)
        /* Do not loop if specifically requested */
        if (gfp_mask & __GFP_NORETRY)
                return 0;

        /* Always retry if specifically requested */
        if (gfp_mask & __GFP_NOFAIL)
                return 1;

         * Suspend converts GFP_KERNEL to __GFP_WAIT which can prevent reclaim
         * making forward progress without invoking OOM. Suspend also disables
         * storage devices so kswapd will not help. Bail if we are suspending.
        if (!did_some_progress && pm_suspended_storage())
                return 0;

         * In this implementation, order <= PAGE_ALLOC_COSTLY_ORDER
         * means __GFP_NOFAIL, but that may not be true in other
         * implementations.
        if (order <= PAGE_ALLOC_COSTLY_ORDER)
                return 1;

         * For order > PAGE_ALLOC_COSTLY_ORDER, if __GFP_REPEAT is
         * specified, then we retry until we no longer reclaim any pages
         * (above), or we've reclaimed an order of pages at least as
         * large as the allocation's order. In both cases, if the
         * allocation still fails, we stop retrying.
        if (gfp_mask & __GFP_REPEAT && pages_reclaimed < (1 << order))
                return 1;

        return 0;

할당 실패 시 재시도 가능한지 여부를 알아온다. 1=재시도 가능, 0=재시도 금지

  • if (gfp_mask & __GFP_NORETRY) return 0;
    • 재시도 금지 요청이 있으므로 0을 반환한다.
  • if (gfp_mask & __GFP_NOFAIL) return 1;
    • 실패를 허용치 않게 하라는 요청이 있으므로 재시도를 위해 1을 반환한다.
  • if (!did_some_progress && pm_suspended_storage()) return 0;
    • 회수된 페이지가 없으면서 할당에 storage 요청을 금지한 경우 0을 반환한다.
  • if (order <= PAGE_ALLOC_COSTLY_ORDER) return 1;
    • order 페이지 확보에 cost가 저렴한 편이면 재시도를 위해 1을 반환한다.
  • if (gfp_mask & __GFP_REPEAT && pages_reclaimed < (1 << order)) return 1;
    • 반복 요청이 있으면서 누적된 회수 페이지가 요청 order 보다 작아 더 확보를 하여야 하는 경우 재시도를 위해 1을 반환한다.




bool pm_suspended_storage(void)
        if ((gfp_allowed_mask & GFP_IOFS) == GFP_IOFS)
                return false;
        return true;

할당에 storage 요청을 금지했는지 여부를 알아온다.

  • 전역 gfp_allowed_mask에 GFP_IOFS 플래그가 설정된 경우 false를 반환한다.
  • 참고로 커널 초기화 중에는 GFP_IOFS를 사용하지 않는다.



답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.