Zoned Allocator -10- (Kswapd)

노드마다 kswapd가 동작하며 평상 시에는 잠들어 있다가 페이지 할당자가 요청 order 페이지 할당을 시도하다 free 페이지가 low 워터마크 기준을 충족하지 못하는 순간 kswapd를 깨운다. kswapd는 자신의 노드에 포함된 zone에 대해 페이지 회수 및 compaction을 진행하는데 모든 zone에 대해 밸런스하게 high 워터마크 기준을 충족하게 되면 스스로 sleep 한다.

 

kswapd 초기화

kswapd_init()

mm/vmscan.c

static int __init kswapd_init(void)
{
        int nid;

        swap_setup();
        for_each_node_state(nid, N_MEMORY)
                kswapd_run(nid);
        hotcpu_notifier(cpu_callback, 0);
        return 0;
}

module_init(kswapd_init)
  • 코드 라인 05에서 kswapd 실행 전에 준비한다.
  • 코드 라인 06~07에서 모든 메모리 노드에 대해 kswapd를 실행시킨다.
  • 코드 라인 08에서 cpu 상태가 변화될 때 마다 호출될 수 있도록 cpu_callback() 함수를 우선 순위 0으로 cpu notifier block에 등록한다.

 

swap_setup()

mm/swap.c

/*
 * Perform any setup for the swap system
 */                                             
void __init swap_setup(void)
{
        unsigned long megs = totalram_pages >> (20 - PAGE_SHIFT); 
#ifdef CONFIG_SWAP
        int i;

        for (i = 0; i < MAX_SWAPFILES; i++)
                spin_lock_init(&swapper_spaces[i].tree_lock);
#endif

        /* Use a smaller cluster for small-memory machines */
        if (megs < 16)                          
                page_cluster = 2;
        else
                page_cluster = 3;
        /* 
         * Right now other parts of the system means that we
         * _really_ don't want to cluster much more
         */
}

kswapd 실행 전에 준비한다.

  • 코드 라인 10-11에서 최대 swap 파일 수 만큼 루프를 돌며 spin lock을 초기화한다.
  • 코드 라인 15~18에서 전역 total 램이 16M 이하이면 page_cluster에 2를 대입하고 그렇지 않으면 3을 대입한다.

 

kswapd_run()

mm/vmscan.c

int kswapd_run(int nid)
{
        pg_data_t *pgdat = NODE_DATA(nid);
        int ret = 0;

        if (pgdat->kswapd)
                return 0;

        pgdat->kswapd = kthread_run(kswapd, pgdat, "kswapd%d", nid);
        if (IS_ERR(pgdat->kswapd)) {
                /* failure at boot is fatal */
                BUG_ON(system_state == SYSTEM_BOOTING);
                pr_err("Failed to start kswapd on node %d\n", nid);
                ret = PTR_ERR(pgdat->kswapd);
                pgdat->kswapd = NULL;
        }
        return ret;
}

 

kswapd 동작

kswapd-1a

kswapd()

mm/vmscan.c

/*
 * The background pageout daemon, started as a kernel thread
 * from the init process.
 *
 * This basically trickles out pages so that we have _some_
 * free memory available even if there is no other activity
 * that frees anything up. This is needed for things like routing
 * etc, where we otherwise might have all activity going on in
 * asynchronous contexts that cannot page things out.
 *
 * If there are applications that are active memory-allocators
 * (most normal use), this basically shouldn't matter.
 */
