Zoned Allocator -11- (Direct Reclaim)

<kernel v5.0>

Zoned Allocator -11- (Direct Reclaim)

Reclaim 판단

should_continue_reclaim()

mm/vmscan.c

1/*
2 * Reclaim/compaction is used for high-order allocation requests. It reclaims
3 * order-0 pages before compacting the zone. should_continue_reclaim() returns
4 * true if more pages should be reclaimed such that when the page allocator
5 * calls try_to_compact_zone() that it will have enough free pages to succeed.
6 * It will give up earlier than that if there is difficulty reclaiming pages.
7 */
01static inline bool should_continue_reclaim(struct pglist_data *pgdat,
02                                        unsigned long nr_reclaimed,
03                                        unsigned long nr_scanned,
04                                        struct scan_control *sc)
05{
06        unsigned long pages_for_compaction;
07        unsigned long inactive_lru_pages;
08        int z;
09 
10        /* If not in reclaim/compaction mode, stop */
11        if (!in_reclaim_compaction(sc))
12                return false;
13 
14        /* Consider stopping depending on scan and reclaim activity */
15        if (sc->gfp_mask & __GFP_RETRY_MAYFAIL) {
16                /*
17                 * For __GFP_RETRY_MAYFAIL allocations, stop reclaiming if the
18                 * full LRU list has been scanned and we are still failing
19                 * to reclaim pages. This full LRU scan is potentially
20                 * expensive but a __GFP_RETRY_MAYFAIL caller really wants to succeed
21                 */
22                if (!nr_reclaimed && !nr_scanned)
23                        return false;
24        } else {
25                /*
26                 * For non-__GFP_RETRY_MAYFAIL allocations which can presumably
27                 * fail without consequence, stop if we failed to reclaim
28                 * any pages from the last SWAP_CLUSTER_MAX number of
29                 * pages that were scanned. This will return to the
30                 * caller faster at the risk reclaim/compaction and
31                 * the resulting allocation attempt fails
32                 */
33                if (!nr_reclaimed)
34                        return false;
35        }
36 
37        /*
38         * If we have not reclaimed enough pages for compaction and the
39         * inactive lists are large enough, continue reclaiming
40         */
41        pages_for_compaction = compact_gap(sc->order);
42        inactive_lru_pages = node_page_state(pgdat, NR_INACTIVE_FILE);
43        if (get_nr_swap_pages() > 0)
44                inactive_lru_pages += node_page_state(pgdat, NR_INACTIVE_ANON);
45        if (sc->nr_reclaimed < pages_for_compaction &&
46                        inactive_lru_pages > pages_for_compaction)
47                return true;
48 
49        /* If compaction would go ahead or the allocation would succeed, stop */
50        for (z = 0; z <= sc->reclaim_idx; z++) {
51                struct zone *zone = &pgdat->node_zones[z];
52                if (!managed_zone(zone))
53                        continue;
54 
55                switch (compaction_suitable(zone, sc->order, 0, sc->reclaim_idx)) {
56                case COMPACT_SUCCESS:
57                case COMPACT_CONTINUE:
58                        return false;
59                default:
60                        /* check next zone */
61                        ;
62                }
63        }
64        return true;
65}

high order 페이지 요청을 처리하는데 reclaim/compaction이 계속되야 하는 경우  true를 반환한다.

  • 코드 라인 11~12에서 reclaim/compaction 모드가 아니면 처리를 중단한다.
  • 코드 라인 15~35에서 __GFP_RETRY_MAYFAIL 플래그가 사용된 경우 reclaimed 페이지와 scanned 페이지가 없는 경우 false를 반환한다. 플래그가 사용되지 않은 경우 reclaimed 페이지가 없는 경우 false를 반환한다.
  • 코드 라인 41~47에서 reclaimed 페이지가 order 페이지의 두 배보다 작아 compaction을 위해 작지만 inactive lru 페이지 수가 order 페이지의 두 배보다는 커 충분한 경우 true를 반환한다.
  • 코드 라인 50~64에서 reclaim_idx만큼 존을 순회하며 compaction이 이미 성공하였거나 계속해야 하는 경우 false를 반환한다.
  • 코드 라인 64에서 순회한 모든 존에서 compaction의 성공이 없는 경우 true를 반환하여 compaction이 계속되어야 함을 알린다.

 

