Memory Cgroup (memcg)

๊ฐœ์š” (Overview)

Memory Cgroup๋Š” cgroup v2์˜ ๋ฉ”๋ชจ๋ฆฌ ์ปจํŠธ๋กค๋Ÿฌ๋กœ, ํ”„๋กœ์„ธ์Šค ๊ทธ๋ฃน๋ณ„ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ๊ณ„์ •(charge), ์ œํ•œ(limit), ํšŒ์ˆ˜(reclaim)ํ•˜๋Š” ์ปค๋„ ์„œ๋ธŒ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ(Docker, Kubernetes)์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๊ฒฉ๋ฆฌ(Isolation)์˜ ํ•ต์‹ฌ ๋„๊ตฌ์ด๋ฉฐ, memory.max(ํ•˜๋“œ ์ œํ•œ), memory.high(์†Œํ”„ํŠธ ์ œํ•œ/throttle), memory.min/memory.low(๋ณดํ˜ธ ์ˆ˜์ค€)๋ฅผ ํ†ตํ•ด ์œ ์—ฐํ•œ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ฐ memcg๋Š” ๋…๋ฆฝ์ ์ธ LRU ๋ฆฌ์ŠคํŠธ, ํšŒ์ˆ˜ ์ •์ฑ…, OOM Killer ๋ฒ”์œ„๋ฅผ ๊ฐ€์ง€๋ฉฐ, cgroup ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด ๋ถ€๋ชจ-์ž์‹ ๊ฐ„ ์ž์› ๋ฐฐ๋ถ„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ปค๋„ ๋‚ด๋ถ€์—์„œ memcg๋Š” ํŽ˜์ด์ง€ ํ• ๋‹น ์‹œ page_counter๋ฅผ ํ†ตํ•ด ๊ณ„์ •์ด ์ด๋ฃจ์–ด์ง€๋ฉฐ, ํ•œ๊ณ„ ์ดˆ๊ณผ ์‹œ ์ง์ ‘ ํšŒ์ˆ˜(direct reclaim) ๋˜๋Š” ์œ ์ € ๋ชจ๋“œ ๋ณต๊ท€ ์‹œ throttle(__mem_cgroup_handle_over_high)์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. cgroup v1๊ณผ v2 ๋ชจ๋‘ ์ง€์›ํ•˜๋ฉฐ, v1์—์„œ๋Š” memory.limit_in_bytes ๋“ฑ ๋ ˆ๊ฑฐ์‹œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ, v2์—์„œ๋Š” memory.max/memory.high/memory.low/memory.min์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ผ์ƒ ๋น„์œ ๋กœ ๋ณด๋ฉด memcg๋Š” ๋Œ€ํ˜• ๊ฑด๋ฌผ์˜ ์ธต๋ณ„ ์ „๋ ฅ ๊ณ„๋Ÿ‰๊ธฐ์™€ ์ฐจ๋‹จ๊ธฐ์— ๊ฐ€๊น์Šต๋‹ˆ๋‹ค. ๊ฐ ์ธต์€ ์ž๊ธฐ ๊ณ„๋Ÿ‰๊ธฐ(memory.current)๋กœ ์‚ฌ์šฉ๋Ÿ‰์„ ๋ณด๊ณ , ๊ด€๋ฆฌ์ž๋Š” ์ ˆ๋Œ€ ๋ณด์žฅ๋Ÿ‰(memory.min), ๊ฐ€๋Šฅํ•˜๋ฉด ์ง€์ผœ ์ค„ ๋ณดํ˜ธ๋Ÿ‰(memory.low), ๊ณผ์—ด์„ ๋ง‰๋Š” ๊ฒฝ๊ณ ์„ (memory.high), ๋„˜์œผ๋ฉด ์ฐจ๋‹จ๋˜๋Š” ์ตœ๋Œ€์น˜(memory.max)๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ๋Š” ๋ณ„๋„ ๋ฌผ๋ฆฌ ๋ฉ”๋ชจ๋ฆฌ๋งต์„ ๊ฐ–๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ˜ธ์ŠคํŠธ ์ปค๋„์˜ ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ๋งต์„ ๊ณต์œ ํ•˜๋ฉด์„œ ์ด ๊ณ„๋Ÿ‰๊ธฐ์™€ ์ฐจ๋‹จ๊ธฐ๋งŒ ๋”ฐ๋กœ ์ ์šฉ๋ฐ›์Šต๋‹ˆ๋‹ค.

// ์†Œ์Šค ํŒŒ์ผ ๊ฒฝ๋กœ
mm/memcontrol.c           // ๋ฉ”๋ชจ๋ฆฌ ์ปจํŠธ๋กค๋Ÿฌ ํ•ต์‹ฌ ๊ตฌํ˜„ (5679์ค„)
mm/memcontrol-v1.c        // cgroup v1 ๋ ˆ๊ฑฐ์‹œ ์ธํ„ฐํŽ˜์ด์Šค (2243์ค„)
include/linux/memcontrol.h // ํ•ต์‹ฌ ๊ตฌ์กฐ์ฒด ์ •์˜ (1939์ค„)
include/linux/page_counter.h // ํŽ˜์ด์ง€ ์นด์šดํ„ฐ ๊ธฐ๋ฐ˜ ๊ณ„์ •
Memory Cgroup ํ˜ธ์ถœ ํ๋ฆ„
Memory Cgroup ์ž๋ฃŒ๊ตฌ์กฐ ๊ด€๊ณ„๋„

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

# 1. ๋ฉ”๋ชจ๋ฆฌ cgroup ์ „์ฒด ํ˜„ํ™ฉ
cat /proc/cgroups | grep memory

# 2. ํ˜„์žฌ ๋ฉ”๋ชจ๋ฆฌ cgroup ๊ณ„์ธต ๊ตฌ์กฐ (cgroup v2)
ls /sys/fs/cgroup/
cat /sys/fs/cgroup/memory.current       # ํ˜„์žฌ ์‚ฌ์šฉ๋Ÿ‰ (๋ฐ”์ดํŠธ)
cat /sys/fs/cgroup/memory.max           # ํ•˜๋“œ ์ œํ•œ
cat /sys/fs/cgroup/memory.high          # ์†Œํ”„ํŠธ ์ œํ•œ
cat /sys/fs/cgroup/memory.stat          # ์ƒ์„ธ ํ†ต๊ณ„

# 3. ํ”„๋กœ์„ธ์Šค์˜ memcg ์†Œ์† ํ™•์ธ
cat /proc/$$/cgroup                      # ํ˜„์žฌ ์…ธ์˜ cgroup ๊ฒฝ๋กœ
cat /proc/$$/status | grep -i "voluntary_ctxt\|nonvoluntary_ctxt"

# 4. ๋ฉ”๋ชจ๋ฆฌ ์ด๋ฒคํŠธ ํ™•์ธ (OOM, high ๋“ฑ)
cat /sys/fs/cgroup/memory.events
# oom: memcg OOM ๋ฐœ์ƒ ํšŸ์ˆ˜
# oom_kill: OOM killer์— ์˜ํ•ด ์ข…๋ฃŒ๋œ ํ”„๋กœ์„ธ์Šค ์ˆ˜
# high: memory.high ์ดˆ๊ณผ๋กœ ํšŒ์ˆ˜ ์‹œ๋„๋œ ํšŸ์ˆ˜

# 5. memcg๋ณ„ ๋ฉ”๋ชจ๋ฆฌ ์••๋ ฅ ํ™•์ธ (PSI)
cat /proc/pressure/memory
cat /sys/fs/cgroup/memory.pressure       # cgroup๋ณ„ PSI (์ปค๋„ 6.1+)