static int kswapd(void *p)
{
        unsigned long order, new_order;
        unsigned balanced_order;
        int classzone_idx, new_classzone_idx;
        int balanced_classzone_idx;
        pg_data_t *pgdat = (pg_data_t*)p;
        struct task_struct *tsk = current;

        struct reclaim_state reclaim_state = {
                .reclaimed_slab = 0,
        };
        const struct cpumask *cpumask = cpumask_of_node(pgdat->node_id);

        lockdep_set_current_reclaim_state(GFP_KERNEL);

        if (!cpumask_empty(cpumask))
                set_cpus_allowed_ptr(tsk, cpumask);
        current->reclaim_state = &reclaim_state;

        /*
         * Tell the memory management that we're a "memory allocator",
         * and that if we need more memory we should get access to it
         * regardless (see "__alloc_pages()"). "kswapd" should
         * never get caught in the normal page freeing logic.
         *
         * (Kswapd normally doesn't need memory anyway, but sometimes
         * you need a small amount of memory in order to be able to
         * page out something else, and this flag essentially protects
         * us from recursively trying to free more memory as we're
         * trying to free the first piece of memory in the first place).
         */
        tsk->flags |= PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD;
        set_freezable();

        order = new_order = 0;
        balanced_order = 0;
        classzone_idx = new_classzone_idx = pgdat->nr_zones - 1;
        balanced_classzone_idx = classzone_idx;

 

  • 코드 라인 26~31에서 요청 노드에서 동작하는 온라인 cpumask를 읽어와서 현재 태스크에 설정한다.
    • 요청 노드의 cpumask를 현재 task에 cpus_allowed 등을 설정한다.
  • 코드 라인 32에서 현재 태스크의 reclaim_state가 초기화된 전역 reclaim_state 구조체를 가리키게 한다.
  • 코드 라인 46에서 현재 태스크의 플래그에 PF_MEMALLOC, PF_SWAPWRITE 및 PF_KSWAPD를 설정한다.
    •  PF_MEMALLOC: 비상용 메모리까지 사용
    • PF_SWAPWRITE: anon 메모리에 대해 swap 기록 요청
    • PF_KSWAPD: kswapd task를 의미
  • 코드 라인 47에서 현재 태스크를 freeze할 수 있도록 PF_NOFREEZE 플래그를 제거한다.

 

        for ( ; ; ) {
                bool ret;

                /*
                 * If the last balance_pgdat was unsuccessful it's unlikely a
                 * new request of a similar or harder type will succeed soon
                 * so consider going to sleep on the basis we reclaimed at
                 */
                if (balanced_classzone_idx >= new_classzone_idx &&
                                        balanced_order == new_order) {
                        new_order = pgdat->kswapd_max_order;
                        new_classzone_idx = pgdat->classzone_idx;
                        pgdat->kswapd_max_order =  0;
                        pgdat->classzone_idx = pgdat->nr_zones - 1;
                }

                if (order < new_order || classzone_idx > new_classzone_idx) {
                        /*
                         * Don't sleep if someone wants a larger 'order'
                         * allocation or has tigher zone constraints
                         */
                        order = new_order;
                        classzone_idx = new_classzone_idx;
                } else {
                        kswapd_try_to_sleep(pgdat, balanced_order,
                                                balanced_classzone_idx);
                        order = pgdat->kswapd_max_order;
                        classzone_idx = pgdat->classzone_idx;
                        new_order = order;
                        new_classzone_idx = classzone_idx;
                        pgdat->kswapd_max_order = 0;
                        pgdat->classzone_idx = pgdat->nr_zones - 1;
                }

                ret = try_to_freeze();
                if (kthread_should_stop())
                        break;

                /*
                 * We can speed up thawing tasks if we don't call balance_pgdat
                 * after returning from the refrigerator
                 */
                if (!ret) {
                        trace_mm_vmscan_kswapd_wake(pgdat->node_id, order);
                        balanced_classzone_idx = classzone_idx;
                        balanced_order = balance_pgdat(pgdat, order,
                                                &balanced_classzone_idx);
                }
        }

        tsk->flags &= ~(PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD);
        current->reclaim_state = NULL;
        lockdep_clear_current_reclaim_state();

        return 0;
}

각 노드에서 동작되는 kswapd 스레드는 각 노드에서 동작하는 zone에 대해 free 페이지가 low 워터마크 이하로 내려가는 경우 백그라운드에서 페이지 회수를 진행하고 high 워터마크 이상이 되는 경우 페이지 회수를 멈춘다.

  • 코드 라인 09~15에서 마지막 balance_pgdat() 함수의 처리가 실패하는 경우 유사한 유형도 성공하지 않을 것 같으므로 sleep에 들어가도록 유도한다.
    • pgdat->kswapd_max_order와 pgdat->classzone_idx 값을 임시 변수에 받은 후 새로운 요청을 받기 위해 가장 기본 값으로 초기화 한다.
    • wakeup_kswapd() 함수 내에서 kswapd를 깨우기 전에 pgdat->kswapd_max_order와 pgdat->classzone_idx 값을 update 받는다.
  • 코드 라인 17~23에서 더 큰 새로운 order 또는 zone이 요청된 경우 이 값으로 시도하려고 대입한다.
  • 코드 라인 24에서 balanced order 및 zone 값으로 free 페이지를 high 워터마크 기준을 충족하면 sleep한다.
  • 코드 라인 25~31에서 새로이 요청받은 pgdat->kswapd_max_order 와 pgdat->classzone_idx 값을 받은 후 기본 값으로 초기화하다.
  • 코드 라인 33에서 현재 태스크 kswapd에 대해 freeze 요청이 있는 경우 freeze 시도한다.
  • 코드 라인 34~35에서 현재 태스크의 KTHREAD_SHOULD_STOP 플래그 비트가 설정된 경우 루프를 탈출하고 스레드 종료 처리한다.
  • 코드 라인 41~46에서 freeze 한 적이 없었던 경우 order 값으로 페이지 회수를 진행하고 그 후 balanced 값들을 업데이트한다.
  • 코드 라인 48~52에서 kswapd 스레드의 종료 처리를 진행한다.

 

kswapd_try_to_sleep()

