๏ปฟ# Shrinker (์Šฌ๋ž˜๋ธŒ/์บ์‹œ ํšŒ์ˆ˜ ํ”„๋ ˆ์ž„์›Œํฌ)

๊ด€๋ จ ์†Œ์Šค: mm/shrinker.c, include/linux/shrinker.h, include/linux/list_lru.h

๊ฐœ์š” (Overview)

Shrinker๋Š” ๋ฆฌ๋ˆ…์Šค ์ปค๋„์˜ ๋ฉ”๋ชจ๋ฆฌ ํšŒ์ˆ˜ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ slab ์บ์‹œ, dentry ์บ์‹œ, inode ์บ์‹œ ๋“ฑ ๋‹ค์–‘ํ•œ ์บ์‹œ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์••์ถ•(reclaim)ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ์ƒํ™ฉ์—์„œ kswapd๋‚˜ direct reclaim ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ๋˜๋ฉฐ, ๊ฐ ์บ์‹œ๋Š” count_objects์™€ scan_objects ์ฝœ๋ฐฑ์„ ๋“ฑ๋กํ•˜์—ฌ ์ž์ฒด์ ์ธ ํšŒ์ˆ˜ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

Linux 7.0์—์„œ๋Š” memcg(memory cgroup) ์ธ์‹ shrinker์™€ NUMA ์ธ์‹ shrinker๋ฅผ ์ง€์›ํ•˜๋ฉฐ, RCU ๊ธฐ๋ฐ˜์˜ lockless ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ๋™์‹œ์„ฑ ์„ฑ๋Šฅ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. list_lru ํ”„๋ ˆ์ž„์›Œํฌ์™€ ํ†ตํ•ฉ๋˜์–ด dentry/inode ์บ์‹œ ํšŒ์ˆ˜์— ํ™œ์šฉ๋˜๋ฉฐ, shrink_slab()์ด ์ตœ์ข… ์ง„์ž…์ ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๋„์„œ๊ด€์œผ๋กœ ๋น„์œ ํ•˜๋ฉด shrinker๋Š” ๋ฐ˜๋‚ฉ๋Œ€ ๋’ค์ชฝ์—์„œ ์ฐธ๊ณ ์„œ์™€ ์นด๋“œ๋ชฉ๋ก์„ ์ •๋ฆฌํ•˜๋Š” ์ง์›์— ๊ฐ€๊น์Šต๋‹ˆ๋‹ค. ์„œ๊ฐ€์— ๋‹ค์‹œ ๊ฝ‚์•„๋‘๋ฉด ๋˜๋Š” ์ฑ…์ฒ˜๋Ÿผ ํŒŒ์ผ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋Š” ๋””์Šคํฌ์—์„œ ๋‹ค์‹œ ์ฝ์„ ์ˆ˜ ์žˆ๊ณ , ๊ฐœ์ธ ๋…ธํŠธ์ฒ˜๋Ÿผ ์ต๋ช… ํŽ˜์ด์ง€๋Š” swap์ด ์žˆ์–ด์•ผ ๋‹ค์‹œ ์‚ด๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ•์€ ๋ณดํ†ต kswapd๊ฐ€ WMARK_HIGH/WMARK_LOW/WMARK_MIN ๊ฐ™์€ zone ์›Œํ„ฐ๋งˆํฌ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ •๋ฆฌ๋ฅผ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜, ํ• ๋‹น ๊ฒฝ๋กœ๊ฐ€ ์ง์ ‘ reclaim์œผ๋กœ ๋“ค์–ด๊ฐ€๋ฉด์„œ ๋ˆˆ์— ๋•๋‹ˆ๋‹ค. ์ด๋•Œ shrink_node()๊ฐ€ LRU๋ฅผ ๋จผ์ € ์ •๋ฆฌํ•˜๊ณ , ๊ทธ ๋‹ค์Œ shrink_slab()์ด dentry/inode/slab ๊ฐ™์€ ์บ์‹œ๋ฅผ ๋น„์šฐ๋Š” ์‹์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค. MGLRU๊ฐ€ ์ผœ์ง„ ๊ฒฝ์šฐ์—๋„ ์ด ์บ์‹œ ํšŒ์ˆ˜ ๋‹จ๊ณ„ ์ž์ฒด๋Š” ๊ฐ™์€ ์œ„์น˜์— ๋‚จ์Šต๋‹ˆ๋‹ค.