# 6. ๋ฉ”๋ชจ๋ฆฌ cgroup ํ†ต๊ณ„ (v1 ๋ ˆ๊ฑฐ์‹œ)
cat /sys/fs/cgroup/memory/memory.stat 2>/dev/null | head -20

# 7. memcg OOM ๊ทธ๋ฃน ํ‚ฌ ์„ค์ • ํ™•์ธ
cat /sys/fs/cgroup/memory.oom.group     # 0 ๋˜๋Š” 1

# 8. ์Šค์™‘ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ
cat /sys/fs/cgroup/memory.swap.current  # ํ˜„์žฌ ์Šค์™‘ ์‚ฌ์šฉ๋Ÿ‰
cat /sys/fs/cgroup/memory.swap.max      # ์Šค์™‘ ํ•˜๋“œ ์ œํ•œ

# 9. cgroup v2 ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ ํ…Œ์ŠคํŠธ์šฉ ๊ทธ๋ฃน ์ƒ์„ฑ
sudo mkdir -p /sys/fs/cgroup/myapp
echo 512M | sudo tee /sys/fs/cgroup/myapp/memory.max
echo 400M | sudo tee /sys/fs/cgroup/myapp/memory.high
echo 256M | sudo tee /sys/fs/cgroup/myapp/memory.low
echo 128M | sudo tee /sys/fs/cgroup/myapp/memory.min

# 10. ํ”„๋กœ์„ธ์Šค๋ฅผ ํ…Œ์ŠคํŠธ ๊ทธ๋ฃน์— ์—ฐ๊ฒฐ
# PID์—๋Š” ๊ด€์ฐฐํ•  ๋Œ€์ƒ ํ”„๋กœ์„ธ์Šค ๋ฒˆํ˜ธ๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค.
PID=12345
echo $PID | sudo tee /sys/fs/cgroup/myapp/cgroup.procs
cat /sys/fs/cgroup/myapp/memory.current
cat /sys/fs/cgroup/myapp/memory.stat | head -30
cat /sys/fs/cgroup/myapp/memory.events

# 11. ๊ณ„์ธต ์ „์ฒด์˜ ํ•ต์‹ฌ ๋ฉ”๋ชจ๋ฆฌ ํŒŒ์ผ ํ›‘๊ธฐ
grep -R . /sys/fs/cgroup/memory.{current,max,high,low,min,events,numa_stat,oom.group} 2>/dev/null

# 12. cgroup v2 ์ปจํŠธ๋กค๋Ÿฌ ํ™œ์„ฑํ™” ์ƒํƒœ ํ™•์ธ
cat /sys/fs/cgroup/cgroup.controllers
cat /sys/fs/cgroup/cgroup.subtree_control

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

struct mem_cgroup

๋ฉ”๋ชจ๋ฆฌ ์ปจํŠธ๋กค๋Ÿฌ์˜ ํ•ต์‹ฌ ๊ตฌ์กฐ์ฒด. ๊ฐ cgroup์— ํ•˜๋‚˜์”ฉ ์ƒ์„ฑ๋˜๋ฉฐ, ํŽ˜์ด์ง€ ๊ณ„์ •, ํ•œ๊ณ„ ์„ค์ •, ํ†ต๊ณ„, ํšŒ์ˆ˜ ์ดํ„ฐ๋ ˆ์ดํ„ฐ ๋“ฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

// include/linux/memcontrol.h:190-325
struct mem_cgroup {
struct cgroup_subsys_state css;

/* memcg ๋น„๊ณต๊ฐœ ID. cgroup๋ณด๋‹ค ์˜ค๋ž˜ ์‚ฌ๋Š” ์˜ค๋ธŒ์ ํŠธ ์‹๋ณ„์— ์‚ฌ์šฉ */
struct mem_cgroup_private_id id;

/* ๊ณ„์ • ๋Œ€์ƒ ์ž์› */
struct page_counter memory;         /* v1๊ณผ v2 ๋ชจ๋‘ ์‚ฌ์šฉ */

union {
struct page_counter swap;       /* v2 ์ „์šฉ */
struct page_counter memsw;      /* v1 ์ „์šฉ */
};

/* ๋“ฑ๋ก๋œ ๋กœ์ปฌ ํ”ผํฌ ๊ฐ์‹œ์ž */
struct list_head memory_peaks;
struct list_head swap_peaks;
spinlock_t       peaks_lock;

/* ์ธํ„ฐ๋ŸฝํŠธ ๊ณ„์ •์˜ ๋ฒ”์œ„ ๊ฐ•์ œ */
struct work_struct high_work;

#ifdef CONFIG_ZSWAP
unsigned long zswap_max;

/* ์ด memcg์˜ ํŽ˜์ด์ง€๊ฐ€ zswap์—์„œ swap์œผ๋กœ ๋˜๋Œ๋ ค ์“ฐ์ด๋Š” ๊ฒƒ์„ ์ œ์–ด */
bool zswap_writeback;
#endif /* CONFIG_ZSWAP */

/* vmpressure ์•Œ๋ฆผ */
struct vmpressure vmpressure;

/* OOM killer๊ฐ€ ํ•œ ํƒœ์Šคํฌ๋ฅผ ์ฃฝ์ผ ๋•Œ ์†Œ์† ํƒœ์Šคํฌ ์ „์ฒด๋ฅผ ์ฃฝ์ผ์ง€ ์—ฌ๋ถ€ */
bool oom_group;

int swappiness;

/* memory.events์™€ memory.events.local */
struct cgroup_file events_file;
struct cgroup_file events_local_file;

/* memory.swap.events ํ•ธ๋“ค */
struct cgroup_file swap_events_file;

/* memory.stat */
struct memcg_vmstats    *vmstats;

/* memory.events */
atomic_long_t      memory_events[MEMCG_NR_MEMORY_EVENTS];
atomic_long_t      memory_events_local[MEMCG_NR_MEMORY_EVENTS];

#ifdef CONFIG_MEMCG_NMI_SAFETY_REQUIRES_ATOMIC
/* NMI ์ปจํ…์ŠคํŠธ์˜ MEMCG_KMEM */
atomic_t        kmem_stat;
#endif /* CONFIG_MEMCG_NMI_SAFETY_REQUIRES_ATOMIC */
/*
* ์†Œ์ผ“ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ํšŒ์ˆ˜ ์••๋ ฅ ํžŒํŠธ. ์†Œ์ผ“ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ณ„๋„
* ๊ณ„์ •/์ถฉ์ „ํ•˜๋Š” ๋ ˆ๊ฑฐ์‹œ cgroup ๋ชจ๋“œ์—์„œ๋Š” ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.
*/
u64         socket_pressure;
#if BITS_PER_LONG < 64
seqlock_t       socket_pressure_seqlock;
#endif
int kmemcg_id;
/*
* objcg reparenting ๊ณผ์ •์—์„œ memcg->objcg๋Š” ์ง€์›Œ์ง„๋‹ค.
* memcg->orig_objcg๋Š” memcg ์ƒ์• ๊ฐ€ ๋๋‚  ๋•Œ๊นŒ์ง€ ์›๋ž˜ objcg ํฌ์ธํ„ฐ์™€
* ์ฐธ์กฐ๋ฅผ ๋ณด์กดํ•œ๋‹ค.
*/
struct obj_cgroup __rcu *objcg;
struct obj_cgroup   *orig_objcg;
/* objcg_lock์œผ๋กœ ๋ณดํ˜ธ๋˜๋Š” ์ƒ์† objcg ๋ชฉ๋ก */
struct list_head objcg_list;

struct memcg_vmstats_percpu __percpu *vmstats_percpu;

#ifdef CONFIG_CGROUP_WRITEBACK
struct list_head cgwb_list;
struct wb_domain cgwb_domain;
struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT];
#endif /* CONFIG_CGROUP_WRITEBACK */