mm/vmscan.c

static void kswapd_try_to_sleep(pg_data_t *pgdat, int order, int classzone_idx)
{
        long remaining = 0;
        DEFINE_WAIT(wait);

        if (freezing(current) || kthread_should_stop())
                return;

        prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE);

        /* Try to sleep for a short interval */
        if (prepare_kswapd_sleep(pgdat, order, remaining, classzone_idx)) {
                remaining = schedule_timeout(HZ/10);
                finish_wait(&pgdat->kswapd_wait, &wait);
                prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE);
        }

        /*
         * After a short sleep, check if it was a premature sleep. If not, then
         * go fully to sleep until explicitly woken up.
         */
        if (prepare_kswapd_sleep(pgdat, order, remaining, classzone_idx)) {
                trace_mm_vmscan_kswapd_sleep(pgdat->node_id);

                /*
                 * vmstat counters are not perfectly accurate and the estimated
                 * value for counters such as NR_FREE_PAGES can deviate from the
                 * true value by nr_online_cpus * threshold. To avoid the zone
                 * watermarks being breached while under pressure, we reduce the
                 * per-cpu vmstat threshold while kswapd is awake and restore
                 * them before going back to sleep.
                 */
                set_pgdat_percpu_threshold(pgdat, calculate_normal_threshold);

                /*
                 * Compaction records what page blocks it recently failed to
                 * isolate pages from and skips them in the future scanning.
                 * When kswapd is going to sleep, it is reasonable to assume
                 * that pages and compaction may succeed so reset the cache.
                 */
                reset_isolation_suitable(pgdat);

                if (!kthread_should_stop())
                        schedule();

                set_pgdat_percpu_threshold(pgdat, calculate_pressure_threshold);
        } else {
                if (remaining)
                        count_vm_event(KSWAPD_LOW_WMARK_HIT_QUICKLY);
                else
                        count_vm_event(KSWAPD_HIGH_WMARK_HIT_QUICKLY);
        }
        finish_wait(&pgdat->kswapd_wait, &wait);
}

노드에 대해 요청 order 및 zone 까지 free 페이지가 high 워터마크 기준으로 밸런스하게 할당할 수 있는 상태라면 sleep 한다.

  • 코드 섹션 06~07에서 freeze 요청이 있는 경우 함수를 빠져나간다.
  • 코드 섹션 09에서 현재 태스크를 kswapd_wait에 추가한다.
    • 이후 sleep한 상태가 된 후 페이지 할당 함수에서 free 페이지가 low 워터마크 기준 이하로 부족해지면 wakeup을 하여 sleep함수에서 깨어나게 된다.
  • 코드 섹션 12~16에서 요청 zone 까지 그리고 요청 order에 대해 free 페이지가 밸런스된 high 워터마크 기준을 충족하면 0.1초를 sleep한다. 그 후 다시 sleep할 준비를 한다.
  • 코드 섹션 22에서 여전히 요청 zone 까지 그리고 요청 order에 대해 free 페이지가 밸런스된 high 워터마크 기준을 충족하면
  • 코드 섹션 33에서 NR_FREE_PAGES 등의 vmstat을 정밀하게 계산해야 할 때가 있으므로 이러한 경우에 사용하기 위해 각 zone에 대해 vmstat에 대한 zone별 normal한 스레졸드 기준을 cpu별로 zone->pageset->stat_threshold 에 저장한다.
  • 코드 섹션 41에서 각 zone의 full compaction이 최근에 끝난 경우에 한해 해당 zone에 대해 나중에 compaction을 처음부터 할 수 있도록 리셋한다.
    • migrate 및 free 스캐너들의 시작 위치를 초기화하고 각 페이지 블럭의 skip 비트를 0으로 클리어한다.
  • 코드 섹션 43~44에서 스레드 종료 요청이 아닌 경우 sleep한다.
  • 코드 섹션 46에서 vmstat을 위해 이번에는 pressure한 스레졸드 값을  대입한다.
  • 코드 섹션 47~52에서 밸런스드 high 워터마크 기준을 충족하지 못한 상황이다. 이 때 remain이 0이 아닌 경우 0.1초간 잠시 sleep 하는 와중에 메모리 부족을 이유로 현재 스레드인 kswapd가 깨어난 경우이므로 KSWAPD_LOW_WMARK_HIT_QUICKLY 카운터를 증가시키고 그렇지 않은 경우는 KSWAPD_HIGH_WMARK_HIT_QUICKLY 카운터를 증가시킨다. 이 두 가지 stat 카운터는 둘 다 메모리가 확보되어 잠들자마자 깨어난 경우이다.
  • 코드 섹션 53에서 kswapd_wait 에서 현재 태스크를 제거한다.

 

kswapd_try_to_sleep-1

 

밸런스 체크

pgdat_balanced()

