๏ปฟ# Workingset (์ž‘์—… ์ง‘ํ•ฉ ๊ฐ์ง€)

๊ด€๋ จ ์†Œ์Šค: mm/workingset.c, mm/filemap.c, mm/vmscan.c, mm/swap.c, mm/memcontrol.c, include/linux/swap.h, include/linux/mmzone.h, include/linux/mm_inline.h

๊ฐœ์š” (Overview)

Workingset์€ ํŽ˜์ด์ง€๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ œ๊ฑฐ๋œ ํ›„ ์งง์€ ์‹œ๊ฐ„ ๋‚ด์— ์žฌ์‚ฌ์šฉ๋˜๋Š”์ง€(refault)๋ฅผ ์ถ”์ ํ•˜์—ฌ, ๋ฉ”๋ชจ๋ฆฌ ํšŒ์ˆ˜ ๊ฒฐ์ •์— ํ™œ์šฉํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๋‹ค. ํ•ต์‹ฌ ์•„์ด๋””์–ด๋Š” "shadow entry"๋กœ, folio๊ฐ€ ์ œ๊ฑฐ๋  ๋•Œ xarray์˜ ํ•ด๋‹น ์Šฌ๋กฏ์— ์ œ๊ฑฐ ์‹œ์ ์˜ ํƒ€์ž„์Šคํƒฌํ”„(๋น„๊ฑฐ์ฃผ ์—ฐ๋ น)๋ฅผ ์ €์žฅํ•˜๊ณ , ๋‚˜์ค‘์— ๊ฐ™์€ folio๊ฐ€ ๋‹ค์‹œ fault๋˜๋ฉด ๊ทธ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ(refault distance)์„ ๊ณ„์‚ฐํ•˜์—ฌ working set ๊ฒฝ์Ÿ ์ƒํ™ฉ์„ ํŒ๋‹จํ•œ๋‹ค. refault distance๊ฐ€ ํ˜„์žฌ ํ™œ์„ฑ+๋น„ํ™œ์„ฑ ์บ์‹œ ํฌ๊ธฐ ์ด๋‚ด์ด๋ฉด ํ•ด๋‹น folio๋Š” ํ™œ์„ฑ ๋ชฉ๋ก์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ๋ณดํ˜ธ๋œ๋‹ค.

์ผ์ƒ ๋น„์œ : ๋„์„œ๊ด€ ๋น„์œ ๋กœ ์„ค๋ช…ํ•˜๋ฉด, workingset์€ "์ตœ๊ทผ์— ๋นŒ๋ ค๊ฐ„ ์ฑ…"์„ ์ถ”์ ํ•˜๋Š” ์‹œ์Šคํ…œ์ด๋‹ค. ๋น„ํ™œ์„ฑ ๋ชฉ๋ก์€ "์ž„์‹œ ์ง„์—ด๋Œ€", ํ™œ์„ฑ ๋ชฉ๋ก์€ "์ž์ฃผ ๋นŒ๋ฆฌ๋Š” ์ฑ… ์ง„์—ด๋Œ€"์ด๋‹ค. ์ƒˆ๋กœ์šด ์ฑ…์ด ๋“ค์–ด์˜ค๋ฉด ์ž„์‹œ ์ง„์—ด๋Œ€ ๋์— ๋†“์ด๊ณ , ์˜ค๋ž˜๋œ ์ฑ…์€ ๋ฐ€๋ ค๋‚˜ ํ‡ด์ถœ๋œ๋‹ค. ์–ด๋–ค ์ฑ…์ด ํ‡ด์ถœ๋œ ํ›„ ๋‹ค์‹œ ๋นŒ๋ ค๊ฐ€๋ฉด(refault), ์ด์ „ ๋Œ€์—ฌ ๊ธฐ๋ก(shadow entry)์„ ํ™•์ธํ•˜์—ฌ "์ด ์ฑ…์ด ์–ผ๋งˆ๋‚˜ ์ž์ฃผ ๋นŒ๋ ค์กŒ๋Š”์ง€" ํŒ๋‹จํ•œ๋‹ค. ์ถฉ๋ถ„ํžˆ ์ž์ฃผ ๋นŒ๋ ค์ง„ ์ฑ…์€ ์ž์ฃผ ๋นŒ๋ฆฌ๋Š” ์ฑ… ์ง„์—ด๋Œ€๋กœ ์Šน๊ฒฉ๋œ๋‹ค.