#ifdef CONFIG_TRANSPARENT_HUGEPAGE
struct deferred_split deferred_split_queue;
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */

#ifdef CONFIG_LRU_GEN_WALKS_MMU
/* memcg๋ณ„ mm_struct ๋ชฉ๋ก */
struct lru_gen_mm_list mm_list;
#endif /* CONFIG_LRU_GEN_WALKS_MMU */

#ifdef CONFIG_MEMCG_V1
/* ๋ ˆ๊ฑฐ์‹œ ์†Œ๋น„์ž ์ง€ํ–ฅ ์นด์šดํ„ฐ */
struct page_counter kmem;           /* v1 ์ „์šฉ */
struct page_counter tcpmem;         /* v1 ์ „์šฉ */

struct memcg1_events_percpu __percpu *events_percpu;

unsigned long soft_limit;

/* memcg_oom_lock์œผ๋กœ ๋ณดํ˜ธ */
bool oom_lock;
int under_oom;

/* OOM-Killer ๋น„ํ™œ์„ฑํ™” */
int oom_kill_disable;

/* ์ž„๊ณ„๊ฐ’ ๋ฐฐ์—ด ๋ณดํ˜ธ */
struct mutex thresholds_lock;

/* ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ž„๊ณ„๊ฐ’. RCU๋กœ ๋ณดํ˜ธ */
struct mem_cgroup_thresholds thresholds;

/* ๋ฉ”๋ชจ๋ฆฌ+์Šค์™‘ ์‚ฌ์šฉ๋Ÿ‰ ์ž„๊ณ„๊ฐ’. RCU๋กœ ๋ณดํ˜ธ */
struct mem_cgroup_thresholds memsw_thresholds;

/* oom notifier event fd์šฉ */
struct list_head oom_notify;

/* ๋ ˆ๊ฑฐ์‹œ TCP ๋ฉ”๋ชจ๋ฆฌ ๊ณ„์ • */
bool tcpmem_active;
int tcpmem_pressure;

/* ์‚ฌ์šฉ์ž ๊ณต๊ฐ„์ด ์ˆ˜์‹ ํ•˜๋ ค๋Š” ์ด๋ฒคํŠธ ๋ชฉ๋ก */
struct list_head event_list;
spinlock_t event_list_lock;
#endif /* CONFIG_MEMCG_V1 */

struct mem_cgroup_per_node *nodeinfo[];
};

ํ•ต์‹ฌ ํ•„๋“œ ์„ค๋ช…:

ํ•„๋“œ์—ญํ• 
`css`cgroup ์„œ๋ธŒ์‹œ์Šคํ…œ ์ƒํƒœ โ€” ๋ถ€๋ชจ/์ž์‹ ๊ด€๊ณ„, ์˜จ๋ผ์ธ ์ƒํƒœ ๊ด€๋ฆฌ
`memory``page_counter` ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€ ๊ณ„์ • โ€” `memory.max`/`memory.high`์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„
`swap` / `memsw`์Šค์™‘ ๊ณ„์ • โ€” v2๋Š” `swap` ๋…๋ฆฝ, v1์€ `memsw` ํ•ฉ์‚ฐ
`vmstats` / `vmstats_percpu`๋ฉ”๋ชจ๋ฆฌ ํ†ต๊ณ„ โ€” `memory.stat` ์ถœ๋ ฅ์˜ ์›์ฒœ
`memory_events`OOM, high ์ดˆ๊ณผ ๋“ฑ์˜ ์ด๋ฒคํŠธ ์นด์šดํ„ฐ โ€” `memory.events` ์ถœ๋ ฅ
`oom_group``memory.oom.group=1` ์„ค์ • ์‹œ memcg ๋‚ด ๋ชจ๋“  ํ”„๋กœ์„ธ์Šค๋ฅผ ํ•œ ๋ฒˆ์— OOM ํ‚ฌ
`swappiness`memcg๋ณ„ ์Šค์™‘ ๊ฒฝํ–ฅ โ€” ๊ธฐ๋ณธ๊ฐ’ 60, 0~200 ๋ฒ”์œ„
`nodeinfo[]`per-NUMA-node ์ •๋ณด โ€” LRU ๋ฒกํ„ฐ, ํšŒ์ˆ˜ ์ดํ„ฐ๋ ˆ์ดํ„ฐ ํฌํ•จ

struct page_counter

memcg์˜ memory, swap, v1 memsw๋Š” ๋ชจ๋‘ page_counter๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. usage๋Š” ํ˜„์žฌ ์‚ฌ์šฉ๋Ÿ‰, min/low/high/max๋Š” cgroup v2 ๋ฉ”๋ชจ๋ฆฌ ํŒŒ์ผ์˜ ๋‚ด๋ถ€ ๊ฐ’์ž…๋‹ˆ๋‹ค.

// include/linux/page_counter.h:10-44
struct page_counter {
/*
* v2์—์„œ 'usage'๊ฐ€ ๋‹ค๋ฅธ ํ•„๋“œ์™€ ์บ์‹œ๋ผ์ธ์„ ๊ณต์œ ํ•˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค.
* memcg->memory.usage๋Š” struct mem_cgroup์—์„œ ์ž์ฃผ ๊ฐฑ์‹ ๋˜๋Š” ํ•„๋“œ๋‹ค.
*/
atomic_long_t usage;
unsigned long failcnt; /* v1 ์ „์šฉ ํ•„๋“œ */

CACHELINE_PADDING(_pad1_);

/* ์œ ํšจ memory.min๊ณผ memory.min ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์  */
unsigned long emin;
atomic_long_t min_usage;
atomic_long_t children_min_usage;

/* ์œ ํšจ memory.low์™€ memory.low ์‚ฌ์šฉ๋Ÿ‰ ์ถ”์  */
unsigned long elow;
atomic_long_t low_usage;
atomic_long_t children_low_usage;

unsigned long watermark;
/* ์ตœ๊ทผ cgroup v2 ์žฌ์„ค์ • watermark */
unsigned long local_watermark;

/* ์ฝ๊ธฐ๊ฐ€ ๋งŽ์€ ํ•„๋“œ๋ฅผ ๋ณ„๋„ ์บ์‹œ๋ผ์ธ์— ๋‘”๋‹ค. */
CACHELINE_PADDING(_pad2_);

bool protection_support;
bool track_failcnt;
unsigned long min;
unsigned long low;
unsigned long high;
unsigned long max;
struct page_counter *parent;
} ____cacheline_internodealigned_in_smp;

struct mem_cgroup_per_node

NUMA ๋…ธ๋“œ๋ณ„ memcg ์ •๋ณด๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