mm/vmscan.c

/*
 * pgdat_balanced() is used when checking if a node is balanced.
 *
 * For order-0, all zones must be balanced!
 *
 * For high-order allocations only zones that meet watermarks and are in a
 * zone allowed by the callers classzone_idx are added to balanced_pages. The
 * total of balanced pages must be at least 25% of the zones allowed by
 * classzone_idx for the node to be considered balanced. Forcing all zones to
 * be balanced for high orders can cause excessive reclaim when there are
 * imbalanced zones.
 * The choice of 25% is due to
 *   o a 16M DMA zone that is balanced will not balance a zone on any
 *     reasonable sized machine
 *   o On all other machines, the top zone must be at least a reasonable
 *     percentage of the middle zones. For example, on 32-bit x86, highmem
 *     would need to be at least 256M for it to be balance a whole node.
 *     Similarly, on x86-64 the Normal zone would need to be at least 1G
 *     to balance a node on its own. These seemed like reasonable ratios.
 */
static bool pgdat_balanced(pg_data_t *pgdat, int order, int classzone_idx)
{
        unsigned long managed_pages = 0;
        unsigned long balanced_pages = 0;
        int i;

        /* Check the watermark levels */
        for (i = 0; i <= classzone_idx; i++) {
                struct zone *zone = pgdat->node_zones + i;

                if (!populated_zone(zone))
                        continue;

                managed_pages += zone->managed_pages;

                /*
                 * A special case here:
                 *
                 * balance_pgdat() skips over all_unreclaimable after
                 * DEF_PRIORITY. Effectively, it considers them balanced so
                 * they must be considered balanced here as well!
                 */
                if (!zone_reclaimable(zone)) {
                        balanced_pages += zone->managed_pages;
                        continue;
                }

                if (zone_balanced(zone, order, 0, i))
                        balanced_pages += zone->managed_pages;
                else if (!order)
                        return false;
        }

        if (order)
                return balanced_pages >= (managed_pages >> 2);
        else
                return true;
}

해당 노드에서 classzone_idx까지 요청 order로 free 페이지가 high 워터마크 기준에 맞게 밸런스하게 할당 가능한지 여부를 체크한다.

  • 0-order 요청의 경우 모든 zone에 대해 균형을 맞춰야 한다.
  • high order의 경우 할당 가능한 zone의 managed pages가 요청한 zone 까지의 managed pages의 25% 이상이어야 한다.

 

  • 코드 라인 28~32에서 하위 zone부터 요청 zone까지 루프를 돌며 요청 노드의 zone이 활성화된 zone이 아니면 skip 한다.
  • 코드 라인 34에서 각 zone의 managed_pages를 더한다.
  • 코드 라인 43~46에서 스페셜 케이스를 처리하기 위해 해당 zone이 페이지 회수가 불가능한 상태이면 balanced_pages에 zone->managed_pages를 더하고  skip 한다.
  • 코드 라인 48~51에서 요청 order로 zone이 밸런스한 경우 balanced_pages에 zone->managed_pages를 더한다. 그렇지 않으면서 요청 order가 0인 경우 false를 반환한다.
  • 코드 라인 54~57에서 order가 0보다 큰 경우 balanced_pages가 managed_pages의 25% 미만인 경우 false를 반환하고 그 이외의 경우 true를 반환한다.

 

pgdat_balanced-1

 

zone_reclaimable()

mm/vmscan.c

bool zone_reclaimable(struct zone *zone)
{
        return zone_page_state(zone, NR_PAGES_SCANNED) <
                zone_reclaimable_pages(zone) * 6;
}

요청 zone에서 회수 가능 여부를 반환한다. (true=회수 가능)

  • 코드 03~04에서 스캔한 페이지 수가 회수 가능한 페이지의 6배보다 작은 경우 더 회수할 수 있다고 판단하여 true를 반환한다.
    • free되는 페이지가 없고 메모리 부족 시 페이지 회수를 NR_PAGES_SCANNED 카운터는 계속 증가한다.

 

zone_reclaimable_pages()

mm/vmscan.c

static unsigned long zone_reclaimable_pages(struct zone *zone)
{
        int nr;

        nr = zone_page_state(zone, NR_ACTIVE_FILE) +
             zone_page_state(zone, NR_INACTIVE_FILE);

        if (get_nr_swap_pages() > 0)
                nr += zone_page_state(zone, NR_ACTIVE_ANON) +
                      zone_page_state(zone, NR_INACTIVE_ANON);

        return nr;
}

회수 가능한 페이지 수를 산출한다.

  • 코드 라인 05~06에서 lru active file 페이지 수와 lru inactive file 페이지 수를 더한다.
  • 코드 라인 08~10에서 swap 페이지가 있는 경우 lru active anon 페이지 수와 lru inactive anon 페이지 수도 더한다.

 