์†Œ์Šค ํŒŒ์ผ ๊ฒฝ๋กœ:
mm/shrinker.c              โ† ํ•ต์‹ฌ ๊ตฌํ˜„
include/linux/shrinker.h   โ† struct shrinker, shrink_control ์ •์˜
include/linux/list_lru.h   โ† list_lru์™€ shrinker ํ†ตํ•ฉ
include/linux/memcontrol.h โ† memcg๋ณ„ shrinker_info ์ •์˜

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

# ๋“ฑ๋ก๋œ shrinker ๋ชฉ๋ก ํ™•์ธ
ls /sys/kernel/debug/shrinker/

# ํŠน์ • shrinker์˜ ํšŒ์ˆ˜ ํ†ต๊ณ„ ํ™•์ธ
cat /sys/kernel/debug/shrinker/<name>/nr_deferred

# slab ์บ์‹œ ํ˜„ํ™ฉ (SLUB)
cat /proc/slabinfo | head -20

# memcg๋ณ„ slab ์‚ฌ์šฉ๋Ÿ‰
cat /sys/fs/cgroup/memory/<cgroup>/slabinfo

# ๋ฉ”๋ชจ๋ฆฌ ํšŒ์ˆ˜ ๊ด€๋ จ vmstat
grep -E 'slabs_scanned|slabs_freed' /proc/vmstat

# slabtop์œผ๋กœ ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง
slabtop -o -s c

# ํŠน์ • ์บ์‹œ์˜ shrinker ๋™์ž‘ ์ถ”์ 
echo 1 > /sys/kernel/debug/tracing/events/vmscan/mm_shrink_slab_start/enable
cat /sys/kernel/debug/tracing/trace_pipe

# shrinker ๋“ฑ๋ก ํ•ด์ œ ์‹œ RCU ๋Œ€๊ธฐ ํ™•์ธ
cat /proc/meminfo | grep -E 'Slab|SReclaimable|SUnreclaim'

# ๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ• ์ง€ํ‘œ ํ™•์ธ
cat /proc/pressure/memory

# kswapd / direct reclaim ํ†ต๊ณ„ ํ™•์ธ
grep -E 'pgscan_kswapd|pgsteal_kswapd|pgscan_direct|pgsteal_direct|allocstall' /proc/vmstat

# zone ์›Œํ„ฐ๋งˆํฌ ํ™•์ธ
cat /proc/zoneinfo | grep -E 'Node|pages free|min|low|high'

# ํšŒ์ˆ˜ ์ถ”์„ธ๋ฅผ 1์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ณด๊ธฐ
vmstat 1 10

# ์Šฌ๋žฉ ์บ์‹œ์—์„œ dentry / inode ์ƒํƒœ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ณด๊ธฐ
grep -E 'dentry|inode' /proc/slabinfo | head -10

# ํŒŒ์ผ ์บ์‹œ ํšŒ์ˆ˜ ์••๋ ฅ ํ™•์ธ
cat /proc/sys/vm/vfs_cache_pressure

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

struct shrinker (`include/linux/shrinker.h:82-118`)

struct shrinker {
unsigned long (*count_objects)(struct shrinker *,
struct shrink_control *sc);
unsigned long (*scan_objects)(struct shrinker *,
struct shrink_control *sc);

long batch;
int seeks;
unsigned flags;

refcount_t refcount;
struct completion done;
struct rcu_head rcu;

void *private_data;

struct list_head list;
#ifdef CONFIG_MEMCG
int id;
#endif
#ifdef CONFIG_SHRINKER_DEBUG
int debugfs_id;
const char *name;
struct dentry *debugfs_entry;
#endif
atomic_long_t *nr_deferred;
};