// include/linux/memcontrol.h:88-123
struct mem_cgroup_per_node {
/* ์ฝ๊ธฐ ์ „์šฉ์— ๊ฐ€๊นŒ์šด ํ•„๋“œ๋Š” ์•ž์ชฝ์— ๋‘”๋‹ค. */
struct mem_cgroup    *memcg;        /* ์—ญ์ฐธ์กฐ, container_of๋ฅผ ์“ธ ์ˆ˜ ์—†์Œ */

struct lruvec_stats_percpu __percpu  *lruvec_stats_percpu;
struct lruvec_stats            *lruvec_stats;
struct shrinker_info __rcu *shrinker_info;

#ifdef CONFIG_MEMCG_V1
/*
* v1 ์ „์šฉ ํ•„๋“œ๋ฅผ ์ค‘๊ฐ„ ๋ฒ„ํผ์ฒ˜๋Ÿผ ๋ฐฐ์น˜ํ•ด ์ฝ๊ธฐ ์œ„์ฃผ ํ•„๋“œ์™€ ์ž์ฃผ ๊ฐฑ์‹ ๋˜๋Š”
* ํ•„๋“œ์˜ ์บ์‹œ๋ผ์ธ ๊ณต์œ  ๊ฒฝํ•ฉ์„ ์ค„์ธ๋‹ค.
*/

struct rb_node        tree_node;     /* RB tree ๋…ธ๋“œ */
unsigned long        usage_in_excess;/* ์†Œํ”„ํŠธ ํ•œ๊ณ„ ์ดˆ๊ณผ๋Ÿ‰ */
bool            on_tree;
#else
CACHELINE_PADDING(_pad1_);
#endif

/* ์ž์ฃผ ๊ฐฑ์‹ ๋˜๋Š” ํ•„๋“œ๋Š” ๋์ชฝ์— ๋‘”๋‹ค. */
struct lruvec        lruvec;
CACHELINE_PADDING(_pad2_);
unsigned long        lru_zone_size[MAX_NR_ZONES][NR_LRU_LISTS];
struct mem_cgroup_reclaim_iter    iter;

#ifdef CONFIG_MEMCG_NMI_SAFETY_REQUIRES_ATOMIC
/* NMI ์ปจํ…์ŠคํŠธ์šฉ slab ํ†ต๊ณ„ */
atomic_t        slab_reclaimable;
atomic_t        slab_unreclaimable;
#endif
};

struct obj_cgroup

์ปค๋„ ๋ฉ”๋ชจ๋ฆฌ(slab) ๊ณ„์ •์„ ์œ„ํ•œ ์˜ค๋ธŒ์ ํŠธ ์ปจํŠธ๋กค๋Ÿฌ. memcg์—์„œ slab ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๊ณ„์ •ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

// include/linux/memcontrol.h:174-182
struct obj_cgroup {
struct percpu_ref refcnt;
struct mem_cgroup *memcg;
atomic_t nr_charged_bytes;
union {
struct list_head list; /* objcg_lock์œผ๋กœ ๋ณดํ˜ธ */
struct rcu_head rcu;
};
};

struct memcg_vmstats / memcg_vmstats_percpu

๋ฉ”๋ชจ๋ฆฌ ํ†ต๊ณ„๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์ฒด. per-CPU ๋กœ์ปฌ ์นด์šดํ„ฐ์™€ aggregate ์นด์šดํ„ฐ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.

// mm/memcontrol.c:504-538
struct memcg_vmstats_percpu {
/* ๋งˆ์ง€๋ง‰ flush ์ดํ›„ ํ†ต๊ณ„ ๊ฐฑ์‹  ํšŸ์ˆ˜ */
unsigned int            stats_updates;

/* memcg_rstat_updated() ๋น ๋ฅธ ์ˆœํšŒ๋ฅผ ์œ„ํ•œ ์บ์‹œ ํฌ์ธํ„ฐ */
struct memcg_vmstats_percpu __percpu    *parent_pcpu;
struct memcg_vmstats            *vmstats;

/* ์œ„ ํ•„๋“œ๋Š” memcg_rstat_updated()์šฉ ๋‹จ์ผ ์บ์‹œ๋ผ์ธ ์•ˆ์— ๋“ค์–ด๊ฐ€์•ผ ํ•œ๋‹ค. */

/* ๋กœ์ปฌ CPU์™€ cgroup์˜ ํŽ˜์ด์ง€ ์ƒํƒœ ๋ฐ ์ด๋ฒคํŠธ */
long            state[MEMCG_VMSTAT_SIZE];
unsigned long        events[NR_MEMCG_EVENTS];

/* lockless ์ƒํ–ฅ ์ „ํŒŒ๋ฅผ ์œ„ํ•œ delta ๊ณ„์‚ฐ */
long            state_prev[MEMCG_VMSTAT_SIZE];
unsigned long events_prev[NR_MEMCG_EVENTS];
} ____cacheline_aligned;

struct memcg_vmstats {
/* ์ง‘๊ณ„๋œ CPU์™€ ํ•˜์œ„ ํŠธ๋ฆฌ ํŽ˜์ด์ง€ ์ƒํƒœ ๋ฐ ์ด๋ฒคํŠธ */
long            state[MEMCG_VMSTAT_SIZE];
unsigned long        events[NR_MEMCG_EVENTS];

/* ๊ณ„์ธตํ™”ํ•˜์ง€ ์•Š์€ CPU ์ง‘๊ณ„ ํŽ˜์ด์ง€ ์ƒํƒœ ๋ฐ ์ด๋ฒคํŠธ */
long            state_local[MEMCG_VMSTAT_SIZE];
unsigned long        events_local[NR_MEMCG_EVENTS];

/* tree ์ „ํŒŒ ์ค‘์ธ ์ž์‹ ์นด์šดํŠธ */
long            state_pending[MEMCG_VMSTAT_SIZE];
unsigned long        events_pending[NR_MEMCG_EVENTS];

/* ๋งˆ์ง€๋ง‰ flush ์ดํ›„ ํ†ต๊ณ„ ๊ฐฑ์‹  ํšŸ์ˆ˜ */
atomic_t        stats_updates;
};

memcg ์ด๋ฒคํŠธ/ํ†ต๊ณ„ enum

// include/linux/memcontrol.h:34-57
/* ๋ฒ”์šฉ ๋…ธ๋“œ ํŽ˜์ด์ง€ ์ƒํƒœ ์œ„์— ์–น๋Š” cgroup๋ณ„ ํŽ˜์ด์ง€ ์ƒํƒœ */
enum memcg_stat_item {
MEMCG_SWAP = NR_VM_NODE_STAT_ITEMS,
MEMCG_SOCK,
MEMCG_PERCPU_B,
MEMCG_VMALLOC,
MEMCG_KMEM,
MEMCG_ZSWAP_B,
MEMCG_ZSWAPPED,
MEMCG_NR_STAT,
};

enum memcg_memory_event {
MEMCG_LOW,
MEMCG_HIGH,
MEMCG_MAX,
MEMCG_OOM,
MEMCG_OOM_KILL,
MEMCG_OOM_GROUP_KILL,
MEMCG_SWAP_HIGH,
MEMCG_SWAP_MAX,
MEMCG_SWAP_FAIL,
MEMCG_SOCK_THROTTLED,
MEMCG_NR_MEMORY_EVENTS,
};

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

1. mem_cgroup_css_alloc (cgroup ์ƒ์„ฑ)