zone_balanced()

mm/vmscan.c

static bool zone_balanced(struct zone *zone, int order,
                          unsigned long balance_gap, int classzone_idx)
{
        if (!zone_watermark_ok_safe(zone, order, high_wmark_pages(zone) +
                                    balance_gap, classzone_idx, 0))
                return false;

        if (IS_ENABLED(CONFIG_COMPACTION) && order && compaction_suitable(zone,
                                order, 0, classzone_idx) == COMPACT_SKIPPED)
                return false;

        return true;
}

요청 zone 및 order로 high 워터마크 + 밸런스 gap 기준에 적합한지 정밀하게 체크한다. 단 high order 요청인 경우 low 워터마크 기준에 적합하지 않을 것 같으면 실패를 반환한다.

  • 코드 라인 04~06에서 요청 zone 및 order로 high 워터마크 + 밸런스 gap 기준에 적합하지 않는 경우 false를 반환한다.
  • 코드 라인 08~10에서 요청 zone에 대해 compaction하더라도 high order가 low 워터마크 기준에 적합하지 않을 것 같으면 false를 반환한다.

 

밸런스될 때까지 페이지 회수

balance_pgdat()

mm/vmscan.c

/*
 * For kswapd, balance_pgdat() will work across all this node's zones until
 * they are all at high_wmark_pages(zone).
 *
 * Returns the final order kswapd was reclaiming at
 *
 * There is special handling here for zones which are full of pinned pages.
 * This can happen if the pages are all mlocked, or if they are all used by
 * device drivers (say, ZONE_DMA).  Or if they are all in use by hugetlb.
 * What we do is to detect the case where all pages in the zone have been
 * scanned twice and there has been zero successful reclaim.  Mark the zone as
 * dead and from now on, only perform a short scan.  Basically we're polling
 * the zone for when the problem goes away.
 *
 * kswapd scans the zones in the highmem->normal->dma direction.  It skips
 * zones which have free_pages > high_wmark_pages(zone), but once a zone is
 * found to have free_pages <= high_wmark_pages(zone), we scan that zone and the
 * lower zones regardless of the number of free pages in the lower zones. This
 * interoperates with the page allocator fallback scheme to ensure that aging
 * of pages is balanced across the zones.
 */