in_reclaim_compaction()

mm/vmscan.c

01/* Use reclaim/compaction for costly allocs or under memory pressure */
02static bool in_reclaim_compaction(struct scan_control *sc)
03{
04        if (IS_ENABLED(CONFIG_COMPACTION) && sc->order &&
05                        (sc->order > PAGE_ALLOC_COSTLY_ORDER ||
06                         sc->priority < DEF_PRIORITY - 2))
07                return true;
08 
09        return false;
10}

reclaim/compaction 모드인 경우 true를 반환한다.

  • 0 order 요청을 제외하고 다음 두 조건을 만족하면 true를 반환한다.
    • 우선 순위를 2번 이상 높여 반복 수행 중이다. (낮은 priority 번호가 높은 우선 순위)
    • costly order 요청이다.(order 4부터)

 


Direct-Reclaim 수행

__alloc_pages_direct_reclaim()

mm/page_alloc.c

01/* The really slow allocator path where we enter direct reclaim */
02static inline struct page *
03__alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order,
04                int alloc_flags, const struct alloc_context *ac,
05                unsigned long *did_some_progress)
06{
07        struct page *page = NULL;
08        bool drained = false;
09 
10        *did_some_progress = __perform_reclaim(gfp_mask, order, ac);
11        if (unlikely(!(*did_some_progress)))
12                return NULL;
13 
14retry:
15        page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
16 
17        /*
18         * If an allocation failed after direct reclaim, it could be because
19         * pages are pinned on the per-cpu lists. Drain them and try again
20         */
21        if (!page && !drained) {
22                unreserve_highatomic_pageblock(ac, false);
23                drain_all_pages(NULL);
24                drained = true;
25                goto retry;
26        }
27 
28        return page;
29}

페이지를 회수한 후 페이지 할당을 시도한다. 만일 처음 실패하는 경우 pcp 캐시를 비워 버디 시스템에 free 페이지를 확보한 후 재시도를 한다.

  • 코드 라인 10~12에서 페이지를 회수하며 작은 확률로 회수한 페이지가 없는 경우 null을 반환한다.
  • 코드 라인 14~15에서 retry: 레이블에서 order 페이지 할당을 시도한다.
  • 코드 라인 21~26에서 페이지 할당이 실패하였고 첫 실패인 경우 highatomic 페이지 블럭을 해제하고,  pcp 캐시를 비워 버디시스템에 free 페이지를 확보한 후 재시도 한다.

 

다음 그림은 direct reclaim을 통해 페이지를 회수하는 과정을 보여준다.

 

__perform_reclaim()

mm/page_alloc.c

01/* Perform direct synchronous page reclaim */
02static int
03__perform_reclaim(gfp_t gfp_mask, unsigned int order,
04                                        const struct alloc_context *ac)
05{
06        struct reclaim_state reclaim_state;
07        int progress;
08        unsigned int noreclaim_flag;
09        unsigned long pflags;
10 
11        cond_resched();
12 
13        /* We now go into synchronous reclaim */
14        cpuset_memory_pressure_bump();
15        psi_memstall_enter(&pflags);
16        fs_reclaim_acquire(gfp_mask);
17        noreclaim_flag = memalloc_noreclaim_save();
18        reclaim_state.reclaimed_slab = 0;
19        current->reclaim_state = &reclaim_state;
20 
21        progress = try_to_free_pages(ac->zonelist, order, gfp_mask,
22                                                                ac->nodemask);
23 
24        current->reclaim_state = NULL;
25        memalloc_noreclaim_restore(noreclaim_flag);
26        fs_reclaim_release(gfp_mask);
27        psi_memstall_leave(&pflags);
28 
29        cond_resched();
30 
31        return progress;
32}