๋ฐ˜ํ™˜๊ฐ’ ์ •์˜ (`include/linux/shrinker.h:58-59`)

#define SHRINK_STOP (~0UL)
#define SHRINK_EMPTY (~0UL - 1)

struct shrink_control (`include/linux/shrinker.h:34-56`)

struct shrink_control {
gfp_t gfp_mask;                   /* ํ˜„์žฌ ํ• ๋‹น ์ปจํ…์ŠคํŠธ */
int nid;                           /* NUMA ๋…ธ๋“œ ID */
unsigned long nr_to_scan;          /* ์Šค์บ”ํ•  ์˜ค๋ธŒ์ ํŠธ ์ˆ˜ */
unsigned long nr_scanned;          /* ์‹ค์ œ ์Šค์บ”๋œ ์˜ค๋ธŒ์ ํŠธ ์ˆ˜ */
struct mem_cgroup *memcg;          /* ๋Œ€์ƒ memcg */
};

struct shrinker_info_unit (`include/linux/shrinker.h:16-19`)

/* memcg๋ณ„ shrinker ๋น„ํŠธ๋งต ๋‹จ์œ„ โ€” 64๊ฐœ shrinker๋ฅผ ํ•˜๋‚˜์˜ unit์œผ๋กœ ๊ด€๋ฆฌ */
struct shrinker_info_unit {
atomic_long_t nr_deferred[SHRINKER_UNIT_BITS];  /* shrinker๋ณ„ ๋ฏธ์—ฐ๊ธฐ ์ž‘์—… ์ˆ˜ */
DECLARE_BITMAP(map, SHRINKER_UNIT_BITS);         /* ํšŒ์ˆ˜ ๋Œ€์ƒ shrinker ๋น„ํŠธ๋งต */
};

struct shrinker_info (`include/linux/shrinker.h:21-25`)

/* memcg์˜ ๋…ธ๋“œ๋ณ„ shrinker ๊ด€๋ฆฌ ์ •๋ณด */
struct shrinker_info {
struct rcu_head rcu;
int map_nr_max;                         /* ๋“ฑ๋ก๋œ shrinker ์ตœ๋Œ€ ์ˆ˜ */
struct shrinker_info_unit *unit[];       /* ์œ ์—ฐํ•œ ๋ฐฐ์—ด (flexible array) */
};

ํ”Œ๋ž˜๊ทธ ์ •์˜ (`include/linux/shrinker.h:121-132`)

#define SHRINKER_REGISTERED   BIT(0)   /* shrinker_list์— ๋“ฑ๋ก๋จ */
#define SHRINKER_ALLOCATED    BIT(1)   /* shrinker_alloc()์œผ๋กœ ํ• ๋‹น๋จ */
#define SHRINKER_NUMA_AWARE   BIT(2)   /* NUMA๋ณ„ ๊ฐœ๋ณ„ ํšŒ์ˆ˜ */
#define SHRINKER_MEMCG_AWARE  BIT(3)   /* memcg๋ณ„ ๊ฐœ๋ณ„ ํšŒ์ˆ˜ */
#define SHRINKER_NONSLAB      BIT(4)   /* slab์ด ์•„๋‹Œ non-slab shrinker */

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

1. `do_shrink_slab()` โ€” ํ•ต์‹ฌ ํšŒ์ˆ˜ ์—”์ง„ (`mm/shrinker.c:371-466`)