static unsigned long balance_pgdat(pg_data_t *pgdat, int order,
                                                        int *classzone_idx)
{
        int i;
        int end_zone = 0;       /* Inclusive.  0 = ZONE_DMA */
        unsigned long nr_soft_reclaimed;
        unsigned long nr_soft_scanned;
        struct scan_control sc = {
                .gfp_mask = GFP_KERNEL,
                .order = order,
                .priority = DEF_PRIORITY,
                .may_writepage = !laptop_mode,
                .may_unmap = 1,
                .may_swap = 1,
        };
        count_vm_event(PAGEOUTRUN);

        do {
                unsigned long nr_attempted = 0;
                bool raise_priority = true;
                bool pgdat_needs_compaction = (order > 0);

                sc.nr_reclaimed = 0;

                /*
                 * Scan in the highmem->dma direction for the highest
                 * zone which needs scanning
                 */
                for (i = pgdat->nr_zones - 1; i >= 0; i--) {
                        struct zone *zone = pgdat->node_zones + i;

                        if (!populated_zone(zone))
                                continue;

                        if (sc.priority != DEF_PRIORITY &&
                            !zone_reclaimable(zone))
                                continue;

                        /*
                         * Do some background aging of the anon list, to give
                         * pages a chance to be referenced before reclaiming.
                         */
                        age_active_anon(zone, &sc);

                        /*
                         * If the number of buffer_heads in the machine
                         * exceeds the maximum allowed level and this node
                         * has a highmem zone, force kswapd to reclaim from
                         * it to relieve lowmem pressure.
                         */
                        if (buffer_heads_over_limit && is_highmem_idx(i)) {
                                end_zone = i;
                                break;
                        }

                        if (!zone_balanced(zone, order, 0, 0)) {
                                end_zone = i;
                                break;
                        } else {
                                /*
                                 * If balanced, clear the dirty and congested
                                 * flags
                                 */
                                clear_bit(ZONE_CONGESTED, &zone->flags);
                                clear_bit(ZONE_DIRTY, &zone->flags);
                        }
                }

                if (i < 0)
                        goto out;

우선 순위를 12부터 1까지 높여가며 페이지 회수 및 compaction을 진행하여 free 페이지가 요청 order 및 zone까지 밸런스하게 high 워터마크 기준을  충족할 때까지 진행한다.

 

  • 코드 라인 29~36에서 준비한 scan_control 구조체에 unmap 및 swap 할 수 있도록 enable한다. 또한 laptop 모드가 아닌 경우 writepage 기능도 enable한다.
  • 코드 라인 38~44에서 이 루틴에서 반복하는데 시도 횟수를 0, 우선 순위 상승을 가능상태, high order 인 경우 compaction 가능 및 회수 카운터를 0으로 초기화한다.
  • 코드 라인 50~53에서 가장 높은 zone 부터 낮은 zone까지 루프를 돌되 이 노드에서 활성화 되지 않은 zone은 skip 한다.
  • 코드 라인 56~58에서 우선 순위를 높여 다시 시도하는데 zone 이 회수 불가능 상태인 경우 skip 한다.
  • 코드 라인 64에서 swap이 활성화된 경우 inactive anon이 active anon보다 작을 경우 active 리스트에 대해 shrink를 수행하여 active와 inactive간의 밸런스를 다시 잡아준다.
  • 코드 라인 72~75에서 현재 처리하는 zone이 highmem이면서 시스템의 buffer_heads 수가 최대 허용 레벨을 초과하는 경우 kswapd가 강제로 lowmem 압력을 해제하도록 강제한다.
  • 코드라인 77~79에서 현재 처리하는 zone까지 요청 order로 high 워터마크 기준에 밸런스하지 않은 상태이면 이 zone을 end_zone으로 대입하고 루프를 빠져나간다.
  • 코드 라인 80~88에서 현재 zone의 플래그에 ZONE_CONGESTED와 ZONE_DIRTY를 clear하고 다음 zone 루프를 돈다.
  • 코드 라인 90~91에서 페이지를 회수할 zone을 찾지못한 경우 함수를 빠져나간다.

 

.               for (i = 0; i <= end_zone; i++) {
                        struct zone *zone = pgdat->node_zones + i;

                        if (!populated_zone(zone))
                                continue;

                        /*
                         * If any zone is currently balanced then kswapd will
                         * not call compaction as it is expected that the
                         * necessary pages are already available.
                         */
                        if (pgdat_needs_compaction &&
                                        zone_watermark_ok(zone, order,
                                                low_wmark_pages(zone),
                                                *classzone_idx, 0))
                                pgdat_needs_compaction = false;
                }

                /*
                 * If we're getting trouble reclaiming, start doing writepage
                 * even in laptop mode.
                 */
                if (sc.priority < DEF_PRIORITY - 2)
                        sc.may_writepage = 1;

                /*
                 * Now scan the zone in the dma->highmem direction, stopping
                 * at the last zone which needs scanning.
                 *
                 * We do this because the page allocator works in the opposite
                 * direction.  This prevents the page allocator from allocating
                 * pages behind kswapd's direction of progress, which would
                 * cause too much scanning of the lower zones.
                 */
                for (i = 0; i <= end_zone; i++) {
                        struct zone *zone = pgdat->node_zones + i;

                        if (!populated_zone(zone))
                                continue;

                        if (sc.priority != DEF_PRIORITY &&
                            !zone_reclaimable(zone))
                                continue;

                        sc.nr_scanned = 0;

                        nr_soft_scanned = 0;
                        /*
                         * Call soft limit reclaim before calling shrink_zone.
                         */
                        nr_soft_reclaimed = mem_cgroup_soft_limit_reclaim(zone,
                                                        order, sc.gfp_mask,
                                                        &nr_soft_scanned);
                        sc.nr_reclaimed += nr_soft_reclaimed;

                        /*
                         * There should be no need to raise the scanning
                         * priority if enough pages are already being scanned
                         * that that high watermark would be met at 100%
                         * efficiency.
                         */
                        if (kswapd_shrink_zone(zone, end_zone,
                                               &sc, &nr_attempted))
                                raise_priority = false;
                }
  • 코드 라인 01~05에서 가장 낮은 zone 부터 위 루틴에서 결정한 end_zone 까지 루프를 돌되 노드에 활성화되지 않은 zone은 skip 한다.
  • 코드 라인 12~16에서 하나의 zone이라도 free 페이지를 현재 zone에서 요청 order로 low 워터마크 기준을 만족하는 경우 compaction 하지 못하게 한다.
  • 코드 라인 23~24에서 페이지 회수 문제가 지속되는 경우 writepage 기능을 enable한다.
  • 코드 라인 35~39에서 가장 낮은 zone 부터 end_zone 까지 루프를 돌되 노드에 활성화되지 않은 zone은 skip 한다.
  • 코드 라인 41~43에서 현재 zone에서 페이지 회수 문제가 지속되는 경우 skip 한다.
  • 코드 라인 45~47에서 스캔 수와 soft 스캔 수를 0으로 초기화한다.
  • 코드 라인 51~54에서 memcg 제한된 soft 페이지 회수를 하고 그 갯수를 sc.nr_reclaimed에 추가한다.
  • 코드라인 62~64에서 현재 zone에 대해 free 페이지가 high 워터마크 기준을 충족할 만큼 shrink 한다. shrink가 성공한 경우 순위를 증가시키지 않도록 한다.

 

                /*
                 * If the low watermark is met there is no need for processes
                 * to be throttled on pfmemalloc_wait as they should not be
                 * able to safely make forward progress. Wake them
                 */
                if (waitqueue_active(&pgdat->pfmemalloc_wait) &&
                                pfmemalloc_watermark_ok(pgdat))
                        wake_up_all(&pgdat->pfmemalloc_wait);

                /*
                 * Fragmentation may mean that the system cannot be rebalanced
                 * for high-order allocations in all zones. If twice the
                 * allocation size has been reclaimed and the zones are still
                 * not balanced then recheck the watermarks at order-0 to
                 * prevent kswapd reclaiming excessively. Assume that a
                 * process requested a high-order can direct reclaim/compact.
                 */
                if (order && sc.nr_reclaimed >= 2UL << order)
                        order = sc.order = 0;

                /* Check if kswapd should be suspending */
                if (try_to_freeze() || kthread_should_stop())
                        break;

                /*
                 * Compact if necessary and kswapd is reclaiming at least the
                 * high watermark number of pages as requsted
                 */
                if (pgdat_needs_compaction && sc.nr_reclaimed > nr_attempted)
                        compact_pgdat(pgdat, order);

                /*
                 * Raise priority if scanning rate is too low or there was no
                 * progress in reclaiming pages
                 */
                if (raise_priority || !sc.nr_reclaimed)
                        sc.priority--;
        } while (sc.priority >= 1 &&
                 !pgdat_balanced(pgdat, order, *classzone_idx));

out:
        /*
         * Return the order we were reclaiming at so prepare_kswapd_sleep()
         * makes a decision on the order we were last reclaiming at. However,
         * if another caller entered the allocator slow path while kswapd
         * was awake, order will remain at the higher level
         */
        *classzone_idx = end_zone;
        return order;
}
  • 코드 라인 06~08에서 pfmemalloc_wait에서 대기하고 있는 태스크들이 있으면서 pfmemalloc 워터마크 기준을 충족하면 그 대기 태스크들을 모두 깨운다.
    • pfmemalloc_wait(): 가장 아래 zone 부터 normal zone까지의 free 페이지 합이 min 워터마크를 더한 페이지의 절반보다 큰 경우 true.
  • 코드 라인 18~19에서 high order의 2배이상 페이지를 회수한 경우 너무 많이 회수되는 것을 막기 위해 order를 0으로 변경한다.
  • 코드 라인 22~23에서 freeze 하였다가 깨어났었던 경우 또는 kswapd 스레드 정지 요청이 있는 경우 루프를 빠져나간다.
  • 코드 라인 29~30에서 compaction이 가능한 상황에서 회수 페이지 수가 시도 횟수 보다 더 큰 경우 compaction을 진행한다.
  • 코드 라인 36~37에서 우선 순위 상승이 필요하거나 회수 페이지가 하나도 없는 경우 우선 순위를 상승시킨다.
  • 코드 라인 38~39에서 우선 순위가 1보다 큰 경우이면서 요청 order와 zone까지 free 페이지가 밸런스하지 못하면 계속 루프를 반복하여 페이지 회수를 위해 노력한다.

 