페이지를 회수한다. 반환되는 값은 회수한 페이지 수이다.

  • 코드 라인 14에서 전역 cpuset_memory_pressure_enabled가 설정된 경우 현재 태스크 cpuset의 frequency meter를 업데이트한다.
    • 루트 cpuset에 있는 memory_pressure_enabled 파일을 1로 설정하여 사용한다.
  •  코드 라인 15에서 메모리 압박이 시작되었음을 psi에 알린다.
  • 코드 라인 17에서 페이지 회수를 목적으로 잠시 페이지 할당이 필요하다. 이 때 다시 페이지 회수 루틴이 재귀 호출되지 않도록 방지하기 위해 reclaim을 하는 동안 잠시 현재 태스크의 플래그에 PF_MEMALLOC를 설정하여 워터 마크 기준을 없앤 후 할당할 수 있도록 한다.
  • 코드 라인 18~19에서 reclaimed_slab 카운터를 0으로 리셋하고, 현재 태스크에 지정한다.
  • 코드 라인 21~22에서 페이지를 회수하고 회수한 페이지 수를 알아온다.
  • 코드 라인 24에서 태스크에 지정한 reclaim_state를 해제한다.
  • 코드 라인 25에서 현재 태스크의 플래그에 reclaim을 하는 동안 잠시 설정해두었던 PF_MEMALLOC을 제거한다.
  • 코드 라인 27에서 메모리 압박이 완료되었음을 psi에 알린다.

 

Scan Control

스캔 컨트롤을 사용하는 루틴들은 다음과 같다.

  • reclaim_clean_pages_from_list()
  • try_to_free_pages()
  • mem_cgroup_shrink_node()
  • try_to_free_mem_cgroup_pages()
  • balance_pgdat()
  • shrink_all_memory()
  • __node_reclaim()

 

페이지 회수로 free 페이지 확보 시도

try_to_free_pages()

mm/vmscan.c

01unsigned long try_to_free_pages(struct zonelist *zonelist, int order,
02                                gfp_t gfp_mask, nodemask_t *nodemask)
03{
04        unsigned long nr_reclaimed;
05        struct scan_control sc = {
06                .nr_to_reclaim = SWAP_CLUSTER_MAX,
07                .gfp_mask = current_gfp_context(gfp_mask),
08                .reclaim_idx = gfp_zone(gfp_mask),
09                .order = order,
10                .nodemask = nodemask,
11                .priority = DEF_PRIORITY,
12                .may_writepage = !laptop_mode,
13                .may_unmap = 1,
14                .may_swap = 1,
15                .may_shrinkslab = 1,
16        };
17 
18        /*
19         * scan_control uses s8 fields for order, priority, and reclaim_idx.
20         * Confirm they are large enough for max values.
21         */
22        BUILD_BUG_ON(MAX_ORDER > S8_MAX);
23        BUILD_BUG_ON(DEF_PRIORITY > S8_MAX);
24        BUILD_BUG_ON(MAX_NR_ZONES > S8_MAX);
25 
26        /*
27         * Do not enter reclaim if fatal signal was delivered while throttled.
28         * 1 is returned so that the page allocator does not OOM kill at this
29         * point.
30         */
31        if (throttle_direct_reclaim(sc.gfp_mask, zonelist, nodemask))
32                return 1;
33 
34        trace_mm_vmscan_direct_reclaim_begin(order,
35                                sc.may_writepage,
36                                sc.gfp_mask,
37                                sc.reclaim_idx);
38 
39        nr_reclaimed = do_try_to_free_pages(zonelist, &sc);
40 
41        trace_mm_vmscan_direct_reclaim_end(nr_reclaimed);
42 
43        return nr_reclaimed;
44}

페이지 회수(Reclaim)를 시도하고 회수된 페이지 수를 반환한다. 유저 요청 시 free page가 normal 존 이하에서 min 워터마크 기준의 절반 이상을 확보할 때까지 태스크가 스로틀링(sleep)될 수 있다.

  • 코드 라인 5~16에서 페이지 회수를 위한 scan_control 구조체를 준비한다.
  • 코드 라인 31~32에서 direct-reclaim을 위해 일정 기준 이상 스로틀링 중 fatal 시그널을 전달 받은 경우 즉각 루틴을 빠져나간다. 단 1을 반환하므로 OOM kill 루틴을 수행하지 못하게 방지한다.
  • 코드 라인 39에서 페이지 회수를 시도한다.

 

유저 요청 시 스로틀링

throttle_direct_reclaim()

mm/vmscan.c