static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
struct shrinker *shrinker, int priority)
{
unsigned long freed = 0;
unsigned long long delta;
long total_scan;
long freeable;
long nr;
long new_nr;
long batch_size = shrinker->batch ? shrinker->batch
: SHRINK_BATCH;
long scanned = 0, next_deferred;

freeable = shrinker->count_objects(shrinker, shrinkctl);
if (freeable == 0 || freeable == SHRINK_EMPTY)
return freeable;

nr = xchg_nr_deferred(shrinker, shrinkctl);

if (shrinker->seeks) {
delta = freeable >> priority;
delta *= 4;
do_div(delta, shrinker->seeks);
} else {
delta = freeable / 2;
}

total_scan = nr >> priority;
total_scan += delta;
total_scan = min(total_scan, (2 * freeable));

trace_mm_shrink_slab_start(shrinker, shrinkctl, nr,
freeable, delta, total_scan, priority);

while (total_scan >= batch_size ||
total_scan >= freeable) {
unsigned long ret;
unsigned long nr_to_scan = min(batch_size, total_scan);

shrinkctl->nr_to_scan = nr_to_scan;
shrinkctl->nr_scanned = nr_to_scan;
ret = shrinker->scan_objects(shrinker, shrinkctl);
if (ret == SHRINK_STOP)
break;
freed += ret;
count_vm_events(SLABS_SCANNED, shrinkctl->nr_scanned);
total_scan -= shrinkctl->nr_scanned;
scanned += shrinkctl->nr_scanned;

cond_resched();
}

next_deferred = max_t(long, (nr + delta - scanned), 0);
next_deferred = min(next_deferred, (2 * freeable));

new_nr = add_nr_deferred(next_deferred, shrinker, shrinkctl);

trace_mm_shrink_slab_end(shrinker, shrinkctl->nid, freed, nr, new_nr, total_scan);
return freed;
}

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

  • count_objects()๊ฐ€ SHRINK_EMPTY ๋ฐ˜ํ™˜ โ†’ ์ฆ‰์‹œ ์ข…๋ฃŒ (์˜ค๋ธŒ์ ํŠธ ์—†์Œ)
  • seeks > 0 โ†’ freeable >> priority * 4 / seeks ๊ณ„์‚ฐ (IO ์บ์‹œ ๋ณดํ˜ธ)
  • seeks == 0 โ†’ freeable / 2 (IO ๋ถˆํ•„์š” ์บ์‹œ๋Š” ๊ณต๊ฒฉ์  ํšŒ์ˆ˜)
  • scan_objects()๊ฐ€ SHRINK_STOP ๋ฐ˜ํ™˜ โ†’ ํ˜„์žฌ ํšŒ์ˆ˜ ์ปจํ…์ŠคํŠธ ์ค‘๋‹จ
  • total_scan < batch_size && total_scan < freeable โ†’ while ๋ฃจํ”„ ํƒˆ์ถœ
  • 2. `shrink_slab()` โ€” ์ตœ์ข… ์ง„์ž…์  (`mm/shrinker.c:614-676`)

    unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
    	                          int priority)
    {
    	unsigned long ret, freed = 0;
    	struct shrinker *shrinker;
    
    	/* memcg ๋น„-root์ด๋ฉด memcg ์ „์šฉ ๊ฒฝ๋กœ๋กœ ๋ถ„๊ธฐํ•œ๋‹ค. */
    	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
    		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
    
    	/* ๊ธ€๋กœ๋ฒŒ shrink๋Š” RCU ๋ณดํ˜ธ ์•„๋ž˜ shrinker_list๋ฅผ ์ˆœํšŒํ•œ๋‹ค. */
    	rcu_read_lock();
    	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
    		struct shrink_control sc = {
    			.gfp_mask = gfp_mask,
    			.nid = nid,
    			.memcg = memcg,
    		};
    
    		if (!shrinker_try_get(shrinker))
    			continue;
    
    		rcu_read_unlock();
    
    		ret = do_shrink_slab(&sc, shrinker, priority);
    		if (ret == SHRINK_EMPTY)
    			ret = 0;
    		freed += ret;
    
    		rcu_read_lock();
    		shrinker_put(shrinker);
    	}
    
    	rcu_read_unlock();
    	cond_resched();
    	return freed;
    }

    3. `shrink_slab_memcg()` โ€” memcg ์ „์šฉ ํšŒ์ˆ˜ (`mm/shrinker.c:469-585`)

    memcg๋ณ„ ๋น„ํŠธ๋งต(shrinker_info)์„ ์ˆœํšŒํ•˜๋ฉฐ do_shrink_slab()์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. SHRINK_EMPTY ๋ฐœ์ƒ ์‹œ ์ด์ค‘ ๊ฒ€์‚ฌ(double-check) ๋กœ์ง์œผ๋กœ ๊ฒฝํ•ฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

    4. `shrinker_alloc()` โ€” shrinker ๋™์  ํ• ๋‹น (`mm/shrinker.c:678-736`)

    SHRINKER_MEMCG_AWARE ํ”Œ๋ž˜๊ทธ ์‹œ shrinker_memcg_alloc()์œผ๋กœ IDR ํ• ๋‹น, ๋ฏธ์‹œ์ž‘ ์‹œ fallback์œผ๋กœ non-memcg ๊ฒฝ๋กœ ์ˆ˜ํ–‰.

    5. `shrinker_register()` โ€” shrinker ๋“ฑ๋ก (`mm/shrinker.c:738-759`)

    shrinker_list์— ์ถ”๊ฐ€ํ•˜๊ณ  debugfs ๋“ฑ๋ก, ์ดˆ๊ธฐ refcount=1 ์„ค์ •.

    6. `shrinker_free()` โ€” shrinker ํ•ด์ œ (`mm/shrinker.c:769-811`)

    ์ดˆ๊ธฐ refcount ํ•ด์ œ โ†’ completion ๋Œ€๊ธฐ โ†’ list ์ œ๊ฑฐ โ†’ RCU ์ฝœ๋ฐฑ์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ.


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

    ๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ• ๋ฐœ์ƒ
    โ”‚
    โ”œโ”€ kswapd() โ†’ balance_pgdat() [mm/vmscan.c:6950-7056]
    โ”‚     โ””โ”€ shrink_node() [mm/vmscan.c:6039-6105]
    โ”‚          โ”œโ”€ lru_gen_shrink_node() [MGLRU ์ผœ์ง]
    โ”‚          โ””โ”€ shrink_lruvec() [mm/vmscan.c:5772-5788]
    โ”‚               โ””โ”€ shrink_slab()
    โ”‚
    โ””โ”€ direct reclaim โ†’ try_to_free_pages() [mm/vmscan.c:6566-6606]
    โ””โ”€ do_try_to_free_pages()
    โ””โ”€ shrink_node()
    โ””โ”€ shrink_lruvec()
    โ””โ”€ shrink_slab()

    ๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ•์ด ๋“ค์–ด์˜ค๋ฉด ๋จผ์ € LRU ํŽ˜์ด์ง€ ํšŒ์ˆ˜๊ฐ€ ์‹œ์ž‘๋˜๊ณ , ํŒŒ์ผ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€์™€ ์ต๋ช… ํŽ˜์ด์ง€์˜ ๋น„์ค‘์„ ์กฐ์ •ํ•œ ๋’ค, ์Šฌ๋ž˜๋ธŒ ๊ณ„์—ด ์บ์‹œ๋Š” ๊ทธ ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. shrink_slab()์€ ์ด ์ „์ฒด ํšŒ์ˆ˜ ํ๋ฆ„์˜ ๋งˆ์ง€๋ง‰ ์บ์‹œ ์ •๋ฆฌ ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.

    shrink_lruvec()๋Š” get_scan_count()๋กœ anon/file ๋น„์ค‘์„ ๋‚˜๋ˆ„๊ณ , inactive list๋ถ€ํ„ฐ active list๋กœ ์••๋ ฅ์„ ์˜ฎ๊ธฐ๋ฉด์„œ ํšŒ์ˆ˜๋Ÿ‰์„ ๋งž์ถฅ๋‹ˆ๋‹ค.

    ๋ฉ”๋ชจ๋ฆฌ ํšŒ์ˆ˜ ํŠธ๋ฆฌ๊ฑฐ (kswapd / direct reclaim / drop_slab)
    โ”‚
    โ”œโ”€ shrink_slab(gfp_mask, nid, memcg, priority)       [mm/shrinker.c:614]
    โ”‚     โ”‚
    โ”‚     โ”œโ”€ [memcg ๋น„-root] โ†’ shrink_slab_memcg()        [mm/shrinker.c:469]
    โ”‚     โ”‚     โ”‚
    โ”‚     โ”‚     โ”œโ”€ rcu_read_lock() + shrinker_info ํš๋“
    โ”‚     โ”‚     โ”œโ”€ for_each_set_bit(offset, unit->map)
    โ”‚     โ”‚     โ”‚     โ”‚
    โ”‚     โ”‚     โ”‚     โ”œโ”€ idr_find(&shrinker_idr, shrinker_id)
    โ”‚     โ”‚     โ”‚     โ”œโ”€ shrinker_try_get(shrinker)
    โ”‚     โ”‚     โ”‚     โ””โ”€ do_shrink_slab()                 [mm/shrinker.c:371]
    โ”‚     โ”‚     โ”‚           โ”‚
    โ”‚     โ”‚     โ”‚           โ”œโ”€ count_objects() โ† ์‚ฌ์šฉ์ž ์ฝœ๋ฐฑ
    โ”‚     โ”‚     โ”‚           โ”œโ”€ xchg_nr_deferred()
    โ”‚     โ”‚     โ”‚           โ”œโ”€ total_scan ๊ณ„์‚ฐ (priority ๊ธฐ๋ฐ˜)
    โ”‚     โ”‚     โ”‚           โ”œโ”€ while (total_scan >= batch_size)
    โ”‚     โ”‚     โ”‚           โ”‚     โ””โ”€ scan_objects() โ† ์‚ฌ์šฉ์ž ์ฝœ๋ฐฑ
    โ”‚     โ”‚     โ”‚           โ””โ”€ add_nr_deferred() (์ด์›”)
    โ”‚     โ”‚     โ”‚
    โ”‚     โ”‚     โ””โ”€ [SHRINK_EMPTY ์‹œ ์ด์ค‘ ๊ฒ€์‚ฌ]
    โ”‚     โ”‚           โ”œโ”€ clear_bit() + smp_mb__after_atomic()
    โ”‚     โ”‚           โ”œโ”€ do_shrink_slab() ์žฌํ˜ธ์ถœ
    โ”‚     โ”‚           โ””โ”€ ๋น„์–ด์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ, ์•„๋‹ˆ๋ฉด set_shrinker_bit()
    โ”‚     โ”‚
    โ”‚     โ””โ”€ [root memcg] โ†’ ๊ธ€๋กœ๋ฒŒ shrink ๊ฒฝ๋กœ
    โ”‚           โ”œโ”€ rcu_read_lock()
    โ”‚           โ”œโ”€ list_for_each_entry_rcu(shrinker_list)
    โ”‚           โ”‚     โ”œโ”€ shrinker_try_get()
    โ”‚           โ”‚     โ”œโ”€ do_shrink_slab()
    โ”‚           โ”‚     โ””โ”€ shrinker_put()
    โ”‚           โ””โ”€ rcu_read_unlock()
    โ”‚
    โ””โ”€ ๋ฐ˜ํ™˜: ํšŒ์ˆ˜๋œ ์˜ค๋ธŒ์ ํŠธ ์ˆ˜

    shrinker ๋“ฑ๋ก/ํ•ด์ œ ํ๋ฆ„:

    shrinker_alloc(flags, fmt, ...)    [mm/shrinker.c:678]
    โ”œโ”€ [SHRINKER_MEMCG_AWARE] โ†’ shrinker_memcg_alloc()
    โ”‚     โ””โ”€ idr_alloc() + expand_shrinker_info()
    โ””โ”€ [non-memcg] โ†’ nr_deferred ๋ฐฐ์—ด ํ• ๋‹น
    
    shrinker_register(shrinker)        [mm/shrinker.c:738]
    โ”œโ”€ list_add_tail_rcu(shrinker_list)
    โ”œโ”€ SHRINKER_REGISTERED ํ”Œ๋ž˜๊ทธ ์„ค์ •
    โ”œโ”€ shrinker_debugfs_add()
    โ””โ”€ refcount_set(1)
    
    shrinker_free(shrinker)            [mm/shrinker.c:769]
    โ”œโ”€ shrinker_put() โ†’ refcount--
    โ”œโ”€ wait_for_completion(done)
    โ”œโ”€ list_del_rcu() + shrinker_debugfs_detach()
    โ””โ”€ call_rcu() โ†’ shrinker_free_rcu_cb() โ†’ kfree()

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

    ๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ• ์ง„์ž…์  ๋น„๊ต

    ์ง„์ž…์ ์‹œ์ž‘ ์กฐ๊ฑด์ฃผ๋œ ํ๋ฆ„ํŠน์ง•
    kswapd์›Œํ„ฐ๋งˆํฌ ๋„๋‹ฌ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํšŒ์ˆ˜ ํ•„์š”`balance_pgdat()` โ†’ `shrink_node()` โ†’ `shrink_slab()`๋‹ค๋ฅธ ์ž‘์—…์„ ๋ง‰์ง€ ์•Š๋„๋ก ๋น„๋™๊ธฐ์ ์œผ๋กœ ์›€์ง์ž„
    direct reclaimํ• ๋‹น ๊ฒฝ๋กœ์—์„œ ์ฆ‰์‹œ ํšŒ์ˆ˜ ํ•„์š”`try_to_free_pages()` โ†’ `shrink_node()` โ†’ `shrink_slab()`ํ˜„์žฌ ํ˜ธ์ถœ์ž ์Šค๋ ˆ๋“œ๊ฐ€ ์ง์ ‘ ๊ธฐ๋‹ค๋ฆผ
    drop_caches๊ด€๋ฆฌ์ž๊ฐ€ ์ˆ˜๋™ ํ•ด์ œ`drop_slab()` ๊ณ„์—ด๋กœ ์บ์‹œ ์ •๋ฆฌํŒŒ์ผ์‹œ์Šคํ…œ ์บ์‹œ๋ฅผ ๊ฐ•์ œ๋กœ ์ค„์ผ ๋•Œ ์‚ฌ์šฉ

    ํšŒ์ˆ˜ ๋Œ€์ƒ๋ณ„ ์ฐจ์ด

    ๋Œ€์ƒ๋จผ์ € ์ค„์–ด๋“œ๋Š”๊ฐ€๋‹ค์‹œ ์ฑ„์šฐ๋Š” ๋ฐฉ๋ฒ•shrinker์™€์˜ ๊ด€๊ณ„
    ํŒŒ์ผ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๊ทธ๋ ‡๋‹คํŒŒ์ผ์—์„œ ๋‹ค์‹œ ์ฝ์ŒLRU ํšŒ์ˆ˜์˜ ์ฃผ์š” ๋Œ€์ƒ
    ์ต๋ช… ํŽ˜์ด์ง€์•„๋‹ˆ๋‹คswap์—์„œ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ดswap์ด ์žˆ์–ด์•ผ ํšŒ์ˆ˜ ๊ฐ€๋Šฅ
    dentry / inode / slab ์บ์‹œ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„ํ•„์š” ์‹œ ์žฌ์ƒ์„ฑ`count_objects()` / `scan_objects()`๋กœ ํšŒ์ˆ˜

    Shrinker ํ”Œ๋ž˜๊ทธ๋ณ„ ๋™์ž‘

    ํ”Œ๋ž˜๊ทธ๋™์ž‘์‚ฌ์šฉ ์‹œ์ 
    `SHRINKER_NUMA_AWARE`NUMA ๋…ธ๋“œ๋ณ„ `nr_deferred` ๋ถ„๋ฆฌํŒŒ์ผ ์‹œ์Šคํ…œ inode ์บ์‹œ ๋“ฑ
    `SHRINKER_MEMCG_AWARE`memcg๋ณ„ `shrinker_info` ๋น„ํŠธ๋งต ๊ด€๋ฆฌ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ slab ์บ์‹œ
    `SHRINKER_NONSLAB`memcg offlines์—์„œ๋„ ํšŒ์ˆ˜ ํ—ˆ์šฉnon-slab ์บ์‹œ (DAMON ๋“ฑ)
    `SHRINKER_REGISTERED``shrinker_list`์— ๋“ฑ๋ก๋จ๋‚ด๋ถ€ ํ”Œ๋ž˜๊ทธ
    `SHRINKER_ALLOCATED``shrinker_alloc()`์œผ๋กœ ํ• ๋‹น๋จ๋‚ด๋ถ€ ํ”Œ๋ž˜๊ทธ

    ํšŒ์ˆ˜ ๊ฒฝ๋กœ ๋น„๊ต

    ๊ฒฝ๋กœ์ง„์ž… ์กฐ๊ฑด์ˆœํšŒ ๋Œ€์ƒ๋™์‹œ์„ฑ ์ œ์–ด
    ๊ธ€๋กœ๋ฒŒ shrinkroot memcg`shrinker_list` ์ „์ฒดRCU + refcount
    memcg shrink๋น„-root memcgmemcg๋ณ„ `shrinker_info` ๋น„ํŠธ๋งตRCU + ๋น„ํŠธ๋งต
    drop_slab`/proc/sys/vm/drop_caches`๋ชจ๋“  memcg ์ˆœํšŒ๋ณ„๋„ lock ๋ถˆํ•„์š”

    count_objects ๋ฐ˜ํ™˜๊ฐ’ ์ฒ˜๋ฆฌ

    ๋ฐ˜ํ™˜๊ฐ’์˜๋ฏธํ›„์† ๋™์ž‘
    `0`ํšŒ์ˆ˜ ๋ถˆ๊ฐ€ (์•Œ ์ˆ˜ ์—†์Œ)์Šค์บ” ์Šคํ‚ต
    `SHRINK_EMPTY`์˜ค๋ธŒ์ ํŠธ 0๊ฐœ (ํ™•์‹ค)์ฆ‰์‹œ ์ข…๋ฃŒ, memcg ์‹œ ๋น„ํŠธ๋งต ํด๋ฆฌ์–ด
    ์–‘์ˆ˜ํšŒ์ˆ˜ ๊ฐ€๋Šฅ ์˜ค๋ธŒ์ ํŠธ ์ˆ˜total_scan ๊ณ„์‚ฐ ํ›„ scan_objects ํ˜ธ์ถœ

    ๊ด€๋ จ ๋ฌธ์„œ

  • ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ฐœ์š”
  • ํŽ˜์ด์ง€ ํšŒ์ˆ˜
  • SLUB ํ• ๋‹น์ž
  • Memory Cgroup
  • List LRU
  • vmpressure

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

    Shrinker ํšŒ์ˆ˜ ํ˜ธ์ถœ ํ๋ฆ„

    Shrinker ํšŒ์ˆ˜ ํ˜ธ์ถœ ํ๋ฆ„

    Shrinker ์ž๋ฃŒ๊ตฌ์กฐ ๊ด€๊ณ„๋„

    Shrinker ์ž๋ฃŒ๊ตฌ์กฐ ๊ด€๊ณ„๋„