아래 그림은 task A에서 direct 페이지 회수를 진행 중에 pfmemalloc 워터마크 기준 이하로 떨어진 경우 kswapd에 의해 페이지 회수가 될 때까지스로틀링 즉, direct 페이지 회수를 잠시 쉬게 하여 cpu 부하를 줄인다.

balance_pgdat-1

 

Kswapd 깨우기

wake_all_kswapds()

mm/page_alloc.c

static void wake_all_kswapds(unsigned int order, const struct alloc_context *ac)
{
        struct zoneref *z;
        struct zone *zone;

        for_each_zone_zonelist_nodemask(zone, z, ac->zonelist,
                                                ac->high_zoneidx, ac->nodemask)
                wakeup_kswapd(zone, order, zone_idx(ac->preferred_zone));
}

alloc context가 가리키는 zonelist 중 관계 노드의 kswpad를 깨운다.

 

wakeup_kswapd()

mm/vmscan.c

/*
 * A zone is low on free memory, so wake its kswapd task to service it.
 */
void wakeup_kswapd(struct zone *zone, int order, enum zone_type classzone_idx)
{
        pg_data_t *pgdat;

        if (!populated_zone(zone))
                return;

        if (!cpuset_zone_allowed(zone, GFP_KERNEL | __GFP_HARDWALL))
                return;
        pgdat = zone->zone_pgdat;
        if (pgdat->kswapd_max_order < order) {
                pgdat->kswapd_max_order = order;
                pgdat->classzone_idx = min(pgdat->classzone_idx, classzone_idx);
        }
        if (!waitqueue_active(&pgdat->kswapd_wait))
                return;
        if (zone_balanced(zone, order, 0, 0))
                return;

        trace_mm_vmscan_wakeup_kswapd(pgdat->node_id, zone_idx(zone), order);
        wake_up_interruptible(&pgdat->kswapd_wait);
}