Linux 7.0์—์„œ๋Š” ๋‘ ๊ฐ€์ง€ ๊ตฌํ˜„ ๊ฒฝ๋กœ๊ฐ€ ๊ณต์กดํ•œ๋‹ค: (1) ๊ธฐ์กด Double CLOCK ์•Œ๊ณ ๋ฆฌ์ฆ˜(MGLRU ๋ฏธ์‚ฌ์šฉ ์‹œ)๊ณผ (2) MGLRU(CONFIG_LRU_GEN) ๊ธฐ๋ฐ˜ ์„ธ๋Œ€๋ณ„ ์ถ”์ . Double CLOCK์€ ๋น„ํ™œ์„ฑ/ํ™œ์„ฑ ๋ชฉ๋ก์—์„œ ๊ฐ๊ฐ์˜ eviction/activation ์นด์šดํ„ฐ๋ฅผ lruvec->nonresident_age๋กœ ๊ด€๋ฆฌํ•˜๊ณ , MGLRU๋Š” lrugen->evicted[hist][type][tier]์™€ lrugen->refaulted[hist][type][tier] ๋ฐฐ์—ด๋กœ ์„ธ๋Œ€๋ณ„๋กœ ์ถ”์ ํ•œ๋‹ค. ๋˜ํ•œ shadow entry๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ๋ˆ„์ ๋˜์ง€ ์•Š๋„๋ก shadow_nodes list_lru์™€ shrinker๋กœ shadow ๋…ธ๋“œ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.

