Zoned Allocator -10- (Kswapd)

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


kswapd 초기화



static int __init kswapd_init(void)
        int nid;

        for_each_node_state(nid, N_MEMORY)
        hotcpu_notifier(cpu_callback, 0);
        return 0;

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




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

        for (i = 0; i < MAX_SWAPFILES; i++)

        /* Use a smaller cluster for small-memory machines */
        if (megs < 16)                          
                page_cluster = 2;
                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을 대입한다.




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 동작




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


        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;

        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,
                        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())

                 * 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,

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

        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 스레드의 종료 처리를 진행한다.




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

        if (freezing(current) || kthread_should_stop())

        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)) {

                 * 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.

                if (!kthread_should_stop())

                set_pgdat_percpu_threshold(pgdat, calculate_pressure_threshold);
        } else {
                if (remaining)
        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 에서 현재 태스크를 제거한다.




밸런스 체크



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

                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;

                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);
                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를 반환한다.






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 카운터는 계속 증가한다.




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 페이지 수도 더한다.




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를 반환한다.


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



 * 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,

        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))

                        if (sc.priority != DEF_PRIORITY &&

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

                        if (!zone_balanced(zone, order, 0, 0)) {
                                end_zone = i;
                        } 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))

                         * 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,
                                                *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))

                        if (sc.priority != DEF_PRIORITY &&

                        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,
                        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) &&

                 * 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())

                 * 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)
        } while (sc.priority >= 1 &&
                 !pgdat_balanced(pgdat, order, *classzone_idx));

         * 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 부하를 줄인다.



Kswapd 깨우기



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를 깨운다.




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

        if (!cpuset_zone_allowed(zone, GFP_KERNEL | __GFP_HARDWALL))
        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))
        if (zone_balanced(zone, order, 0, 0))

        trace_mm_vmscan_wakeup_kswapd(pgdat->node_id, zone_idx(zone), order);

지정된 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 태스크를 깨운다.




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

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



swapper_spaces[] 배열


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,




 * 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,
        .migratepage    = migrate_page,


address_space_operations 구조체


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



8 thoughts to “Zoned Allocator -10- (Kswapd)”

      1. 큰 도움이 되었습니다. 한글로 이렇게 잘 설명된 사이트는 오랜만이네요.
        염치불구하고 한가지 질문드립니다.
        kzalloc(alloc_size, GFP_KERNEL);의 결과가 null이 반환되는 경우가 발생하는데, zoneinfo를 보면 아래와 같습니다. (모바일기기이고 RAM 2G입니다)
        Node 0, zone DMA
        pages free 6985
        min 1342
        low 4204
        high 4540
        cat proc/sys/vm/min_free_kbytes : 읽은값 5368

        1. 아래와 같이 변경하면 malloc에러가 개선되는데 이렇게 수동으로 늘려주는게 올바른지요.
        echo 107216 > /proc/sys/vm/min_free_kbytes
        2. 맞지않는다면 malloc메모리 여유를 주려면 무슨 방법이 좋을까요.

  1. 반갑습니다.

    kzalloc() 함수에서 GFP_KERNEL 플래그를 사용한 경우 커널 메모리 할당을 위해 다음과 같이 동작합니다.
    alloc_size에 따라
    – 8K 이하이면 2의 제곱승 단위로 kmalloc용 슬랩(slub) 메모리를 할당하고,
    – 8K를 초과하는 경우 곧바로 버디 시스템에서 페이지를 할당합니다.

    현재 시스템 메모리 상태를 보니 남은 메모리 여분이 약 28M이고, 약 20M 미만으로 내려가면 kswapd를 통해 백그라운드에서 메모리를 확보하라고 한 상태입니다.
    위의 상태라면 보통 alloc_size가 약간 큰 페이지를 할당하려고 한 것 같습니다.
    28M의 여분이 있다 하더라도 버디시스템에서 연속된 큰 페이지들이 모자란 상태인 듯 합니다.
    메모리를 지속적으로 할당하고 다시 풀어주고를 반복하는 경우 황일섭님이 설정하신 것 같이 워터마크 기준을 높이면 메모리가 shortage 나기 전에 compaction 및 reclaim 등을 통해 메모리가 다시 확보되니 대부분 해결이 됩니다. (물론 지속적으로 할당을 시도하는 상황에서 할당 해제되는 메모리가 계속 모자라지는 버그 또는 설계가 잘못된 demon이 없다는 가정입니다)

    그런데 휴대폰이라면 사용자가 메모리가 큰 게임 등을 구동하는 경우 메모리 관리 앱을 사용하여 정리하곤 하는데 그 와는 다른 상황인가 보네요?
    참고로 휴대폰이 아니고 1년 365일 계속 동작해야 하는 임베디드 시스템인 경우에는 메모리가 줄어들지 않도록 충분히 잘 설계하는 것으로 회피합니다.


  2. 좋은 글 감사합니다.
    근데 하나 궁금한 게 있습니다.
    메모리를 회수하고 나서 storage device로 swap partition(예를 들어 /swap)으로 보내줘야 할 것 같은데
    이와 관련된 동작이 언제 어디서 일어나는지 알 수 있을까요??

    1. swap_writepage()를 통해 swap partition으로 쓰여지는 것으로 이해되는데요, 문제는 이 함수에 printk()를 심어놔도 trigger되는 경우가 없다는 것입니다.
      제 컴퓨터에 swap partition이 따로 잡혀있지는 않고 swapfile만 존재하고 커널 버전은 5.0.5입니다.
      fio benchmark를 통해 약 20gb를 쓰는데도(제 DRAM용량은 16GB입니다) trigger되는 경우가 없습니다. 혹시 조언을 받을 수 있을까요?
      혹시, mmap()등을 통해 anonymous로 매핑 후에 실험을 해야 할까요?

      1. 안녕하세요?

        swap을 동작시키려면 유저 레벨에서 할당하는 anon 메모리에 기록을 해야 합니다.
        그냥 유저 레벨 application을 작성할 때 반복 루프 내에서 malloc()을 사용하시고 memset()으로 아무 값이나 기록해보시면 알 수 있을 것입니다.


댓글 남기기

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