// mm/memcontrol.c:3822-3873
static struct cgroup_subsys_state * __ref
mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
{
struct mem_cgroup *parent = mem_cgroup_from_css(parent_css);
struct mem_cgroup *memcg, *old_memcg;
bool memcg_on_dfl = cgroup_subsys_on_dfl(memory_cgrp_subsys);

old_memcg = set_active_memcg(parent);
memcg = mem_cgroup_alloc(parent);
set_active_memcg(old_memcg);
if (IS_ERR(memcg))
return ERR_CAST(memcg);

page_counter_set_high(&memcg->memory, PAGE_COUNTER_MAX);
memcg1_soft_limit_reset(memcg);
#ifdef CONFIG_ZSWAP
memcg->zswap_max = PAGE_COUNTER_MAX;
WRITE_ONCE(memcg->zswap_writeback, true);
#endif
page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX);
if (parent) {
WRITE_ONCE(memcg->swappiness, mem_cgroup_swappiness(parent));

page_counter_init(&memcg->memory, &parent->memory, memcg_on_dfl);
page_counter_init(&memcg->swap, &parent->swap, false);
#ifdef CONFIG_MEMCG_V1
memcg->memory.track_failcnt = !memcg_on_dfl;
WRITE_ONCE(memcg->oom_kill_disable, READ_ONCE(parent->oom_kill_disable));
page_counter_init(&memcg->kmem, &parent->kmem, false);
page_counter_init(&memcg->tcpmem, &parent->tcpmem, false);
#endif
} else {
init_memcg_stats();
init_memcg_events();
page_counter_init(&memcg->memory, NULL, true);
page_counter_init(&memcg->swap, NULL, false);
#ifdef CONFIG_MEMCG_V1
page_counter_init(&memcg->kmem, NULL, false);
page_counter_init(&memcg->tcpmem, NULL, false);
#endif
root_mem_cgroup = memcg;
return &memcg->css;
}

if (memcg_on_dfl && !cgroup_memory_nosocket)
static_branch_inc(&memcg_sockets_enabled_key);

if (!cgroup_memory_nobpf)
static_branch_inc(&memcg_bpf_enabled_key);

return &memcg->css;
}