ํ•ต์‹ฌ ์†Œ์Šค ํŒŒ์ผ:

  • mm/workingset.c โ€” workingset ๊ฐ์ง€ ์ „์ฒด ๊ตฌํ˜„ (826์ค„)
  • mm/filemap.c โ€” workingset_refault() ์žฌ์ง„์ž…์  (filemap_add_folio())
  • mm/vmscan.c โ€” workingset_eviction() ํ˜ธ์ถœ ์ง€์  (shrink_folio_list())
  • mm/swap.c โ€” workingset_activation() ํ˜ธ์ถœ ์ง€์  (folio_mark_accessed())
  • mm/memcontrol.c โ€” WORKINGSET_* vmstat/cgroup ํ†ต๊ณ„ ์ด๋ฆ„
  • include/linux/swap.h โ€” workingset_eviction(), workingset_refault() ๋“ฑ ์„ ์–ธ (line 314-320)
  • include/linux/mmzone.h โ€” lruvec.nonresident_age, WORKINGSET_* ํ†ต๊ณ„ enum (line 192-202, 681)
  • include/linux/mm_inline.h โ€” lru_gen_enabled(), lru_gen_in_fault(), folio_lru_refs() ๋“ฑ MGLRU ํ—ฌํผ

  • ๋น ๋ฅธ ์ ๊ฒ€ ๋ช…๋ น

    # workingset ๊ด€๋ จ ์ปค๋„ ์‹ฌ๋ณผ ํ™•์ธ
    cat /proc/kallsyms | grep -E 'workingset_eviction|workingset_refault|workingset_test_recent|workingset_age_nonresident|workingset_activation'
    
    # workingset ํ†ต๊ณ„ ํ™•์ธ (๋…ธ๋“œ๋ณ„)
    cat /proc/vmstat | grep -E 'workingset_|nr_workingset'
    
    # shadow entry ํ†ต๊ณ„
    cat /proc/vmstat | grep -E 'workingset_nodes|workingset_refault|workingset_activate|workingset_restore|workingset_nodereclaim'
    
    # MGLRU ํ™œ์„ฑํ™” ์—ฌ๋ถ€ ํ™•์ธ
    cat /sys/kernel/mm/lru_gen/enabled
    
    # memcg๋ณ„ workingset ํ†ต๊ณ„ ํ™•์ธ
    cat /sys/fs/cgroup/<cgroup>/memory.stat | grep -E 'workingset_refault|workingset_activate|workingset_restore|workingset_nodereclaim'
    
    # memcg ์ด๋ฒคํŠธ ํ™•์ธ
    cat /sys/fs/cgroup/<cgroup>/memory.events
    
    # ํŽ˜์ด์ง€ ํดํŠธ/๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ• ์ง€ํ‘œ ํ™•์ธ
    cat /proc/vmstat | grep -E 'pgfault|pgmajfault|allocstall|workingset_'
    
    # PSI ๋ฉ”๋ชจ๋ฆฌ ์••๋ ฅ ํ™•์ธ
    cat /proc/pressure/memory
    
    # LRU_GEN ๊ด€๋ จ ํ†ต๊ณ„ ํ™•์ธ
    cat /proc/vmstat | grep -E 'nr_lru_gen|nr_inactive|nr_active'
    
    # workingset refault ๋นˆ๋„ ๋ชจ๋‹ˆํ„ฐ๋ง (์‹ค์‹œ๊ฐ„)
    watch -n 1 'cat /proc/vmstat | grep workingset'
    
    # ํŠน์ • ํ”„๋กœ์„ธ์Šค์˜ workingset ํฌ๊ธฐ ํ™•์ธ
    cat /proc/<pid>/smaps | grep -E 'Referenced|Anonymous|File'
    
    # shadow entry ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ
    cat /proc/meminfo | grep -E 'Slab|SReclaimable|SUnreclaim'
    
    # MGLRU ์„ธ๋Œ€๋ณ„ ํ†ต๊ณ„ ํ™•์ธ
    cat /sys/kernel/mm/lru_gen/stats
    
    # memcg๋ณ„ nonresident_age ํ™•์ธ
    cat /sys/fs/cgroup/<cgroup>/memory.stat | grep -E 'workingset_refault|workingset_activate|workingset_restore|workingset_nodereclaim'

    ํ•ต์‹ฌ ์ž๋ฃŒ๊ตฌ์กฐ

    shadow entry โ€” ์ œ๊ฑฐ ์‹œ์  ์ •๋ณด ์ €์žฅ

    shadow entry๋Š” xarray์˜ value ์—”ํŠธ๋ฆฌ๋กœ, folio ์ œ๊ฑฐ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ์Šฌ๋กฏ์— ์ €์žฅ๋œ๋‹ค. pack_shadow()๋กœ ์ธ์ฝ”๋”ฉํ•˜๊ณ  unpack_shadow()๋กœ ๋””์ฝ”๋”ฉํ•œ๋‹ค.

    // mm/workingset.c:199-228
    static void *pack_shadow(int memcgid, pg_data_t *pgdat, unsigned long eviction,
    				 bool workingset)
    {
    	eviction &= EVICTION_MASK;
    	eviction = (eviction << MEM_CGROUP_ID_SHIFT) | memcgid;
    	eviction = (eviction << NODES_SHIFT) | pgdat->node_id;
    	eviction = (eviction << WORKINGSET_SHIFT) | workingset;
    
    	return xa_mk_value(eviction);
    }
    
    static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat,
    			  unsigned long *evictionp, bool *workingsetp)
    {
    	int memcgid, nid;
    	bool workingset;
    	unsigned long entry = xa_to_value(shadow);
    
    	workingset = entry & ((1UL << WORKINGSET_SHIFT) - 1);
    	entry >>= WORKINGSET_SHIFT;
    	nid = entry & ((1UL << NODES_SHIFT) - 1);
    	entry >>= NODES_SHIFT;
    	memcgid = entry & ((1UL << MEM_CGROUP_ID_SHIFT) - 1);
    	entry >>= MEM_CGROUP_ID_SHIFT;
    
    	*memcgidp = memcgid;
    	*pgdat = NODE_DATA(nid);
    	*evictionp = entry;
    	*workingsetp = workingset;
    }
  • EVICTION_SHIFT: BITS_PER_LONG - BITS_PER_XA_VALUE + WORKINGSET_SHIFT + NODES_SHIFT + MEM_CGROUP_ID_SHIFT โ€” ํƒ€์ž„์Šคํƒฌํ”„์— ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋น„ํŠธ ์ˆ˜ ๊ฒฐ์ •
  • bucket_order: ๋ฉ”๋ชจ๋ฆฌ ํฌ๊ธฐ๊ฐ€ ํƒ€์ž„์Šคํƒฌํ”„ ๋น„ํŠธ ๋ฒ”์œ„๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ํ•˜์œ„ ๋น„ํŠธ๋ฅผ ์ž˜๋ผ coarser granularity๋กœ ์ฒ˜๋ฆฌ
  • lruvec โ€” LRU ๋ฒกํ„ฐ์™€ ๋น„๊ฑฐ์ฃผ ์—ฐ๋ น

    // include/linux/mmzone.h:670-698
    struct lruvec {
    	struct list_head		lists[NR_LRU_LISTS]; // ๋น„ํ™œ์„ฑ/ํ™œ์„ฑ LRU ๋ชฉ๋ก (anon/file/unevictable)
    	spinlock_t			lru_lock;           // memcg๋ณ„ LRU ์ž ๊ธˆ
    	unsigned long			anon_cost;          // anon LRU ํšŒ์ˆ˜ ๋น„์šฉ ์ถ”์ 
    	unsigned long			file_cost;          // file LRU ํšŒ์ˆ˜ ๋น„์šฉ ์ถ”์ 
    	atomic_long_t			nonresident_age;    // ๋น„๊ฑฐ์ฃผ ์—”ํŠธ๋ฆฌ ์—ฐ๋ น (ํ•ต์‹ฌ!)
    	unsigned long			refaults[ANON_AND_FILE]; // ์ด์ „ ํšŒ์ˆ˜ ์‚ฌ์ดํด ์‹œ refault ์ˆ˜
    	unsigned long			flags;
    #ifdef CONFIG_LRU_GEN
    	struct lru_gen_folio		lrugen;             // MGLRU ์„ธ๋Œ€๋ณ„ folio ๊ด€๋ฆฌ
    #endif
    #ifdef CONFIG_MEMCG
    	struct pglist_data *pgdat;  // ์ด lruvec์ด ์†ํ•œ NUMA ๋…ธ๋“œ
    #endif
    };
  • nonresident_age: workingset_age_nonresident()์—์„œ atomic_long_add(nr_pages)๋กœ ์ฆ๊ฐ€. folio ์ œ๊ฑฐ/ํ™œ์„ฑํ™” ์‹œ์ ์— ์ด ๊ฐ’์„ ํƒ€์ž„์Šคํƒฌํ”„๋กœ ์ €์žฅ
  • WORKINGSET_* ํ†ต๊ณ„ enum

    // include/linux/mmzone.h:192-202
    WORKINGSET_NODES,              // shadow entry๋ฅผ ํฌํ•จํ•˜๋Š” xarray ๋…ธ๋“œ ์ˆ˜
    WORKINGSET_REFAULT_BASE,
    WORKINGSET_REFAULT_ANON = WORKINGSET_REFAULT_BASE,  // ์ต๋ช… refault ์ˆ˜
    WORKINGSET_REFAULT_FILE,       // ํŒŒ์ผ refault ์ˆ˜
    WORKINGSET_ACTIVATE_BASE,
    WORKINGSET_ACTIVATE_ANON = WORKINGSET_ACTIVATE_BASE, // refault๋กœ ํ™œ์„ฑํ™”๋œ ์ต๋ช… ํŽ˜์ด์ง€ ์ˆ˜
    WORKINGSET_ACTIVATE_FILE,      // refault๋กœ ํ™œ์„ฑํ™”๋œ ํŒŒ์ผ ํŽ˜์ด์ง€ ์ˆ˜
    WORKINGSET_RESTORE_BASE,
    WORKINGSET_RESTORE_ANON = WORKINGSET_RESTORE_BASE,  // workingset์œผ๋กœ ๋ณต์›๋œ ์ต๋ช… ํŽ˜์ด์ง€ ์ˆ˜
    WORKINGSET_RESTORE_FILE,       // workingset์œผ๋กœ ๋ณต์›๋œ ํŒŒ์ผ ํŽ˜์ด์ง€ ์ˆ˜
    WORKINGSET_NODERECLAIM,        // shrinker์— ์˜ํ•ด ํšŒ์ˆ˜๋œ shadow ๋…ธ๋“œ ์ˆ˜

    MGLRU ์„ธ๋Œ€๋ณ„ ์ถ”์  ๊ตฌ์กฐ (CONFIG_LRU_GEN)

    // mm/workingset.c:232-258 (lru_gen_eviction ๋‚ด๋ถ€)
    // MGLRU์—์„œ folio ์ œ๊ฑฐ ์‹œ shadow entry ์ƒ์„ฑ
    static void *lru_gen_eviction(struct folio *folio)
    {
    	int hist;
    	unsigned long token;
    	unsigned long min_seq;
    	struct lruvec *lruvec;
    	struct lru_gen_folio *lrugen;
    	int type = folio_is_file_lru(folio);     // ์ต๋ช…(0) ๋˜๋Š” ํŒŒ์ผ(1)
    	int delta = folio_nr_pages(folio);
    	int refs = folio_lru_refs(folio);        // ์ ‘๊ทผ ํšŸ์ˆ˜ ์ฐธ์กฐ
    	bool workingset = folio_test_workingset(folio);
    	int tier = lru_tier_from_refs(refs, workingset);  // ์ฐธ์กฐ ํšŸ์ˆ˜์—์„œ tier๋กœ ๋ณ€ํ™˜
    	struct mem_cgroup *memcg = folio_memcg(folio);
    	struct pglist_data *pgdat = folio_pgdat(folio);
    
    	BUILD_BUG_ON(LRU_GEN_WIDTH + LRU_REFS_WIDTH > BITS_PER_LONG - EVICTION_SHIFT);
    
    	lruvec = mem_cgroup_lruvec(memcg, pgdat);
    	lrugen = &lruvec->lrugen;
    	min_seq = READ_ONCE(lrugen->min_seq[type]);
    	token = (min_seq << LRU_REFS_WIDTH) | max(refs - 1, 0);  // ์‹œํ€€์Šค + ์ฐธ์กฐ ํšŸ์ˆ˜ ๊ฒฐํ•ฉ
    
    	hist = lru_hist_from_seq(min_seq);
    	atomic_long_add(delta, &lrugen->evicted[hist][type][tier]);  // ์„ธ๋Œ€๋ณ„ ์ถ•์ถœ ์นด์šดํŠธ ์ฆ๊ฐ€
    
    	return pack_shadow(mem_cgroup_private_id(memcg), pgdat, token, workingset);
    }

    ํ•ต์‹ฌ ํ•จ์ˆ˜

    workingset_eviction() โ€” folio ์ œ๊ฑฐ ์‹œ shadow entry ์ƒ์„ฑ

    // mm/workingset.c:381-404
    void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg)

    ์—ญํ• : folio๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ œ๊ฑฐ๋  ๋•Œ shadow entry๋ฅผ ์ƒ์„ฑํ•˜์—ฌ xarray์— ์ €์žฅํ•œ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

    1. lru_gen_enabled() โ†’ lru_gen_eviction() ํ˜ธ์ถœ (MGLRU ๊ฒฝ๋กœ)

    2. MGLRU ๋ฏธ์‚ฌ์šฉ ์‹œ: lruvec->nonresident_age๋ฅผ ์ฝ์–ด bucket_order๋กœ ์ •๋ ฌ ํ›„ pack_shadow()๋กœ ์ธ์ฝ”๋”ฉ

    3. folio_test_workingset(folio) ํ”Œ๋ž˜๊ทธ๋ฅผ shadow entry์— ํฌํ•จ โ€” ์ด์ „์— workingset์œผ๋กœ ํ™œ์„ฑํ™”๋˜์—ˆ๋Š”์ง€ ์ถ”์ 

    workingset_refault() โ€” refault ์‹œ workingset ํŒ๋‹จ ๋ฐ ํ™œ์„ฑํ™”

    // mm/workingset.c:534-583
    void workingset_refault(struct folio *folio, void *shadow)

    ์—ญํ• : ์ด์ „์— ์ œ๊ฑฐ๋œ folio๊ฐ€ ๋‹ค์‹œ fault๋˜๋ฉด refault distance๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ , ์ ์ ˆํ•˜๋ฉด active list๋กœ ํ™œ์„ฑํ™”ํ•œ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

    1. lru_gen_enabled() โ†’ lru_gen_refault() ํ˜ธ์ถœ (MGLRU ๊ฒฝ๋กœ)

    2. workingset_test_recent() ํ˜ธ์ถœ โ†’ shadow entry๊ฐ€ ์ตœ๊ทผ ๊ฒƒ์ธ์ง€ ํŒ๋‹จ

    3. workingset_test_recent()๊ฐ€ true์ด๋ฉด:

    - folio_set_active(folio) โ€” active list๋กœ ํ™œ์„ฑํ™”

    - workingset_age_nonresident() โ€” ๋น„๊ฑฐ์ฃผ ์—ฐ๋ น ์ฆ๊ฐ€

    - workingset ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ์œผ๋ฉด: folio_set_workingset(), lru_note_cost_refault() ํ˜ธ์ถœ

    4. false์ด๋ฉด ์•„๋ฌด ๋™์ž‘ ์—†์ด ๋ฐ˜ํ™˜

    workingset_test_recent() โ€” refault distance ๊ณ„์‚ฐ ๋ฐ ํŒ๋‹จ

    // mm/workingset.c:418-523
    bool workingset_test_recent(void *shadow, bool file, bool *workingset, bool flush)

    ์—ญํ• : shadow entry๋ฅผ ๋””์ฝ”๋”ฉํ•˜๊ณ , ํ˜„์žฌ nonresident_age์™€ ๋น„๊ตํ•˜์—ฌ refault distance๊ฐ€ working set ํฌ๊ธฐ ์ด๋‚ด์ธ์ง€ ํŒ๋‹จํ•œ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

    1. lru_gen_enabled() โ†’ lru_gen_test_recent() ํ˜ธ์ถœ

    2. unpack_shadow()๋กœ memcgid, node, eviction ํƒ€์ž„์Šคํƒฌํ”„ ์ถ”์ถœ

    3. mem_cgroup_from_private_id()๋กœ eviction ์‹œ์ ์˜ memcg ์กฐํšŒ (์‚ญ์ œ๋˜์—ˆ์„ ์ˆ˜ ์žˆ์Œ)

    4. mem_cgroup_flush_stats_ratelimited()๋กœ ํ†ต๊ณ„ ๊ฐฑ์‹ 

    5. refault_distance = (refault - eviction) & EVICTION_MASK ๊ณ„์‚ฐ

    6. workingset_size ๊ณ„์‚ฐ:

    - file ํŽ˜์ด์ง€์ธ ๊ฒฝ์šฐ: NR_ACTIVE_FILE๋งŒ ๊ธฐ๋ณธ ํฌํ•จ

    - anon ํŽ˜์ด์ง€์ธ ๊ฒฝ์šฐ: NR_INACTIVE_FILE๋„ ํฌํ•จ

    - swap ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์‹œ: NR_ACTIVE_ANON์„ ์ถ”๊ฐ€ํ•˜๊ณ , file ํŽ˜์ด์ง€์ธ ๊ฒฝ์šฐ NR_INACTIVE_ANON๋„ ํฌํ•จ

    7. refault_distance <= workingset_size ๋ฐ˜ํ™˜ โ€” ์ด ์กฐ๊ฑด์ด true์ด๋ฉด ํ™œ์„ฑํ™” ๋Œ€์ƒ

    workingset_age_nonresident() โ€” ๋น„๊ฑฐ์ฃผ ์—ฐ๋ น ์ฆ๊ฐ€

    // mm/workingset.c:355-371
    void workingset_age_nonresident(struct lruvec *lruvec, unsigned long nr_pages)

    ์—ญํ• : ๋ฉ”๋ชจ๋ฆฌ ๋‚ด LRU๊ฐ€ aging๋  ๋•Œ ๋น„๊ฑฐ์ฃผ ์—”ํŠธ๋ฆฌ๋„ ํ•จ๊ป˜ aging์‹œํ‚จ๋‹ค. memcg ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๋”ฐ๋ผ ์ƒ์œ„ memcg๊นŒ์ง€ ๋ชจ๋‘ ์ฆ๊ฐ€์‹œํ‚จ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

  • do-while ๋ฃจํ”„: parent_lruvec(lruvec)๊ฐ€ NULL์ด ๋  ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณต
  • ๋ชจ๋“  ์ƒ์œ„ memcg์˜ nonresident_age์— nr_pages๋ฅผ ๋”ํ•จ
  • workingset_activation() โ€” accessed folio์˜ ๋น„๊ฑฐ์ฃผ ์—ฐ๋ น ๋ฐ˜์˜

    // mm/workingset.c:589-596
    void workingset_activation(struct folio *folio)
    {
    	if (mem_cgroup_disabled() || folio_memcg_charged(folio))
    		workingset_age_nonresident(folio_lruvec(folio), folio_nr_pages(folio));
    }

    folio_mark_accessed()์—์„œ active ์ „ํ™˜์ด ์ผ์–ด๋‚  ๋•Œ ํ•จ๊ป˜ ํ˜ธ์ถœ๋˜์–ด, LRU ์ด๋™๊ณผ nonresident age ์ฆ๊ฐ€๋ฅผ ๋งž์ถฐ ์ค€๋‹ค.

    workingset_update_node() โ€” shadow ๋…ธ๋“œ LRU ๊ด€๋ฆฌ

    // mm/workingset.c:613-638
    void workingset_update_node(struct xa_node *node)

    ์—ญํ• : xarray ๋…ธ๋“œ๊ฐ€ shadow entry๋งŒ ํฌํ•จํ•˜๋Š”์ง€ ์ถ”์ ํ•˜์—ฌ, ๊ณผ๋„ํ•˜๊ฒŒ ๋ˆ„์ ๋˜๋ฉด shrinker๋กœ ํšŒ์ˆ˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

    ๋ถ„๊ธฐ ๋กœ์ง:

    1. node->count && node->count == node->nr_values โ†’ ๋ชจ๋“  ๊ฐ’์ด shadow entry

    - list_empty(&node->private_list)์ด๋ฉด: list_lru_add_obj()๋กœ shadow ๋…ธ๋“œ LRU์— ์ถ”๊ฐ€

    2. ๊ทธ ์™ธ (์ผ๋ฐ˜ ํŽ˜์ด์ง€ ํฌํ•จ ๋˜๋Š” ํ•ด์ œ ์ค‘):

    - list_empty()๊ฐ€ ์•„๋‹ˆ๋ฉด: list_lru_del_obj()๋กœ LRU์—์„œ ์ œ๊ฑฐ


    ํ˜ธ์ถœ ํ๋ฆ„

    1. folio ์ œ๊ฑฐ ๊ฒฝ๋กœ

    vmscan.c: shrink_folio_list()
    โ””โ”€ workingset_eviction(folio, memcg)
    โ”œโ”€ [MGLRU] lru_gen_eviction(folio)
    โ”‚    โ””โ”€ pack_shadow() โ†’ xa_mk_value()
    โ””โ”€ [CLOCK] atomic_long_read(nonresident_age) โ†’ pack_shadow()
    โ””โ”€ folio->mapping->i_pages[xarray]์— shadow entry ์ €์žฅ

    2. folio refault ๊ฒฝ๋กœ

    mm/filemap.c: filemap_add_folio()
    โ”œโ”€ __filemap_add_folio(..., &shadow)
    โ”œโ”€ shadow && !(gfp & __GFP_WRITE)
    โ”‚    โ””โ”€ workingset_refault(folio, shadow)
    โ”‚         โ”œโ”€ [MGLRU] lru_gen_refault(folio, shadow)
    โ”‚         โ”‚    โ”œโ”€ lru_gen_test_recent() โ†’ abs_diff(max_seq, token) < MAX_NR_GENS
    โ”‚         โ”‚    โ”œโ”€ lrugen->refaulted[hist][type][tier] ์ฆ๊ฐ€
    โ”‚         โ”‚    โ””โ”€ folio_set_workingset() + mod_lruvec_state()
    โ”‚         โ””โ”€ [CLOCK] workingset_test_recent()
    โ”‚              โ”œโ”€ unpack_shadow() โ†’ memcgid, node, eviction
    โ”‚              โ”œโ”€ mem_cgroup_from_private_id() โ†’ eviction ์‹œ์  memcg ์กฐํšŒ
    โ”‚              โ”œโ”€ refault_distance = (refault - eviction) & EVICTION_MASK
    โ”‚              โ”œโ”€ workingset_size = NR_ACTIVE + NR_INACTIVE (+ swap)
    โ”‚              โ””โ”€ refault_distance <= workingset_size ?
    โ”‚                   โ”œโ”€ true: folio_set_active() + workingset_age_nonresident()
    โ”‚                   โ””โ”€ false: ์•„๋ฌด ๋™์ž‘ ์—†์Œ
    โ””โ”€ folio_add_lru(folio)
    mm/swap.c: folio_mark_accessed()
    โ””โ”€ workingset_activation(folio)

    3. shadow ๋…ธ๋“œ ํšŒ์ˆ˜ ๊ฒฝ๋กœ

    kswapd / direct reclaim
    โ””โ”€ scan_shadow_nodes(shrinker, sc)
    โ””โ”€ list_lru_shrink_walk_irq(&shadow_nodes, shadow_lru_isolate)
    โ”œโ”€ xa_trylock(&mapping->i_pages)
    โ”œโ”€ spin_trylock(&mapping->host->i_lock)
    โ”œโ”€ xa_delete_node(node, workingset_update_node)
    โ””โ”€ WORKINGSET_NODERECLAIM ํ†ต๊ณ„ ์ฆ๊ฐ€

    ์กฐ๊ฑด๋ณ„ ๋น„๊ต

    Double CLOCK vs MGLRU workingset ๋™์ž‘

    ํ•ญ๋ชฉDouble CLOCK (`!CONFIG_LRU_GEN`)MGLRU (`CONFIG_LRU_GEN`)
    **์ œ๊ฑฐ ์‹œ**`lruvec->nonresident_age`์—์„œ ์ฝ์Œ`lrugen->min_seq[type]` + `refs`๋ฅผ token์œผ๋กœ ์‚ฌ์šฉ
    **refault ํŒ์ •**`refault_distance <= workingset_size` ์ˆ˜์‹`abs_diff(max_seq, token >> LRU_REFS_WIDTH) < MAX_NR_GENS`
    **์„ธ๋ถ€ ์ถ”์ **์ „์ฒด nonresident_age ์นด์šดํ„ฐ ํ•˜๋‚˜`evicted[hist][type][tier]` 3์ฐจ์› ๋ฐฐ์—ด
    **tier ๋ถ„๋ฅ˜**์—†์Œ (workingset ํ”Œ๋ž˜๊ทธ๋งŒ)`lru_tier_from_refs(refs, workingset)`์œผ๋กœ 4๋‹จ๊ณ„
    **์ตœ๊ทผ ํŒ์ •**refault ๊ฑฐ๋ฆฌ์™€ ๋ฉ”๋ชจ๋ฆฌ ํฌ๊ธฐ ๋น„๊ต์‹œํ€€์Šค ๊ธฐ๋ฐ˜ ์ตœ๊ทผ์„ฑ ๋น„๊ต

    refault ๊ฒฐ๊ณผ๋ณ„ ๋™์ž‘

    ์กฐ๊ฑด๋™์ž‘์„ค๋ช…
    shadow ์—†์Œpass์ด์ „ ์ œ๊ฑฐ ๊ธฐ๋ก ์—†์Œ
    `workingset_test_recent()` = falsepass๋„ˆ๋ฌด ์˜ค๋ž˜์ „ ์ œ๊ฑฐ, ๊ด€๋ จ ์—†์Œ
    recent = true, workingset = false`folio_set_active()`๋น„ํ™œ์„ฑ ๋ชฉ๋ก์—์„œ ํ™œ์„ฑ์œผ๋กœ ์Šน๊ฒฉ
    recent = true, workingset = true`folio_set_active()` + `folio_set_workingset()` + `lru_note_cost_refault()`์ด์ „์— active์˜€๋˜ ํŽ˜์ด์ง€ ๋ณต์›, ๋น„์šฉ ์ฆ๊ฐ€ ๊ธฐ๋ก

    shadow entry ํƒ€์ž„์Šคํƒฌํ”„ ์ •๋ฐ€๋„

    ์กฐ๊ฑด`bucket_order`์„ค๋ช…
    ๋ฉ”๋ชจ๋ฆฌ โ‰ค ํƒ€์ž„์Šคํƒฌํ”„ ๋น„ํŠธ ๋ฒ”์œ„0์ „์ฒด ์ •๋ฐ€๋„ ์œ ์ง€
    ๋ฉ”๋ชจ๋ฆฌ > ํƒ€์ž„์Šคํƒฌํ”„ ๋น„ํŠธ ๋ฒ”์œ„`max_order - timestamp_bits`ํ•˜์œ„ ๋น„ํŠธ ์ž˜๋ผ coarsening

    anon vs file ํŽ˜์ด์ง€์˜ refault distance ๊ณ„์‚ฐ

    ํŽ˜์ด์ง€ ์œ ํ˜•workingset_size์— ํฌํ•จ
    file ํŽ˜์ด์ง€`NR_ACTIVE_FILE` + (swap ์‚ฌ์šฉ ์‹œ `NR_ACTIVE_ANON` + `NR_INACTIVE_ANON`)
    anon ํŽ˜์ด์ง€`NR_ACTIVE_FILE` + `NR_INACTIVE_FILE` + (swap ์‚ฌ์šฉ ์‹œ `NR_ACTIVE_ANON`)

    ๊ด€๋ จ ๋ฌธ์„œ

  • 05-page_reclaim.html โ€” ํŽ˜์ด์ง€ ํšŒ์ˆ˜์—์„œ workingset_eviction/refault ํ˜ธ์ถœ
  • 00-overview.html โ€” ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ์ „์ฒด ๊ฐœ์š”
  • 28-rmap.html โ€” Reverse Mapping๊ณผ ์—ฐ๊ฒฐ๋œ folio ์ถ”์ 
  • 02-slab.html โ€” shadow ๋…ธ๋“œ์˜ list_lru/shrinker์™€ ์—ฐ๋™
  • 14-memcontrol.html โ€” memcg๋ณ„ nonresident_age ๊ด€๋ฆฌ

  • SVG ๋‹ค์ด์–ด๊ทธ๋žจ

    Double CLOCK workingset ํ๋ฆ„

    Double CLOCK workingset ํ๋ฆ„

    MGLRU ์„ธ๋Œ€๋ณ„ workingset ํ๋ฆ„

    MGLRU ์„ธ๋Œ€๋ณ„ workingset ํ๋ฆ„