1/*
2 * Throttle direct reclaimers if backing storage is backed by the network
3 * and the PFMEMALLOC reserve for the preferred node is getting dangerously
4 * depleted. kswapd will continue to make progress and wake the processes
5 * when the low watermark is reached.
6 *
7 * Returns true if a fatal signal was delivered during throttling. If this
8 * happens, the page allocator should not consider triggering the OOM killer.
9 */
01static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist,
02                                        nodemask_t *nodemask)
03{
04        struct zoneref *z;
05        struct zone *zone;
06        pg_data_t *pgdat = NULL;
07 
08        /*
09         * Kernel threads should not be throttled as they may be indirectly
10         * responsible for cleaning pages necessary for reclaim to make forward
11         * progress. kjournald for example may enter direct reclaim while
12         * committing a transaction where throttling it could forcing other
13         * processes to block on log_wait_commit().
14         */
15        if (current->flags & PF_KTHREAD)
16                goto out;
17 
18        /*
19         * If a fatal signal is pending, this process should not throttle.
20         * It should return quickly so it can exit and free its memory
21         */
22        if (fatal_signal_pending(current))
23                goto out;
24 
25        /*
26         * Check if the pfmemalloc reserves are ok by finding the first node
27         * with a usable ZONE_NORMAL or lower zone. The expectation is that
28         * GFP_KERNEL will be required for allocating network buffers when
29         * swapping over the network so ZONE_HIGHMEM is unusable.
30         *
31         * Throttling is based on the first usable node and throttled processes
32         * wait on a queue until kswapd makes progress and wakes them. There
33         * is an affinity then between processes waking up and where reclaim
34         * progress has been made assuming the process wakes on the same node.
35         * More importantly, processes running on remote nodes will not compete
36         * for remote pfmemalloc reserves and processes on different nodes
37         * should make reasonable progress.
38         */
39        for_each_zone_zonelist_nodemask(zone, z, zonelist,
40                                        gfp_zone(gfp_mask), nodemask) {
41                if (zone_idx(zone) > ZONE_NORMAL)
42                        continue;
43 
44                /* Throttle based on the first usable node */
45                pgdat = zone->zone_pgdat;
46                if (allow_direct_reclaim(pgdat))
47                        goto out;
48                break;
49        }
50 
51        /* If no zone was usable by the allocation flags then do not throttle */
52        if (!pgdat)
53                goto out;
54 
55        /* Account for the throttling */
56        count_vm_event(PGSCAN_DIRECT_THROTTLE);
57 
58        /*
59         * If the caller cannot enter the filesystem, it's possible that it
60         * is due to the caller holding an FS lock or performing a journal
61         * transaction in the case of a filesystem like ext[3|4]. In this case,
62         * it is not safe to block on pfmemalloc_wait as kswapd could be
63         * blocked waiting on the same lock. Instead, throttle for up to a
64         * second before continuing.
65         */
66        if (!(gfp_mask & __GFP_FS)) {
67                wait_event_interruptible_timeout(pgdat->pfmemalloc_wait,
68                        allow_direct_reclaim(pgdat), HZ);
69 
70                goto check_pending;
71        }
72 
73        /* Throttle until kswapd wakes the process */
74        wait_event_killable(zone->zone_pgdat->pfmemalloc_wait,
75                allow_direct_reclaim(pgdat));
76 
77check_pending:
78        if (fatal_signal_pending(current))
79                return true;
80 
81out:
82        return false;
83}

유저 태스크에서 direct-reclaim 요청 시 필요한 만큼 스로틀링한다. 파일 시스템을 사용하지 않는(nofs) direct-reclaim 요청인 경우 스로틀링은 1초로 제한된다. 스로틀링 중 sigkill 시그널 수신 여부를 반환한다.

  • 코드 라인 15~16에서 커널 스레드에서 요청한 경우 스로틀링을 하지 않기 위해 처리를 중단하고 false를 반환한다.
  • 코드 라인 22~23에서 SIGKILL 시그널이 처리되고 있는 태스크의 경우도 역시 처리를 중단하고 false를 반환한다.
  • 코드 라인 39~49에서 요청한 노드의 lowmem 존들의 direct-reclaim이 허용 기준 이상인 경우 스로틀링을 포기한다.
  • 코드 라인 52~53에서 사용할 수 있는 노드가 없는 경우 처리를 포기한다.
  • 코드 라인 56에서 스로틀링이 시작되는 구간이다. PGSCAN_DIRECT_THROTTLE stat을 증가시킨다.
  • 코드 라인 66~71에서 파일 시스템을 사용하지 않는 direct-reclaim 요청인 경우 direct-reclaim을 허락할 때까지 최대 1초간 스로틀링 후 check_pending 레이블로 이동한다.
  • 코드 라인74~75에서 파일 시스템을 사용하는 direct-reclaim의 경우 kswapd를 깨워 free page를 확보하며 direct-reclaim을 허락할 때까지 슬립한다.
  • 코드 라인 77~82에서 현재 태스크에 SIGKILL 시그널이 요청된 경우 true를 반환하고 그렇지 않은 경우 false를 반환한다.

 