์—ญํ• : ์ƒˆ memcg๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. page_counter๋ฅผ ๋ถ€๋ชจ์™€ ์—ฐ๊ฒฐํ•˜๊ณ , ํ†ต๊ณ„ ๊ตฌ์กฐ์ฒด๋ฅผ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

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

  • parent == NULL โ†’ ๋ฃจํŠธ memcg ์ƒ์„ฑ (ํ†ต๊ณ„ ์ดˆ๊ธฐํ™”, root_mem_cgroup ์„ค์ •)
  • parent != NULL โ†’ ์ž์‹ memcg ์ƒ์„ฑ (๋ถ€๋ชจ์˜ page_counter์— ์—ฐ๊ฒฐ, swappiness ์ƒ์†)
  • cgroup_subsys_on_dfl(memory_cgrp_subsys) โ†’ v2 ๋ชจ๋“œ์—์„œ ์†Œ์ผ“/BPF ํ‚ค ํ™œ์„ฑํ™”
  • 2. try_charge_memcg (ํŽ˜์ด์ง€ ๊ณ„์ •)

    // mm/memcontrol.c:2355-2548
    static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,
    unsigned int nr_pages)

    ์—ญํ• : folio ํ• ๋‹น ์‹œ memcg์— ํŽ˜์ด์ง€๋ฅผ ๊ณ„์ •(charge)ํ•ฉ๋‹ˆ๋‹ค. ํ•œ๊ณ„ ์ดˆ๊ณผ ์‹œ ํšŒ์ˆ˜, OOM, force charge ๋“ฑ์˜ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

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

    1. consume_stock() โ†’ per-CPU stock์—์„œ ์ฆ‰์‹œ ๊ณ„์ • ์„ฑ๊ณต ์‹œ ๋ฆฌํ„ด

    2. page_counter_try_charge(&memcg->memory) โ†’ ํ•œ๊ณ„ ๋‚ด ๊ณ„์ • ์„ฑ๊ณต โ†’ done_restock

    3. ํ•œ๊ณ„ ์ดˆ๊ณผ ์‹œ:

    - PF_MEMALLOC ํ”Œ๋ž˜๊ทธ โ†’ ์ฆ‰์‹œ force charge (goto force)

    - task_in_memcg_oom() โ†’ OOM ์ค‘์ด๋ฉด nomem

    - gfpflags_allow_blocking() ๋ถˆ๊ฐ€ โ†’ nomem

    - try_to_free_mem_cgroup_pages() โ†’ ์ง์ ‘ ํšŒ์ˆ˜ ์‹œ๋„

    - ํšŒ์ˆ˜ ํ›„ margin ํ™•๋ณด ๊ฐ€๋Šฅํ•˜๋ฉด retry

    - mem_cgroup_oom() โ†’ memcg OOM killer ๋ฐœ๋™

    4. done_restock: stock ์ž”์—ฌ ๋ถ„๋Ÿ‰ ์ถฉ์ „ + memory.high ์ดˆ๊ณผ ์‹œ __mem_cgroup_handle_over_high() ์˜ˆ์•ฝ

    3. __mem_cgroup_handle_over_high (high ์ดˆ๊ณผ ์‹œ throttle)

    // mm/memcontrol.c:2265-2353
    void __mem_cgroup_handle_over_high(gfp_t gfp_mask)

    ์—ญํ• : memory.high ์ดˆ๊ณผ ์‹œ ํ• ๋‹น์ž๋ฅผ throttleํ•ฉ๋‹ˆ๋‹ค. ์ง์ ‘ ํšŒ์ˆ˜ + schedule_timeout_killable๋กœ ์ง€์—ฐ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

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

    1. task_is_dying() โ†’ ์ข…๋ฃŒ ์ค‘์ด๋ฉด ์ฆ‰์‹œ ๋ฆฌํ„ด

    2. reclaim_high() โ†’ ํšŒ์ˆ˜ ์‹œ๋„ (์ฒซ ๋ฒˆ์งธ๋Š” nr_pages, ์ดํ›„ SWAP_CLUSTER_MAX)

    3. ํšŒ์ˆ˜ ์„ฑ๊ณต or ์žฌ์‹œ๋„ ๊ฐ€๋Šฅ โ†’ retry_reclaim

    4. calculate_high_delay() โ†’ ์ดˆ๊ณผ๋Ÿ‰์— ๋น„๋ก€ํ•œ ์ง€์—ฐ ์‹œ๊ฐ„ ๊ณ„์‚ฐ

    5. penalty_jiffies <= HZ / 100 โ†’ ์ง€์—ฐ์ด ๋„ˆ๋ฌด ์ ์œผ๋ฉด ์Šคํ‚ต

    6. schedule_timeout_killable() โ†’ ์œ ์ € ๋ชจ๋“œ ๋ณต๊ท€ ์‹œ ์‹ค์ œ throttle

    4. charge_memcg / __mem_cgroup_charge (folio ๊ณ„์ • ์ง„์ž…์ )

    // mm/memcontrol.c:4739-4765
    static int charge_memcg(struct folio *folio, struct mem_cgroup *memcg,
    gfp_t gfp)
    {
    int ret;
    
    ret = try_charge(memcg, gfp, folio_nr_pages(folio));
    if (ret)
    goto out;
    
    css_get(&memcg->css);
    commit_charge(folio, memcg);
    memcg1_commit_charge(folio, memcg);
    out:
    return ret;
    }
    
    int __mem_cgroup_charge(struct folio *folio, struct mm_struct *mm, gfp_t gfp)
    {
    struct mem_cgroup *memcg;
    int ret;
    
    memcg = get_mem_cgroup_from_mm(mm);
    ret = charge_memcg(folio, memcg, gfp);
    css_put(&memcg->css);
    
    return ret;
    }

    ์—ญํ• : folio๋ฅผ memcg์— ๊ณ„์ •ํ•˜๋Š” ์ƒ์œ„ API. __mem_cgroup_charge๋Š” mm์—์„œ memcg๋ฅผ ์ฐพ์•„ charge_memcg๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

    ํ๋ฆ„:

    __mem_cgroup_charge()
    โ””โ”€ get_mem_cgroup_from_mm(mm)     // mm์˜ memcg ํš๋“
    โ””โ”€ charge_memcg(folio, memcg, gfp)
    โ”œโ”€ try_charge(memcg, gfp, nr_pages)  // ๊ณ„์ • ์‹œ๋„
    โ”œโ”€ css_get(&memcg->css)               // ์ฐธ์กฐ ์นด์šดํŠธ ์ฆ๊ฐ€
    โ”œโ”€ commit_charge(folio, memcg)         // folio->memcg_data ์„ค์ •
    โ””โ”€ memcg1_commit_charge(folio, memcg)  // v1 ์ „์šฉ ํ›„์ฒ˜๋ฆฌ
    โ””โ”€ css_put(&memcg->css)

    5. mem_cgroup_css_offline (cgroup ์ œ๊ฑฐ)

    // mm/memcontrol.c:3919-3939
    static void mem_cgroup_css_offline(struct cgroup_subsys_state *css)
    {
    struct mem_cgroup *memcg = mem_cgroup_from_css(css);
    
    memcg1_css_offline(memcg);
    
    page_counter_set_min(&memcg->memory, 0);
    page_counter_set_low(&memcg->memory, 0);
    
    zswap_memcg_offline_cleanup(memcg);
    
    memcg_offline_kmem(memcg);
    reparent_deferred_split_queue(memcg);
    reparent_shrinker_deferred(memcg);
    wb_memcg_offline(memcg);
    lru_gen_offline_memcg(memcg);
    
    drain_all_stock(memcg);
    
    mem_cgroup_private_id_put(memcg);
    }

    ์—ญํ• : memcg๊ฐ€ ์˜คํ”„๋ผ์ธ๋  ๋•Œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ถ€๋ชจ์—๊ฒŒ ๋ฆฌ์†Œ์Šค๋ฅผ ์ƒ์†ํ•˜๊ณ , ๊ด€๋ จ ์›Œ์ปค๋ฅผ ๋“œ๋ ˆ์ธํ•ฉ๋‹ˆ๋‹ค.

    ํ๋ฆ„:

    mem_cgroup_css_offline()
    โ”œโ”€ memcg1_css_offline(memcg)           // v1 ๋ ˆ๊ฑฐ์‹œ ์ •๋ฆฌ
    โ”œโ”€ page_counter_set_min/low(&memcg->memory, 0)  // ๋ณดํ˜ธ ์ œ๊ฑฐ
    โ”œโ”€ zswap_memcg_offline_cleanup(memcg)  // zswap ์ •๋ฆฌ
    โ”œโ”€ memcg_offline_kmem(memcg)           // ์ปค๋„ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ
    โ”œโ”€ reparent_deferred_split_queue(memcg) // THP ๋ถ„ํ•  ํ ์ƒ์†
    โ”œโ”€ reparent_shrinker_deferred(memcg)   // shrinker ์ƒ์†
    โ”œโ”€ wb_memcg_offline(memcg)             // ์“ฐ๊ธฐ ๋˜๋Œ๋ฆผ ์ •๋ฆฌ
    โ”œโ”€ lru_gen_offline_memcg(memcg)        // MGLRU ์ •๋ฆฌ
    โ”œโ”€ drain_all_stock(memcg)              // per-CPU stock ๋“œ๋ ˆ์ธ
    โ””โ”€ mem_cgroup_private_id_put(memcg)    // ID ํ•ด์ œ

    6. memory_files (cgroup v2 ํŒŒ์ผ ๋“ฑ๋ก)

    // mm/memcontrol.c:4624-4696
    static struct cftype memory_files[] = {
    {
    .name = "current",
    .flags = CFTYPE_NOT_ON_ROOT,
    .read_u64 = memory_current_read,
    },
    {
    .name = "peak",
    .flags = CFTYPE_NOT_ON_ROOT,
    .open = peak_open,
    .release = peak_release,
    .seq_show = memory_peak_show,
    .write = memory_peak_write,
    },
    {
    .name = "min",
    .flags = CFTYPE_NOT_ON_ROOT,
    .seq_show = memory_min_show,
    .write = memory_min_write,
    },
    {
    .name = "low",
    .flags = CFTYPE_NOT_ON_ROOT,
    .seq_show = memory_low_show,
    .write = memory_low_write,
    },
    {
    .name = "high",
    .flags = CFTYPE_NOT_ON_ROOT,
    .seq_show = memory_high_show,
    .write = memory_high_write,
    },
    {
    .name = "max",
    .flags = CFTYPE_NOT_ON_ROOT,
    .seq_show = memory_max_show,
    .write = memory_max_write,
    },
    {
    .name = "events",
    .flags = CFTYPE_NOT_ON_ROOT,
    .file_offset = offsetof(struct mem_cgroup, events_file),
    .seq_show = memory_events_show,
    },
    {
    .name = "events.local",
    .flags = CFTYPE_NOT_ON_ROOT,
    .file_offset = offsetof(struct mem_cgroup, events_local_file),
    .seq_show = memory_events_local_show,
    },
    {
    .name = "stat",
    .seq_show = memory_stat_show,
    },
    #ifdef CONFIG_NUMA
    {
    .name = "numa_stat",
    .seq_show = memory_numa_stat_show,
    },
    #endif
    {
    .name = "oom.group",
    .flags = CFTYPE_NOT_ON_ROOT | CFTYPE_NS_DELEGATABLE,
    .seq_show = memory_oom_group_show,
    .write = memory_oom_group_write,
    },
    {
    .name = "reclaim",
    .flags = CFTYPE_NS_DELEGATABLE,
    .write = memory_reclaim,
    },
    { }    /* ์ข…๋ฃŒ */
    };
    // mm/memcontrol.c:4698-4714
    struct cgroup_subsys memory_cgrp_subsys = {
    .css_alloc = mem_cgroup_css_alloc,
    .css_online = mem_cgroup_css_online,
    .css_offline = mem_cgroup_css_offline,
    .css_released = mem_cgroup_css_released,
    .css_free = mem_cgroup_css_free,
    .css_reset = mem_cgroup_css_reset,
    .css_rstat_flush = mem_cgroup_css_rstat_flush,
    .attach = mem_cgroup_attach,
    .fork = mem_cgroup_fork,
    .exit = mem_cgroup_exit,
    .dfl_cftypes = memory_files,
    #ifdef CONFIG_MEMCG_V1
    .legacy_cftypes = mem_cgroup_legacy_files,
    #endif
    .early_init = 0,
    };

    ์—ญํ• : memory.current, memory.min, memory.low, memory.high, memory.max, memory.events, memory.numa_stat, memory.oom.group, memory.reclaim ๊ฐ™์€ cgroup v2 ํŒŒ์ผ์„ memory controller์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฐ์—ด์€ memory_cgrp_subsys.dfl_cftypes๋กœ ๋“ฑ๋ก๋˜์–ด ๋ฃจํŠธ๊ฐ€ ์•„๋‹Œ cgroup์—์„œ ์ œ์–ด ํŒŒ์ผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.


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

    folio ๊ณ„์ • ์ง„์ž… ๊ฒฝ๋กœ

    ํŒŒ์ผ ์บ์‹œ: filemap_add_folio() [mm/filemap.c:949-969]
    โ””โ”€ mem_cgroup_charge(folio, NULL, gfp)
    โ””โ”€ __mem_cgroup_charge(folio, mm, gfp)
    
    ์ต๋ช… fault: alloc_anon_folio() [mm/memory.c:5179-5209]
    โ””โ”€ vma_alloc_folio(gfp, order, vma, addr)
    โ””โ”€ mem_cgroup_charge(folio, vma->vm_mm, gfp)
    โ””โ”€ __mem_cgroup_charge(folio, mm, gfp)
    
    ๊ณตํ†ต ๊ณ„์ •: __mem_cgroup_charge() [mm/memcontrol.c:4755-4765]
    โ”œโ”€ get_mem_cgroup_from_mm(mm)
    โ”‚    โ””โ”€ mem_cgroup_from_task(mm->owner)
    โ””โ”€ charge_memcg(folio, memcg, gfp)
    โ”œโ”€ try_charge(memcg, gfp, folio_nr_pages(folio))
    โ”‚    โ””โ”€ try_charge_memcg(memcg, gfp_mask, nr_pages)
    โ”‚         โ”œโ”€ consume_stock() [๋น ๋ฅธ ๊ฒฝ๋กœ]
    โ”‚         โ”œโ”€ page_counter_try_charge(&memcg->memory) [๊ณ„์ • ์‹œ๋„]
    โ”‚         โ”œโ”€ try_to_free_mem_cgroup_pages() [์ง์ ‘ ํšŒ์ˆ˜]
    โ”‚         โ”œโ”€ mem_cgroup_oom() [memcg OOM]
    โ”‚         โ””โ”€ done_restock [memory.high / memory.swap.high ํ™•์ธ]
    โ”œโ”€ css_get(&memcg->css)
    โ”œโ”€ commit_charge(folio, memcg)
    โ”‚    โ””โ”€ folio->memcg_data = (unsigned long)memcg
    โ””โ”€ memcg1_commit_charge(folio, memcg)

    memcg OOM ๊ฒฝ๋กœ

    try_charge_memcg() โ†’ mem_cgroup_oom()
    โ”œโ”€ memcg_memory_event(memcg, MEMCG_OOM)
    โ”œโ”€ memcg1_oom_prepare(memcg, &locked)
    โ””โ”€ mem_cgroup_out_of_memory(memcg, mask, order)
    โ”œโ”€ mutex_lock_killable(&oom_lock)
    โ”œโ”€ mem_cgroup_margin(memcg) ์žฌํ™•์ธ
    โ””โ”€ out_of_memory(&oc)
    โ””โ”€ oom_kill_process()
    โ””โ”€ mem_cgroup_get_oom_group(victim, oom_domain)

    folio ํ•ด์ œ ์‹œ uncharge ๊ฒฝ๋กœ

    folio_memcg_free()
    โ””โ”€ __mem_cgroup_uncharge(folio)
    โ””โ”€ uncharge_folio(folio, &ug)
    โ”œโ”€ folio_memcg_kmem() โ†’ objcg ๊ฒฝ๋กœ
    โ”œโ”€ __folio_memcg() โ†’ ์ผ๋ฐ˜ ๋ฉ”๋ชจ๋ฆฌ ๊ฒฝ๋กœ
    โ””โ”€ uncharge_batch(ug)
    โ””โ”€ memcg_uncharge(memcg, nr_pages)
    โ””โ”€ page_counter_uncharge(&memcg->memory, nr_pages)

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

    memcg v1 vs v2 ์ฃผ์š” ์ฐจ์ด

    ํ•ญ๋ชฉcgroup v1cgroup v2
    ํ•œ๊ณ„ ์„ค์ •`memory.limit_in_bytes``memory.max`
    ์†Œํ”„ํŠธ ํ•œ๊ณ„`memory.soft_limit_in_bytes``memory.high`
    ๋ณดํ˜ธ ์ˆ˜์ค€์—†์Œ`memory.min`, `memory.low`
    ์Šค์™‘ ๊ณ„์ •`memory.memsw.limit_in_bytes` (ํ•ฉ์‚ฐ)`memory.swap.max` (๋…๋ฆฝ)
    ์ปค๋„ ๋ฉ”๋ชจ๋ฆฌ`memory.kmem.limit_in_bytes`๊ธฐ๋ณธ ๊ณ„์ • (๋ณ„๋„ ์ œํ•œ ์—†์Œ)
    ์†Œ์ผ“ ๋ฉ”๋ชจ๋ฆฌ`memory.kmem.tcp.limit_in_bytes`๊ธฐ๋ณธ ๊ณ„์ •
    OOM ์ œ์–ด`memory.oom_control``memory.oom.group`
    ์ด๋ฒคํŠธ`memory.oom_control`์˜ `oom_kill``memory.events`์˜ `oom_kill`
    ํ†ต๊ณ„`memory.stat` (๋‹ค์†Œ ์ƒ์ด)`memory.stat` (ํ™•์žฅ)
    PSI์—†์Œ`memory.pressure`

    cgroup v2 ๋ณดํ˜ธ/์ œํ•œ ํŒŒ์ผ

    memory.min <= memory.low <= memory.high <= memory.max ์ˆœ์„œ๋กœ ์ดํ•ดํ•˜๋ฉด ์šด์˜ ์ •์ฑ…์„ ์žก๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋ถ€๋ชจ cgroup์˜ ์‹ค์ œ ํ•œ๊ณ„๊ฐ€ ์ตœ์ข… ์ƒํ•œ์ด๋ฏ€๋กœ, ์ž์‹ cgroup์˜ ์„ค์ • ํ•ฉ๊ณ„๊ฐ€ ๋ถ€๋ชจ๋ณด๋‹ค ํฌ๊ฒŒ ์žกํž ์ˆ˜๋Š” ์žˆ์–ด๋„ ์‹ค์ œ ์‚ฌ์šฉ๋Ÿ‰์€ ๋ถ€๋ชจ์˜ ๊ณ„์ •๊ณผ ์ œํ•œ์„ ํ•จ๊ป˜ ํ†ต๊ณผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    ํŒŒ์ผ๋‚ด๋ถ€ ํ•„๋“œ์˜๋ฏธ์ดˆ๊ณผ ๋˜๋Š” ๋ถ€์กฑ ์‹œ ๋™์ž‘
    `memory.min``memcg->memory.min`์ ˆ๋Œ€ ๋ณดํ˜ธ๋Ÿ‰๋ณดํ˜ธ ๋ฒ”์œ„ ์•„๋ž˜์˜ ๋ฉ”๋ชจ๋ฆฌ๋Š” ์›์น™์ ์œผ๋กœ ํšŒ์ˆ˜ํ•˜์ง€ ์•Š์Œ
    `memory.low``memcg->memory.low`๋…ธ๋ ฅ ๊ธฐ๋ฐ˜ ๋ณดํ˜ธ๋Ÿ‰์ „์—ญ ์••๋ฐ•์ด ๊ฐ•ํ•˜๋ฉด ํšŒ์ˆ˜๋  ์ˆ˜ ์žˆ์ง€๋งŒ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์Œ
    `memory.high``memcg->memory.high`throttle ๊ฒฝ๊ณ ์„ ์ดˆ๊ณผ ์‹œ ํšŒ์ˆ˜ ์œ ๋„, ์œ ์ € ๋ชจ๋“œ ๋ณต๊ท€ ๊ฒฝ๋กœ์—์„œ ์ง€์—ฐ ์ ์šฉ
    `memory.max``memcg->memory.max`ํ•˜๋“œ ์ œํ•œํšŒ์ˆ˜ ์‹คํŒจ ์‹œ memcg OOM ๊ฒฝ๋กœ๋กœ ์ง„์ž…
    `memory.current``memcg->memory.usage`ํ˜„์žฌ ์‚ฌ์šฉ๋Ÿ‰`page_counter_read(&memcg->memory)` ๊ธฐ๋ฐ˜ ์ถœ๋ ฅ
    `memory.events``memory_events[]`low/high/max/OOM ์ด๋ฒคํŠธ์ด๋ฒคํŠธ ํŒŒ์ผ ์•Œ๋ฆผ๊ณผ ์šด์˜ ๋ชจ๋‹ˆํ„ฐ๋ง์— ์‚ฌ์šฉ

    ์šด์˜ ๊ณ„์ธต ์˜ˆ์‹œ

    cgroup๋Œ€ํ‘œ ์„ค์ •ํ•ด์„
    `system.slice``memory.max=4G`, `memory.high=3.5G`, `memory.low=1G`์‹œ์Šคํ…œ ์„œ๋น„์Šค๊ฐ€ ์ตœ์†Œ 1G ๋ณดํ˜ธ๋ฅผ ๋ฐ›๋˜ 3.5G๋ถ€ํ„ฐ ์†๋„ ์ œํ•œ์„ ๋ฐ›์Œ
    `app.slice``memory.max=8G`, `memory.high=7G`, `memory.swap.max=2G`์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ RSS์™€ page cache๋ฅผ ์ œํ•œํ•˜๊ณ  swap ์‚ฌ์šฉ๋Ÿ‰์„ ๋ณ„๋„ ๊ด€๋ฆฌ
    `guest.slice``memory.max=16G`, `memory.oom.group=1`VM ๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ ๋ฌถ์Œ์„ ํ•˜๋‚˜์˜ OOM ๋„๋ฉ”์ธ์œผ๋กœ ์ทจ๊ธ‰

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

    ๊ฒฝ๋กœํŠธ๋ฆฌ๊ฑฐ๋™์ž‘
    ์ง์ ‘ ํšŒ์ˆ˜ (direct reclaim)`try_charge_memcg()`์—์„œ ํ•œ๊ณ„ ์ดˆ๊ณผ`try_to_free_mem_cgroup_pages()` ํ˜ธ์ถœ
    ์œ ์ € ๋ชจ๋“œ ๋ณต๊ท€ ์‹œ throttle`memory.high` ์ดˆ๊ณผ, `done_restock`์—์„œ ๊ฐ์ง€`__mem_cgroup_handle_over_high()` โ†’ `schedule_timeout_killable()`
    periodic flush2์ดˆ๋งˆ๋‹ค deferred work`flush_memcg_stats_dwork()` โ†’ ํ†ต๊ณ„ ๋™๊ธฐํ™”
    per-CPU stock drainmemcg ์˜คํ”„๋ผ์ธ ์‹œ`drain_all_stock()` โ†’ ๋ชจ๋“  CPU์˜ stock ๋น„์šฐ๊ธฐ

    OOM ๋ฐœ์ƒ ์กฐ๊ฑด

    ์กฐ๊ฑดmemory.max ์ดˆ๊ณผ ์‹œ ๋™์ž‘
    ์ผ๋ฐ˜ ํ• ๋‹น (`GFP_KERNEL`)ํšŒ์ˆ˜ ์‹œ๋„ โ†’ ์‹คํŒจ ์‹œ memcg OOM killer ๋ฐœ๋™
    `__GFP_NOFAIL` ํ• ๋‹นํ•œ๊ณ„๋ฅผ ์ผ์‹œ์ ์œผ๋กœ ์ดˆ๊ณผํ•˜์—ฌ force charge
    `__GFP_HIGH` ํ• ๋‹นforce charge ํ—ˆ์šฉ (์ปค๋„ ๋‚ด๋ถ€ ๊ธด๊ธ‰ ํ• ๋‹น)
    `PF_MEMALLOC` ํ”Œ๋ž˜๊ทธ์ฆ‰์‹œ force charge (์‹œ์Šคํ…œ ์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ์ค‘)
    `oom_kill_disable=1` (v1)OOM killer ๋น„ํ™œ์„ฑํ™”, ํšŒ์ˆ˜๋งŒ ์‹œ๋„

    ์Šค์ผ€์ค„๋Ÿฌ ์—ฐ๋™: high throttle ์ง€์—ฐ ๊ณ„์‚ฐ

    penalty_jiffies = calculate_high_delay(memcg, nr_pages, max_overage)
    + calculate_high_delay(memcg, nr_pages, swap_max_overage)
    ์ดˆ๊ณผ ๋น„์œจ์˜ˆ์ƒ ์ง€์—ฐํšจ๊ณผ
    ์•ฝ๊ฐ„ ์ดˆ๊ณผ (<= HZ/100)0 (์Šคํ‚ต)ํ”„๋กœ์„ธ์Šค์— ์˜ํ–ฅ ์—†์Œ
    ์ค‘๊ฐ„ ์ดˆ๊ณผ์ˆ˜๋ฐฑ ms์ ์ง„์  ์†๋„ ์ €ํ•˜
    ํฌ๊ฒŒ ์ดˆ๊ณผ์ตœ๋Œ€ `MEMCG_MAX_HIGH_DELAY_JIFFIES`๊ฐ•ํ•œ throttle

    memcg ํ†ต๊ณ„ flush ๋ฉ”์ปค๋‹ˆ์ฆ˜

    ์ปค๋„์€ memcg ํ†ต๊ณ„๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋™๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด 2-tier flush ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

    1. Per-CPU ๋กœ์ปฌ ์—…๋ฐ์ดํŠธ: memcg_vmstats_percpu์— ์ฆ‰์‹œ ๊ธฐ๋ก (lockless)

    2. Periodic flush: 2์ดˆ๋งˆ๋‹ค flush_memcg_stats_dwork()๊ฐ€ aggregate๋กœ ๋ณ‘ํ•ฉ

    3. Reader-side flush: MEMCG_CHARGE_BATCH * nr_cpus ์ด์ƒ ์—…๋ฐ์ดํŠธ ์‹œ ๋™๊ธฐ flush

    Per-CPU state ์—…๋ฐ์ดํŠธ
    โ””โ”€ memcg_rstat_updated()
    โ””โ”€ stats_updates >= MEMCG_CHARGE_BATCH โ†’ vmstats->stats_updates์— atomic_add
    
    Reader-side flush
    โ””โ”€ memcg_vmstats_needs_flush(vmstats) โ†’ true ์‹œ css_rstat_flush()

    ๊ด€๋ จ ๋ฌธ์„œ

  • 00-overview.html โ€” ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ฐœ์š”
  • 01-page_alloc.html โ€” Buddy Allocator (memcg ๊ณ„์ • ๋Œ€์ƒ)
  • 05-page_reclaim.html โ€” ํŽ˜์ด์ง€ ํšŒ์ˆ˜ (memcg๋ณ„ LRU ์Šค์บ”)
  • 07-swap_zswap.html โ€” Swap / zswap (memcg ์Šค์™‘ ๊ณ„์ •)
  • 08-oom.html โ€” OOM Killer (memcg OOM ํฌํ•จ)
  • 13-numa.html โ€” NUMA (per-node memcg ํ†ต๊ณ„)
  • 15-mempolicy.html โ€” ๋ฉ”๋ชจ๋ฆฌ ์ •์ฑ