지정된 zone에서 order 페이지를 할당하려다 메모리가 부족해지면 kswapd 태스크를 깨운다.

  • if (!populated_zone(zone)) return;
    • 활성화된 zone이 아닌경우 처리할 페이지가 없으므로 함수를 빠져나간다.
  • if (!cpuset_zone_allowed(zone, GFP_KERNEL | __GFP_HARDWALL)) return;
    • 현재 zone 노드가 아니면서 태스크가 허락한 노드가 아닌 경우 처리를 포기하고 빠져나간다.
  • if (pgdat->kswapd_max_order < order) { pgdat->kswapd_max_order = order; pgdat->classzone_idx = min(pgdat->classzone_idx, classzone_idx); }
    • 노드의 kswapd_max_order를 초과하는 요청인 경우 그 값을 갱신하고 노드의 classzone_idx를 최소값으로 갱신한다.
  • if (!waitqueue_active(&pgdat->kswapd_wait)) return;
    • kswapd_wait의 waitqueue가 비어 있으면 처리를 하지 않고 빠져나간다.
  • if (zone_balanced(zone, order, 0, 0)) return;
    • zone의 free page가 high 워터마크를 초과하였거나 compaction의 필요성이 없는 경우 페이지 회수가 필요한 zone이 아니므로 처리를 하지 않고 빠져나간다.
  • wake_up_interruptible(&pgdat->kswapd_wait);
    • kswapd 태스크를 깨운다.

 

current_is_kswapd()

include/linux/swap.h

static inline int current_is_kswapd(void)
{
        return current->flags & PF_KSWAPD;
}

현재 태스크가 kswapd 인 경우 true를 반환한다.

 

기타

swapper_spaces[] 배열

mm/swap_state.c

struct address_space swapper_spaces[MAX_SWAPFILES] = {
        [0 ... MAX_SWAPFILES - 1] = {
                .page_tree      = RADIX_TREE_INIT(GFP_ATOMIC|__GFP_NOWARN),
                .i_mmap_writable = ATOMIC_INIT(0),
                .a_ops          = &swap_aops,
        }
};

 

swap_aops

mm/swap_state.c

/*                                      
 * swapper_space is a fiction, retained to simplify the path through
 * vmscan's shrink_page_list.
 */
static const struct address_space_operations swap_aops = {
        .writepage      = swap_writepage,
        .set_page_dirty = swap_set_page_dirty,
#ifdef CONFIG_MIGRATION
        .migratepage    = migrate_page,
#endif 
};

 

address_space_operations 구조체

include/linux/fs.h

struct address_space_operations {
        int (*writepage)(struct page *page, struct writeback_control *wbc);
        int (*readpage)(struct file *, struct page *); 
        
        /* Write back some dirty pages from this mapping. */
        int (*writepages)(struct address_space *, struct writeback_control *);
        
        /* Set a page dirty.  Return true if this dirtied it */
        int (*set_page_dirty)(struct page *page);

        int (*readpages)(struct file *filp, struct address_space *mapping,
                        struct list_head *pages, unsigned nr_pages);
                
        int (*write_begin)(struct file *, struct address_space *mapping,
                                loff_t pos, unsigned len, unsigned flags,
                                struct page **pagep, void **fsdata);
        int (*write_end)(struct file *, struct address_space *mapping,
                                loff_t pos, unsigned len, unsigned copied,
                                struct page *page, void *fsdata);

        /* Unfortunately this kludge is needed for FIBMAP. Don't use it */
        sector_t (*bmap)(struct address_space *, sector_t);
        void (*invalidatepage) (struct page *, unsigned int, unsigned int);
        int (*releasepage) (struct page *, gfp_t);
        void (*freepage)(struct page *);
        ssize_t (*direct_IO)(int, struct kiocb *, struct iov_iter *iter, loff_t offset);
        /*
         * migrate the contents of a page to the specified target. If
         * migrate_mode is MIGRATE_ASYNC, it must not block.
         */
        int (*migratepage) (struct address_space *,
                        struct page *, struct page *, enum migrate_mode);
        int (*launder_page) (struct page *);
        int (*is_partially_uptodate) (struct page *, unsigned long,
                                        unsigned long);
        void (*is_dirty_writeback) (struct page *, bool *, bool *);
        int (*error_remove_page)(struct address_space *, struct page *);

        /* swapfile support */
        int (*swap_activate)(struct swap_info_struct *sis, struct file *file,
                                sector_t *span);
        void (*swap_deactivate)(struct file *file);
};

 

참고

답글 남기기

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