다음 그림은 유저 요청 direct-reclaim 시 파일 시스템 사용 여부에 따라 direct-reclaim을 사용하기 위해 스로틀링하는 과정을 보여준다.

 

direct-reclaim 허락 여부

allow_direct_reclaim()

mm/vmscan.c

01static bool allow_direct_reclaim(pg_data_t *pgdat)
02{
03        struct zone *zone;
04        unsigned long pfmemalloc_reserve = 0;
05        unsigned long free_pages = 0;
06        int i;
07        bool wmark_ok;
08 
09        if (pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES)
10                return true;
11 
12        for (i = 0; i <= ZONE_NORMAL; i++) {
13                zone = &pgdat->node_zones[i];
14                if (!managed_zone(zone))
15                        continue;
16 
17                if (!zone_reclaimable_pages(zone))
18                        continue;
19 
20                pfmemalloc_reserve += min_wmark_pages(zone);
21                free_pages += zone_page_state(zone, NR_FREE_PAGES);
22        }
23 
24        /* If there are no reserves (unexpected config) then do not throttle */
25        if (!pfmemalloc_reserve)
26                return true;
27 
28        wmark_ok = free_pages > pfmemalloc_reserve / 2;
29 
30        /* kswapd must be awake if processes are being throttled */
31        if (!wmark_ok && waitqueue_active(&pgdat->kswapd_wait)) {
32                pgdat->kswapd_classzone_idx = min(pgdat->kswapd_classzone_idx,
33                                                (enum zone_type)ZONE_NORMAL);
34                wake_up_interruptible(&pgdat->kswapd_wait);
35        }
36 
37        return wmark_ok;
38}

요청한 노드에서 direct-reclaim을 허락하는지 여부를 반환한다. 만일 lowmem 존들의 free 페이지가 min 워터마크 50% 이하인 경우 현재 태스크를 슬립하고, kswapd를 깨운 뒤 false를 반환한다. 그리고 그 이상인 경우 direct-reclaim을 시도해도 좋다고 판단하여 true를 반환한다.

  • 코드 라인 9~10에서 reclaim 실패 횟수가 MAX_RECLAIM_RETRIES(16)번 이상일 때 스로틀을 하지못하게 곧바로 true를 반환한다.
  • 코드 라인 12~22에서 lowmem 존들의 min 워터마크를 합산한 pfmemalloc_reserve 값 및 free 페이지 수의 합산 값을 구한다.
  • 코드 라인 25~26에서 pfmemalloc_reserve 값이 0인 경우 스로틀을 하지 못하게 곧바로 true를 반환한다.
  • 코드 라인 28~35에서 free 페이지 합산 수가 lowmem 존들의 min 워터마크 합산 값의 50% 이하이면 현재 태스크를 슬립시키고 kswapd를 깨운 뒤 false를 반환한다.

 

다음 그림은 direct-reclaim 허락 여부를 알아오는 과정을 보여준다.

 

do_try_to_free_pages()

mm/vmscan.c

01/*
02 * This is the main entry point to direct page reclaim.
03 *
04 * If a full scan of the inactive list fails to free enough memory then we
05 * are "out of memory" and something needs to be killed.
06 *
07 * If the caller is !__GFP_FS then the probability of a failure is reasonably
08 * high - the zone may be full of dirty or under-writeback pages, which this
09 * caller can't do much about.  We kick the writeback threads and take explicit
10 * naps in the hope that some of these pages can be written.  But if the
11 * allocating task holds filesystem locks which prevent writeout this might not
12 * work, and the allocation attempt will fail.
13 *
14 * returns:     0, if no pages reclaimed
15 *              else, the number of pages reclaimed
16 */
01static unsigned long do_try_to_free_pages(struct zonelist *zonelist,
02                                          struct scan_control *sc)
03{
04        int initial_priority = sc->priority;
05        pg_data_t *last_pgdat;
06        struct zoneref *z;
07        struct zone *zone;
08retry:
09        delayacct_freepages_start();
10 
11        if (global_reclaim(sc))
12                __count_zid_vm_events(ALLOCSTALL, sc->reclaim_idx, 1);
13 
14        do {
15                vmpressure_prio(sc->gfp_mask, sc->target_mem_cgroup,
16                                sc->priority);
17                sc->nr_scanned = 0;
18                shrink_zones(zonelist, sc);
19 
20                if (sc->nr_reclaimed >= sc->nr_to_reclaim)
21                        break;
22 
23                if (sc->compaction_ready)
24                        break;
25 
26                /*
27                 * If we're getting trouble reclaiming, start doing
28                 * writepage even in laptop mode.
29                 */
30                if (sc->priority < DEF_PRIORITY - 2)
31                        sc->may_writepage = 1;
32        } while (--sc->priority >= 0);
33 
34        last_pgdat = NULL;
35        for_each_zone_zonelist_nodemask(zone, z, zonelist, sc->reclaim_idx,
36                                        sc->nodemask) {
37                if (zone->zone_pgdat == last_pgdat)
38                        continue;
39                last_pgdat = zone->zone_pgdat;
40                snapshot_refaults(sc->target_mem_cgroup, zone->zone_pgdat);
41                set_memcg_congestion(last_pgdat, sc->target_mem_cgroup, false);
42        }
43 
44        delayacct_freepages_end();
45 
46        if (sc->nr_reclaimed)
47                return sc->nr_reclaimed;
48 
49        /* Aborted reclaim to try compaction? don't OOM, then */
50        if (sc->compaction_ready)
51                return 1;
52 
53        /* Untapped cgroup reserves?  Don't OOM, retry. */
54        if (sc->memcg_low_skipped) {
55                sc->priority = initial_priority;
56                sc->memcg_low_reclaim = 1;
57                sc->memcg_low_skipped = 0;
58                goto retry;
59        }
60 
61        return 0;
62}

direct-reclaim 요청을 통해 페이지를 회수하여 free 페이지를 확보를 시도한다.

  • 코드 라인 8~9에서 retry: 레이블이다. 페이지 회수에 소요되는 시간을 계량하기 위해 시작한다.
  • 코드 라인 11~12에서 global reclaim을 사용해야하는 경우 ALLOCSTALL stat을 증가시킨다.
  • 코드 라인 14~16에서 루프를 돌며 우선 순위가 높아져 스캔 depth가 깊어지는 경우 vmpressure 정보를 갱신한다.
  • 코드 라인 17~18에서 스캔 건 수를 리셋시키고 페이지를 회수하고 회수한 건 수를 알아온다.
  • 코드 라인 20~21에서 회수 건 수가 회수해야 할 건 수보다 큰 경우 처리를 위해 루프에서 벗어난다.
  • 코드 라인 23~24에서 compaction이 준비된 경우 처리를 위해 루프에서 벗어난다.
  • 코드 라인 30~31에서 우선 순위를 2 단계 더 높여 처리하는 경우 writepage 기능을 설정한다.
  • 코드 라인 32에서 우선 순위를 최고까지 높여가며(0으로 갈수록 높아진다) 루프를 돈다.
  • 코드 라인 34~42에서 zonelist를 순회하며 노드에 대해 노드 또는 memcg lru의 refaults를 갱신하고 memcg 노드의 congested를 false로 리셋한다.
  • 코드 라인 44에서 페이지 회수에 소요되는 시간을 계량한다.
  • 코드 라인 46~47네거 회수한 적이 있는 경우 그 값을 반환한다.
  • 코드 라인 50~51에서 compaction이 준비된 경우 1을 반환한다.
  • 코드 라인 54~59에서 sc->memcg_low_skipped가 설정된 경우 처음 재시도에 한해 priority를 다시 원래 요청 priority로 바꾸고 재시도한다.

 

global_reclaim()

mm/vmscan.c

01#ifdef CONFIG_MEMCG
02static bool global_reclaim(struct scan_control *sc)
03{
04        return !sc->target_mem_cgroup;
05}
06#else
07static bool global_reclaim(struct scan_control *sc)
08{
09        return true;
10}
11#endif

CONFIG_MEMCG 커널 옵션을 사용하여 Memory Control Group을 사용하는 경우 scan_control의 target_mem_cgroup이 정해진 경우 false를 반환한다. 그렇지 않은 경우 global reclaim을 위해 true를 반환한다. CONFIG_MEMCG를 사용하지 않는 경우 항상 true이다.

 


Memory Pressure (per-cpuset reclaims)

cpuset_memory_pressure_bump()

include/linux/cpuset.h

1#define cpuset_memory_pressure_bump()                           \
2        do {                                                    \
3                if (cpuset_memory_pressure_enabled)             \
4                        __cpuset_memory_pressure_bump();        \
5        } while (0)

현재 태스크 cpuset의 frequency meter를 업데이트한다.

 

__cpuset_memory_pressure_bump()

kernel/cpuset.c

01/**
02 * cpuset_memory_pressure_bump - keep stats of per-cpuset reclaims.
03 *
04 * Keep a running average of the rate of synchronous (direct)
05 * page reclaim efforts initiated by tasks in each cpuset.
06 *
07 * This represents the rate at which some task in the cpuset
08 * ran low on memory on all nodes it was allowed to use, and
09 * had to enter the kernels page reclaim code in an effort to
10 * create more free memory by tossing clean pages or swapping
11 * or writing dirty pages.
12 *
13 * Display to user space in the per-cpuset read-only file
14 * "memory_pressure".  Value displayed is an integer
15 * representing the recent rate of entry into the synchronous
16 * (direct) page reclaim by any task attached to the cpuset.
17 **/
1void __cpuset_memory_pressure_bump(void)
2{
3        rcu_read_lock();
4        fmeter_markevent(&task_cs(current)->fmeter);
5        rcu_read_unlock();
6}

현재 태스크 cpuset의 frequency meter를 업데이트한다.

 

fmeter_markevent()

kernel/cpuset.c

1/* Process any previous ticks, then bump cnt by one (times scale). */
2static void fmeter_markevent(struct fmeter *fmp)
3{
4        spin_lock(&fmp->lock);
5        fmeter_update(fmp);
6        fmp->cnt = min(FM_MAXCNT, fmp->cnt + FM_SCALE);
7        spin_unlock(&fmp->lock);
8}

요청한 frequency meter를 업데이트하고 다음 계산을 위해 이벤트 수에 1,000을 대입하되 최대 1,000,000을 넘기지 않게 한다.

 

fmeter_update()

kernel/cpuset.c

01/* Internal meter update - process cnt events and update value */
02static void fmeter_update(struct fmeter *fmp)
03{
04        time_t now = get_seconds();
05        time_t ticks = now - fmp->time;
06 
07        if (ticks == 0)
08                return;
09 
10        ticks = min(FM_MAXTICKS, ticks);
11        while (ticks-- > 0)
12                fmp->val = (FM_COEF * fmp->val) / FM_SCALE;
13        fmp->time = now;
14 
15        fmp->val += ((FM_SCALE - FM_COEF) * fmp->cnt) / FM_SCALE;
16        fmp->cnt = 0;
17}

요청한 frequency meter로 val 값을 계산하고 이벤트 수를 0으로 리셋한다.

  • 코드 라인 4~8에서 fmeter에 기록된 초(second)로부터 경과한 초를 알아온다.
  • 코드 라인 10~12에서 ticks는 최대 99까지로 제한하고, ticks 만큼 fmp->val *= 93.3%를 반복한다.
  • 코드 라인 13에서 다음 계산을 위해 현재 초로 갱신한다.
  • 코드 라인 15~16에서 fmp->val에 fmp->cnt x 6.7%를 더한 후 이벤트 수를 0으로 리셋한다.

 

fmeter 구조체

kernel/cgroup/cpuset.c

1struct fmeter {
2        int cnt;                /* unprocessed events count */
3        int val;                /* most recent output value */
4        time_t time;            /* clock (secs) when val computed */
5        spinlock_t lock;        /* guards read or write of above */
6};
  • cnt
    • 처리되지 않은 이벤트 수
  • val
    • 최근 fmeter 업데이트 시 계산된 값
  • time
    • val 값이 계산될 때의 clock(secs)

 

참고

 

댓글 남기기