๏ปฟ# Buddy Allocator (ํ์ด์ง ํ ๋น์)
Linux 7.0 ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ถ์ ์๋ฆฌ์ฆ
๊ฐ์ (Overview)
Buddy Allocator๋ ๋ฆฌ๋
์ค ์ปค๋์ ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ ๋ด๋นํ๋ ํต์ฌ ํ์ ์์คํ
์
๋๋ค. ์์คํ
๋ถํ
์ memblock ํ ๋น์๊ฐ ํ๋ณดํ ๋ฌผ๋ฆฌ ํ์ด์ง ํ๋ ์์ Buddy Allocator๊ฐ ๊ด๋ฆฌํ๋ฉฐ, ์ดํ ๋ชจ๋ ๋ฌผ๋ฆฌ ํ์ด์ง ํ ๋น์ ์ด ํ ๋น์๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋๋ค. kmalloc()๊ณผ ๊ฐ์ slab ํ ๋น์๋ ๋ด๋ถ์ ์ผ๋ก Buddy Allocator์ alloc_pages()๋ฅผ ํธ์ถํฉ๋๋ค.
Buddy ์์คํ
์ ํต์ฌ ์์ด๋์ด๋ 2์ ๊ฑฐ๋ญ์ ๊ณฑ(order) ํฌ๊ธฐ ๋ธ๋ก์ ๊ด๋ฆฌํ๋ ๊ฒ์
๋๋ค. ๊ฐ order์ ๋น ๋ธ๋ก์ ํด๋น migratetype๋ณ free list๋ก ๊ด๋ฆฌ๋๋ฉฐ, ์์ฒญ order๋ณด๋ค ํฐ ๋ธ๋ก์ด ์์ผ๋ฉด expand()๋ก ๋ถํ (split)ํ๊ณ , ๋ฐ๋๋ก ๋ฐํ ์ ์ธ์ buddy ๋ธ๋ก์ด ์์ ๋กญ๋ฉด ๋ณํฉ(coalesce)ํ์ฌ ๋ ํฐ ๋ธ๋ก์ ๋ง๋ญ๋๋ค. ์ด๋ฅผ ํตํด ์ธ๋ถ ๋จํธํ๋ฅผ ์ํํฉ๋๋ค.
์ผ์ ๋น์
Buddy Allocator๋ ํฐ ์ฃผ์ฐจ์ฅ์ ํญ์ 1์นธ, 2์นธ, 4์นธ, 8์นธ์ฒ๋ผ 2์ ๊ฑฐ๋ญ์ ๊ณฑ ํฌ๊ธฐ ๊ตฌ์ญ์ผ๋ก๋ง ๋๋๋ ๊ด๋ฆฌ์์ ๋น์ทํฉ๋๋ค. 2์นธ์ง๋ฆฌ ๊ณต๊ฐ์ด ํ์ํ๋ฐ ๋น์ด ์์ง ์์ผ๋ฉด 4์นธ์ง๋ฆฌ ๊ตฌ์ญ์ ๋ฐ์ผ๋ก ์ชผ๊ฐ ํ์ชฝ์ ๋ด์ฃผ๊ณ , ๋๋จธ์ง 2์นธ์ ๋ค์ ๋น ๊ตฌ์ญ ๋ชฉ๋ก์ ๋ณด๊ดํฉ๋๋ค.
๋ฐ๋๋ก ์ฐจ๊ฐ ๋น ์ ธ ๋ ์ธ์ ๊ตฌ์ญ์ด ๋ชจ๋ ๋น๋ฉด ๋์ ๋ค์ ํฉ์ณ ๋ ํฐ ๊ตฌ์ญ์ผ๋ก ๋ณต์ํฉ๋๋ค. ์ด ๊ท์น ๋๋ถ์ ์ธ์ ํ ์ง(buddy)์ ๋น ๋ฅด๊ฒ ์ฐพ์ ์ ์๊ณ , ์์ ๋น ๊ตฌ์ญ์ด ํฉ์ด์ ธ ํฐ ์ฐ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ชป ๋ง๋๋ ์ธ๋ถ ๋จํธํ๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.
์ด ํ ๋น์๋ ์ฌ์ฉ์ ๊ณต๊ฐ์ mmap()์ด๋ malloc()์ ์ง์ ์ฒ๋ฆฌํ์ง๋ ์์ง๋ง, ๊ทธ ์๋์์ handle_mm_fault() โ do_anonymous_page() / do_wp_page() โ alloc_pages()๋ก ์ด์ด์ง๋ ๋ฌผ๋ฆฌ ํ์ด์ง ์ฐ๊ฒฐ์ ๋ฐ์ณ ์ค๋๋ค. ์ต๋ช
ํ์ด์ง์ ์ฒซ ์ ๊ทผ์ demand paging์ด๊ณ , fork() ๋ค private mapping์ ์ฐ๊ธฐ fault๋ COW๋ก ๋ถ๊ธฐํ ๋ค ๊ฒฐ๊ตญ ๊ฐ์ Buddy ๊ฒฝ๋ก์์ ํ์ด์ง๊ฐ ๋์ต๋๋ค.
์์ค ํ์ผ ๊ฒฝ๋ก:
mm/page_alloc.c โ ํ ๋น/ํด์ ํต์ฌ ๋ก์ง (7856์ค)
include/linux/mmzone.h โ struct zone, free_area, pglist_data ์ ์
include/linux/mm_types.h โ struct page ์ ์
mm/internal.h โ ๋ด๋ถ ํฌํผ ํจ์, GFP ๋ง์คํฌ ์์
๋น ๋ฅธ ์ ๊ฒ ๋ช ๋ น
# ์์คํ
์ ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์ ํ์ธ
cat /proc/pagetypeinfo
# ๊ธฐ๋ณธ ํ์ด์ง ํฌ๊ธฐ ํ์ธ
getconf PAGE_SIZE
# ๊ฐ ์กด์ ํ๋ฆฌ ํ์ด์ง ํต๊ณ
cat /proc/vmstat | grep -E "nr_free_pages|nr_free_cma"
# zone๋ณ ์ํฐ๋งํฌ ๋ฐ free area ์ ๋ณด
cat /proc/zoneinfo | head -100
# Buddy ํ๋ฆฌ ๋ฆฌ์คํธ ๋ถํฌ (order๋ณ ํ๋ฆฌ ํ์ด์ง ์)
cat /proc/buddyinfo
# ๋ฉ๋ชจ๋ฆฌ ๋จํธํ ์ํ ํ์ธ
cat /proc/extfraginfo
# per-CPU ํ์ด์ง ๋ฆฌ์คํธ(PCP) ์ํ ํ์ธ
cat /proc/vmstat | grep pcp
# Slab ์บ์์ Buddy์ ์ํธ์์ฉ ํ์ธ
cat /proc/slabinfo | head -5
# ํ์ด์ง ํดํธ์ COW ์งํ ํ์ธ
ps -o pid,minflt,majflt -p $$
# ํ์ฌ ์์คํ
์ ๋ฉ๋ชจ๋ฆฌ ๋
ธ๋/์กด ๊ตฌ์กฐ
lstopo 2>/dev/null || numactl --hardware
์ถ๊ฐ ์ง๋จ ์์ง ๋ช ๋ น
# ์ ์ฒด ๋ฉ๋ชจ๋ฆฌ์ page allocator ์ฆ์ ๋์ ์์ง
cat /proc/meminfo | head -30
# ๋ฌผ๋ฆฌ/๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ๋งต ํ์ธ
cat /proc/iomem
sudo cat /proc/vmallocinfo | head -20
# page allocation failure, OOM, compaction ๊ด๋ จ ์ปค๋ ๋ก๊ทธ ํ์ธ
dmesg | grep -Ei "page allocation failure|oom|compact|kswapd" | tail -50
# compaction, reclaim, allocation stall ์นด์ดํฐ ํ์ธ
cat /proc/vmstat | grep -E "compact|allocstall|pgscan|pgsteal|pgalloc|pgfree"
# PSI ๋ฉ๋ชจ๋ฆฌ ์๋ ฅ ํ์ธ (์ปค๋ 4.20+)
cat /proc/pressure/memory
# zoneinfo์์ PCP pageset๊ณผ ์ํฐ๋งํฌ ์ฃผ๋ณ ์ ๋ณด ํ์ธ
cat /proc/zoneinfo | grep -E "Node|zone|pagesets|count:|high:|batch:|low|min|high" | head -120
ํต์ฌ ์๋ฃ๊ตฌ์กฐ
1. `struct page` โ ๋ฌผ๋ฆฌ ํ์ด์ง ์ค๋ช ์
๋ชจ๋ ๋ฌผ๋ฆฌ ํ์ด์ง ํ๋ ์์๋ ํ๋์ struct page๊ฐ ์กด์ฌํฉ๋๋ค. Buddy Allocator์์ ์ฌ์ฉ๋๋ ํ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// include/linux/mm_types.h:79-190
struct page {
memdesc_flags_t flags; // ํ์ด์ง ์ํ ๋นํธ (PG_buddy, PG_head ๋ฑ)
union {
struct {
union {
struct list_head lru; // LRU ๋ฆฌ์คํธ
struct list_head buddy_list; // โ Buddy free list ์ฐ๊ฒฐ
struct list_head pcp_list; // โ PCP ๋ฆฌ์คํธ ์ฐ๊ฒฐ
struct llist_node pcp_llist; // โ NMI-safe PCP ๋ฆฌ์คํธ
};
struct address_space *mapping;
union {
pgoff_t __folio_index;
unsigned long share;
};
unsigned long private; // Buddy ์์คํ
์์ order ์ ์ฅ (PageBuddy์ผ ๋)
};
// ... ์๋ต
};
union {
unsigned int page_type; // typed folio์ฉ
atomic_t _mapcount; // ๋งคํ ์นด์ดํธ
};
atomic_t _refcount; // ์ฐธ์กฐ ์นด์ดํธ
// ...
};
ํต์ฌ ํฌ์ธํธ:
buddy_list: free ์ํ์ ํ์ด์ง๊ฐ free_area์ free_list์ ์ฐ๊ฒฐ๋๋ ๋ฆฌ์คํธ ํค๋private: PageBuddy(page)๊ฐ ์ค์ ๋ ๊ฒฝ์ฐ page_private(page)๋ก order ๊ฐ ์ ์ฅflags.f: PG_buddy, PG_head, PG_tail ๋นํธ๋ก compound page/buddy ์ํ ์ถ์ 2. `struct free_area` โ order๋ณ free ๋ฆฌ์คํธ
// include/linux/mmzone.h:138-141
struct free_area {
struct list_head free_list[MIGRATE_TYPES]; // migratetype๋ณ ์ฐ๊ฒฐ ๋ฆฌ์คํธ
unsigned long nr_free; // ์ด ํ๋ฆฌ ๋ธ๋ก ์
};
๊ฐ zone์ NR_PAGE_ORDERS(๋ณดํต 11, order 0~10)๊ฐ์ free_area ๋ฐฐ์ด์ ๊ฐ์ง๋๋ค. ๊ฐ free_area๋ MIGRATE_TYPES๊ฐ์ free list๋ฅผ ๊ฐ์ง๋ฉฐ, ๊ฐ list๋ ํด๋น migratetype๊ณผ order์ ํ๋ฆฌ ๋ธ๋ก๋ค์ buddy_list๋ก ์ฐ๊ฒฐํฉ๋๋ค.
3. `struct zone` โ ๋ฉ๋ชจ๋ฆฌ ์กด
// include/linux/mmzone.h:879-1060
struct zone {
// ์ํฐ๋งํฌ
unsigned long _watermark[NR_WMARK]; // MIN, LOW, HIGH, PROMO
unsigned long watermark_boost;
// ์กด ์์ญ ์ ๋ณด
unsigned long zone_start_pfn;
atomic_long_t managed_pages; // buddy๊ฐ ๊ด๋ฆฌํ๋ ํ์ด์ง ์
unsigned long spanned_pages;
unsigned long present_pages;
// ํ๋ฆฌ ์์ด๋ฆฌ์ด ๋ฐฐ์ด (ํต์ฌ!)
struct free_area free_area[NR_PAGE_ORDERS];
// ๋์์ฑ ์ ์ด
spinlock_t lock; // free_area ๋ณดํธ
// NUMA ๊ด๋ จ
struct pglist_data *zone_pgdat;
struct per_cpu_pages __percpu *per_cpu_pageset; // PCP
// ํต๊ณ
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
};
ํต์ฌ ํฌ์ธํธ:
free_area[NR_PAGE_ORDERS]: order 0๋ถํฐ MAX_PAGE_ORDER๊น์ง์ free area ๋ฐฐ์ดlock: free_area ์ ๊ทผ ์ ํ์ (zone->lock)_watermark[]: ํ ๋น ๊ฐ๋ฅ ์ฌ๋ถ ํ๋จ ๊ธฐ์ค (MIN/LOW/HIGH)per_cpu_pageset: per-CPU ์บ์ ํ์ด์ง ๋ฆฌ์คํธ (PCP)4. `struct pglist_data` (pg_data_t) โ NUMA ๋ ธ๋
// include/linux/mmzone.h:1381-1498
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; // ์ด ๋
ธ๋์ ์กด ๋ฐฐ์ด
struct zonelist node_zonelists[MAX_ZONELISTS]; // ํ ๋น ์์ ๋ชฉ๋ก
int nr_zones;
unsigned long node_start_pfn;
unsigned long node_present_pages;
unsigned long node_spanned_pages;
int node_id;
// kswapd ๊ด๋ จ
wait_queue_head_t kswapd_wait;
struct task_struct *kswapd;
int kswapd_order;
// ...
} pg_data_t;
5. `struct per_cpu_pages` (PCP) โ Per-CPU ํ์ด์ง ์บ์
// include/linux/mmzone.h:744-760
struct per_cpu_pages {
spinlock_t lock;
int count; // ํ์ฌ ๋ฆฌ์คํธ์ ํ์ด์ง ์
int high; // high ์ํฐ๋งํฌ (์ด ๋์ผ๋ฉด ๋๋ ์ธ)
int high_min;
int high_max;
int batch; // buddy์์ ๊ฐ์ ธ์ฌ/๋ณด๋ผ ๋ฐฐ์น ํฌ๊ธฐ
u8 alloc_factor; // allocate ์ batch ์ค์ผ์ผ๋ง ํฉํฐ
short free_count; // ์ฐ์ free ์นด์ดํธ
struct list_head lists[NR_PCP_LISTS]; // migratetype ร order ๋ฆฌ์คํธ
};
ํต์ฌ ํฌ์ธํธ:
NR_PCP_LISTS = MIGRATE_PCPTYPES ร (PAGE_ALLOC_COSTLY_ORDER + 1) + NR_PCP_THPhigh: ์ด ๊ฐ ์ด๊ณผ ์ zone lock ํ์์ buddy๋ก ๋ฐํbatch: ํ ๋ฒ์ buddy์์ ๊ฐ์ ธ์ฌ/๋ณด๋ผ ํ์ด์ง ์6. Migratetype
// include/linux/mmzone.h:64-90
enum migratetype {
MIGRATE_UNMOVABLE, // ์ด๋ ๋ถ๊ฐ (kernel allocations)
MIGRATE_MOVABLE, // ์ด๋ ๊ฐ๋ฅ (user pages)
MIGRATE_RECLAIMABLE, // ํ์ ๊ฐ๋ฅ (inode cache ๋ฑ)
MIGRATE_PCPTYPES, // PCP lists์ ์ (3)
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
MIGRATE_CMA, // CMA ์์ญ (CONFIG_CMA)
MIGRATE_ISOLATE, // ๊ฒฉ๋ฆฌ (CONFIG_MEMORY_ISOLATION)
MIGRATE_TYPES
};
Fallback ์์:
// mm/page_alloc.c:1951-1955
static int fallbacks[MIGRATE_PCPTYPES][MIGRATE_PCPTYPES - 1] = {
[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE },
[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE },
[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE },
};
ํต์ฌ ํจ์
1. `__alloc_pages_noprof()` โ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ๋ฉ์ธ ์ํธ๋ฆฌ
// mm/page_alloc.c:5279-5289
struct page *__alloc_pages_noprof(gfp_t gfp, unsigned int order,
int preferred_nid, nodemask_t *nodemask)
{
struct page *page;
page = __alloc_frozen_pages_noprof(gfp, order, preferred_nid, nodemask);
if (page)
set_page_refcounted(page);
return page;
}
์ญํ : alloc_pages() ๋งคํฌ๋ก์ ์ต์ข
๊ตฌํ. frozen ํ์ด์ง๋ฅผ ํ ๋นํ ํ refcount=1๋ก ์ค์ ํฉ๋๋ค.
๋ถ๊ธฐ ๋ก์ง:
__alloc_frozen_pages_noprof() ํธ์ถ โ ์คํจ ์ NULL ๋ฐํ2. `__alloc_frozen_pages_noprof()` โ ํ ๋น ํต์ฌ ๋ก์ง
// mm/page_alloc.c:5214-5277
struct page *__alloc_frozen_pages_noprof(gfp_t gfp, unsigned int order,
int preferred_nid, nodemask_t *nodemask)
{
struct alloc_context ac = { };
unsigned int alloc_flags = ALLOC_WMARK_LOW;
gfp_t alloc_gfp;
if (WARN_ON_ONCE_GFP(order > MAX_PAGE_ORDER, gfp))
return NULL;
gfp &= gfp_allowed_mask;
gfp = current_gfp_context(gfp);
alloc_gfp = gfp;
if (!prepare_alloc_pages(gfp, order, preferred_nid, nodemask, &ac,
&alloc_gfp, &alloc_flags))
return NULL;
alloc_flags |= alloc_flags_nofragment(zonelist_zone(ac.preferred_zoneref), gfp);
/* 1์ฐจ ์๋: fast path */
page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac);
if (likely(page))
goto out;
alloc_gfp = gfp;
ac.spread_dirty_pages = false;
/* 2์ฐจ ์๋: slow path (direct reclaim, OOM ๋ฑ) */
page = __alloc_pages_slowpath(alloc_gfp, order, &ac);
out:
if (memcg_kmem_online() && (gfp & __GFP_ACCOUNT) && page &&
unlikely(__memcg_kmem_charge_page(page, gfp, order) != 0)) {
free_frozen_pages(page, order);
page = NULL;
}
trace_mm_page_alloc(page, order, alloc_gfp, ac.migratetype);
kmsan_alloc_page(page, order, alloc_gfp);
return page;
}
๋ถ๊ธฐ ๋ก์ง:
1. order > MAX_PAGE_ORDER โ ๊ฒฝ๊ณ ํ NULL ๋ฐํ
2. prepare_alloc_pages() ์คํจ โ NULL ๋ฐํ
3. get_page_from_freelist() ์ฑ๊ณต โ ๋ฐ๋ก ๋ฐํ (fast path)
4. ์คํจ โ __alloc_pages_slowpath() ์ง์
(slow path)
- kswapd wake, direct reclaim, compaction, OOM ์์๋ก ์๋
3. `get_page_from_freelist()` โ ์กด ์ํ ๋ฐ ํ ๋น
// mm/page_alloc.c:3808-4003
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
struct zoneref *z;
struct zone *zone;
retry:
no_fallback = alloc_flags & ALLOC_NOFRAGMENT;
z = ac->preferred_zoneref;
// zonelist ์ํ
for_next_zone_zonelist_nodemask(zone, z, ac->highest_zoneidx,
ac->nodemask) {
// cpuset ์ฒดํฌ
// dirty limit ์ฒดํฌ
// fragmentation avoidance ์ฒดํฌ
check_alloc_wmark:
// ์ํฐ๋งํฌ ๊ฒ์ฌ
mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK);
if (!zone_watermark_fast(zone, order, mark,
ac->highest_zoneidx, alloc_flags,
gfp_mask)) {
// watermarks ๋ฏธ์ถฉ์กฑ ์ node_reclaim ์๋
continue;
}
try_this_zone:
// ์ค์ ํ ๋น ์๋
page = rmqueue(zonelist_zone(ac->preferred_zoneref), zone, order,
gfp_mask, alloc_flags, ac->migratetype);
if (page) {
prep_new_page(page, order, gfp_mask, alloc_flags);
if (unlikely(alloc_flags & ALLOC_HIGHATOMIC))
reserve_highatomic_pageblock(page, order, zone);
return page;
}
}
// fallback: NOFRAGMENT ํด์ ํ ์ฌ์๋
if (no_fallback && !defrag_mode) {
alloc_flags &= ~ALLOC_NOFRAGMENT;
goto retry;
}
return NULL;
}
์์ page fault ์ฐ๊ฒฐ
page fault
โโ handle_mm_fault() [mm/memory.c:6589]
โโ do_anonymous_page() [mm/memory.c:5217] โ ์ฒซ ์ ๊ทผ์ ์ต๋ช
ํ์ด์ง
โโ do_wp_page() [mm/memory.c:4149] โ COW / write-protect fault
โโ alloc_page(GFP_KERNEL)
// mm/memory.c:6589-6624
vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
unsigned int flags, struct pt_regs *regs)
{
...
if (unlikely(is_vm_hugetlb_page(vma)))
ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
else
ret = __handle_mm_fault(vma, address, flags);
...
}
// mm/memory.c:5217-5267
static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{
...
if (!(vmf->flags & FAULT_FLAG_WRITE) &&
!mm_forbids_zeropage(vma->vm_mm)) {
...
entry = pte_mkspecial(pfn_pte(my_zero_pfn(vmf->address),
vma->vm_page_prot));
...
}
...
folio = alloc_anon_folio(vmf);
...
}
// mm/memory.c:4149-4241
static vm_fault_t do_wp_page(struct vm_fault *vmf)
{
...
if (folio && folio_test_anon(folio) &&
(PageAnonExclusive(vmf->page) || wp_can_reuse_anon_folio(folio, vma))) {
...
wp_page_reuse(vmf, folio);
return 0;
}
...
return wp_page_copy(vmf);
}
๋ถ๊ธฐ ๋ก์ง:
1. zonelist ์ํ: preferred zone โ fallback zone ์
2. ๊ฐ zone๋ง๋ค:
- cpuset ํ์ฉ ์ฌ๋ถ
- dirty limit ์ด๊ณผ ์ฌ๋ถ
- ์ํฐ๋งํฌ ์ถฉ์กฑ ์ฌ๋ถ (fast check โ full check)
- watermarks ๋ฏธ์ถฉ์กฑ ์: _deferred_grow_zone() ๋๋ node_reclaim() ์๋
3. rmqueue() ์ฑ๊ณต ์: prep_new_page() ํ ๋ฐํ
4. ๋ชจ๋ zone ์คํจ ์: NOFRAGMENT ํ๋๊ทธ ํด์ ํ retry
4. `__rmqueue()` โ free list์์ ํ์ด์ง ์ ๊ฑฐ
// mm/page_alloc.c:2477-2540
static __always_inline struct page *
__rmqueue(struct zone *zone, unsigned int order, int migratetype,
unsigned int alloc_flags, enum rmqueue_mode *mode)
{
// CMA ๋ฐธ๋ฐ์ฑ: CMA free๊ฐ 50% ์ด๊ณผ ์ CMA์์ ๋จผ์ ํ ๋น
if (alloc_flags & ALLOC_CMA &&
zone_page_state(zone, NR_FREE_CMA_PAGES) >
zone_page_state(zone, NR_FREE_PAGES) / 2) {
page = __rmqueue_cma_fallback(zone, order);
if (page) return page;
}
// fallback ๋ชจ๋ ์์ฐจ ์๋
switch (*mode) {
case RMQUEUE_NORMAL:
page = __rmqueue_smallest(zone, order, migratetype); // 1์์
if (page) return page;
fallthrough;
case RMQUEUE_CMA:
page = __rmqueue_cma_fallback(zone, order); // 2์์
if (page) { *mode = RMQUEUE_CMA; return page; }
fallthrough;
case RMQUEUE_CLAIM:
page = __rmqueue_claim(zone, order, migratetype, alloc_flags); // 3์์
if (page) { *mode = RMQUEUE_NORMAL; return page; }
fallthrough;
case RMQUEUE_STEAL:
if (!(alloc_flags & ALLOC_NOFRAGMENT)) {
page = __rmqueue_steal(zone, order, migratetype); // 4์์
if (page) { *mode = RMQUEUE_STEAL; return page; }
}
}
return NULL;
}
๋ถ๊ธฐ ๋ก์ง (4๋จ๊ณ fallback):
1. NORMAL: ๊ฐ์ migratetype์ free list์์ ์ง์ ํ ๋น
2. CMA: CMA free list์์ ํ ๋น (MOVABLE๋ง)
3. CLAIM: ๋ค๋ฅธ migratetype์ free list์์ ํ์ด์ง๋ธ๋ก์ claim (migratetype ๋ณ๊ฒฝ)
4. STEAL: ๋ค๋ฅธ migratetype์์ ํ์ด์ง๋ฅผ steal (๋จํธํ ํ์ฉ ์)
5. `__rmqueue_smallest()` โ ์ต์ order์์ ํ ๋น
// mm/page_alloc.c:1919-1942
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
int migratetype)
{
unsigned int current_order;
struct free_area *area;
struct page *page;
// ์์ฒญ order๋ถํฐ MAX_PAGE_ORDER๊น์ง ์์ order ํ์
for (current_order = order; current_order < NR_PAGE_ORDERS; ++current_order) {
area = &(zone->free_area[current_order]);
page = get_page_from_free_area(area, migratetype);
if (!page)
continue;
// ํฐ ๋ธ๋ก์ ํ์ order๋ก ๋ถํ
page_del_and_expand(zone, page, order, current_order, migratetype);
return page;
}
return NULL;
}
๋ถ๊ธฐ ๋ก์ง:
page_del_and_expand()๋ก ๋ถํ ํ ๋ฐํ6. `expand()` โ ๋ธ๋ก ๋ถํ (Split)
// mm/page_alloc.c:1732-1758
static inline unsigned int expand(struct zone *zone, struct page *page, int low,
int high, int migratetype)
{
unsigned int size = 1 << high;
unsigned int nr_added = 0;
while (high > low) {
high--;
size >>= 1;
// guard page ์ค์ ์ skip
if (set_page_guard(zone, &page[size], high))
continue;
// ์์ half๋ฅผ free list์ ์ถ๊ฐ
__add_to_free_list(&page[size], zone, high, migratetype, false);
set_buddy_order(&page[size], high);
nr_added += size;
}
return nr_added;
}
๋ถ๊ธฐ ๋ก์ง:
7. `__free_one_page()` โ ํ์ด์ง ๋ฐํ ๋ฐ ๋ฒ๋ ๋ณํฉ
// mm/page_alloc.c:978-1064
static inline void __free_one_page(struct page *page,
unsigned long pfn, struct zone *zone, unsigned int order,
int migratetype, fpi_t fpi_flags)
{
unsigned long buddy_pfn = 0;
unsigned long combined_pfn;
account_freepages(zone, 1 << order, migratetype);
// order < MAX_PAGE_ORDER๊น์ง ๋ฒ๋ ๋ณํฉ ์๋
while (order < MAX_PAGE_ORDER) {
int buddy_mt = migratetype;
// compaction capture ์ฒดํฌ
if (compaction_capture(capc, page, order, migratetype)) {
account_freepages(zone, -(1 << order), migratetype);
return;
}
// ์ธ์ ๋ฒ๋ ์ฐพ๊ธฐ
buddy = find_buddy_page_pfn(page, pfn, order, &buddy_pfn);
if (!buddy)
goto done_merging;
// pageblock_order ์ด์์์๋ migratetype ์ผ์น ํ์ธ
if (unlikely(order >= pageblock_order)) {
buddy_mt = get_pfnblock_migratetype(buddy, buddy_pfn);
if (migratetype != buddy_mt &&
(!migratetype_is_mergeable(migratetype) ||
!migratetype_is_mergeable(buddy_mt)))
goto done_merging;
}
// ๋ฒ๋๋ฅผ free list์์ ์ ๊ฑฐํ๊ณ ๋ณํฉ
__del_page_from_free_list(buddy, zone, order, buddy_mt);
combined_pfn = buddy_pfn & pfn;
page = page + (combined_pfn - pfn);
pfn = combined_pfn;
order++;
}
done_merging:
set_buddy_order(page, order);
__add_to_free_list(page, zone, order, migratetype, to_tail);
}
๋ถ๊ธฐ ๋ก์ง:
1. compaction capture: compaction ์ค์ด๋ฉด ํด๋น ํ์ด์ง๋ฅผ ์บก์ฒ
2. find_buddy_page_pfn(): ์ธ์ ๋ฒ๋ ํ์ด์ง ๊ฒ์
3. order โฅ pageblock_order: migratetype ํธํ์ฑ ํ์ธ ํ ๋ณํฉ
4. ๋ฒ๋ ๋ณํฉ: combined_pfn = buddy_pfn & pfn์ผ๋ก ๋ณํฉ๋ base pfn ๊ณ์ฐ
5. ๋ณํฉ๋ ๋ธ๋ก์ free list์ ์ถ๊ฐ
8. `__alloc_pages_slowpath()` โ Slow Path (ํ ๋น ์คํจ ์ ๋์)
// mm/page_alloc.c:4710-4994
static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
struct alloc_context *ac)
{
bool can_direct_reclaim = gfp_mask & __GFP_DIRECT_RECLAIM;
bool can_compact = can_direct_reclaim && gfp_compaction_allowed(gfp_mask);
bool nofail = gfp_mask & __GFP_NOFAIL;
const bool costly_order = order > PAGE_ALLOC_COSTLY_ORDER;
struct page *page = NULL;
unsigned int alloc_flags;
unsigned long did_some_progress;
enum compact_priority compact_priority;
enum compact_result compact_result;
int compaction_retries;
int no_progress_loops;
restart:
compaction_retries = 0;
no_progress_loops = 0;
compact_result = COMPACT_SKIPPED;
compact_priority = DEF_COMPACT_PRIORITY;
// costly/high-order ํ ๋น ์ compaction ์ฐ์ ์๋
if (can_compact && (costly_order || (order > 0 &&
ac->migratetype != MIGRATE_MOVABLE))) {
compact_first = true;
compact_priority = INIT_COMPACT_PRIORITY;
}
alloc_flags = gfp_to_alloc_flags(gfp_mask, order);
retry:
// kswapd wake
if (alloc_flags & ALLOC_KSWAPD)
wake_all_kswapds(order, gfp_mask, ac);
// ์ฌ์๋: watermarks ์ฌ๊ฒ์ฌ
page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
if (page)
goto got_pg;
// pfmemalloc ํ๋๊ทธ ํ์ธ (๊ธด๊ธ ํ ๋น)
reserve_flags = __gfp_pfmemalloc_flags(gfp_mask);
if (reserve_flags) {
alloc_flags = gfp_to_alloc_flags_cma(gfp_mask, reserve_flags) |
(alloc_flags & ALLOC_KSWAPD);
if (can_retry_reserves) {
can_retry_reserves = false;
goto retry;
}
}
// ์ง์ ํ์ ๋ถ๊ฐ๋ฅํ๋ฉด ์คํจ
if (!can_direct_reclaim)
goto nopage;
// ์ง์ ํ์ ์๋
if (!compact_first) {
page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags,
ac, &did_some_progress);
if (page)
goto got_pg;
}
// ์ง์ compaction ์๋
page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,
compact_priority, &compact_result);
if (page)
goto got_pg;
// compact_first ํ retry
if (compact_first) {
compact_first = false;
goto retry;
}
// ํ์/compaction ์ฌ์๋ ๊ฒ์ฌ
if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags,
did_some_progress > 0, &no_progress_loops))
goto retry;
if (did_some_progress > 0 && can_compact &&
should_compact_retry(ac, order, alloc_flags,
compact_result, &compact_priority,
&compaction_retries))
goto retry;
// NOFRAGMENT ํด์ ํ ์ฌ์๋
if (defrag_mode && (alloc_flags & ALLOC_NOFRAGMENT)) {
alloc_flags &= ~ALLOC_NOFRAGMENT;
goto retry;
}
// OOM Killer ๋ฐ๋
page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress);
if (page)
goto got_pg;
// NOFAIL ์ฒ๋ฆฌ
if (unlikely(nofail)) {
page = __alloc_pages_cpuset_fallback(gfp_mask, order,
ALLOC_MIN_RESERVE, ac);
if (page)
goto got_pg;
cond_resched();
goto retry;
}
nopage:
warn_alloc(gfp_mask, ac->nodemask,
"page allocation failure: order:%u", order);
got_pg:
return page;
}
๋ถ๊ธฐ ๋ก์ง (๋จ๊ณ๋ณ ๋์):
1. kswapd wake: ๋ฐฑ๊ทธ๋ผ์ด๋ ํ์ ํธ๋ฆฌ๊ฑฐ
2. watermark ์ฌ๊ฒ์ฌ: ALLOC_MIN_RESERVE ์ ์ฉ ํ ์ฌ์๋
3. pfmemalloc: ๊ธด๊ธ ์ ALLOC_NO_WATERMARKS ์ ์ฉ
4. ์ง์ ํ์: try_to_free_pages() โ LRU ํ์ด์ง ํ์
5. ์ง์ compaction: ๋ฉ๋ชจ๋ฆฌ ์กฐ๊ฐ ๋ชจ์์ผ๋ก ํฐ ๋ธ๋ก ํ๋ณด
6. ํ์/compaction ์ฌ์๋: MAX_RECLAIM_RETRIES(16ํ)๊น์ง ๋ฐ๋ณต
7. NOFRAGMENT ํด์ : ๋จํธํ ํ์ฉ ํ ์ฌ์๋
8. OOM Killer: out_of_memory() โ ํ๋ก์ธ์ค ์ข
๋ฃ
9. NOFAIL: ๋ฌดํ ์ฌ์๋ (์กฐ๊ฑด๋ถ)
9. `__free_frozen_pages()` โ PCP๋ฅผ ํตํ ํ์ด์ง ๋ฐํ
// mm/page_alloc.c:2964-3020
static void __free_frozen_pages(struct page *page, unsigned int order,
fpi_t fpi_flags)
{
unsigned long UP_flags;
struct per_cpu_pages *pcp;
struct zone *zone;
unsigned long pfn = page_to_pfn(page);
int migratetype;
// PCP ํ์ฉ order๊ฐ ์๋๋ฉด buddy๋ก ์ง์ ๋ฐํ
if (!pcp_allowed_order(order)) {
__free_pages_ok(page, order, fpi_flags);
return;
}
if (!__free_pages_prepare(page, order, fpi_flags))
return;
// ISOLATE ํ์
์ ์ฆ์ free_one_page()
zone = page_zone(page);
migratetype = get_pfnblock_migratetype(page, pfn);
if (unlikely(migratetype >= MIGRATE_PCPTYPES)) {
if (unlikely(is_migrate_isolate(migratetype))) {
free_one_page(zone, page, pfn, order, fpi_flags);
return;
}
migratetype = MIGRATE_MOVABLE;
}
// PCP์ ์ถ๊ฐ
pcp = pcp_spin_trylock(zone->per_cpu_pageset, UP_flags);
if (pcp) {
free_frozen_page_commit(zone, pcp, page, migratetype,
order, fpi_flags, &UP_flags);
pcp_spin_unlock(pcp, UP_flags);
} else {
// PCP lock ์คํจ ์ buddy๋ก ์ง์ ๋ฐํ
free_one_page(zone, page, pfn, order, fpi_flags);
}
}
๋ถ๊ธฐ ๋ก์ง:
1. PCP ํ์ฉ order ์ด๊ณผ โ __free_pages_ok() (buddy ์ง์ ๋ฐํ)
2. ISOLATE ํ์
โ free_one_page() (์ฆ์ buddy ๋ฐํ)
3. PCP lock ์ฑ๊ณต โ free_frozen_page_commit() (PCP์ ์ถ๊ฐ)
4. PCP lock ์คํจ โ free_one_page() (buddy ์ง์ ๋ฐํ)
10. `free_frozen_page_commit()` โ PCP ๋ฐํ ๋ฐ ๋๋ ์ธ
// mm/page_alloc.c:2859-2959
static bool free_frozen_page_commit(struct zone *zone,
struct per_cpu_pages *pcp, struct page *page, int migratetype,
unsigned int order, fpi_t fpi_flags, unsigned long *UP_flags)
{
int high, batch;
int to_free, to_free_batched;
int pindex;
// PCP ํ ๋น ํฉํฐ ์ ๋ฐ์ผ๋ก ๊ฐ์
pcp->alloc_factor >>= 1;
// PCP ๋ฆฌ์คํธ์ ์ถ๊ฐ
pindex = order_to_pindex(migratetype, order);
list_add(&page->pcp_list, &pcp->lists[pindex]);
pcp->count += 1 << order;
// high-order ํ์ด์ง ๋๋ ์ธ ๊ฒ์ฌ
if (order && order <= PAGE_ALLOC_COSTLY_ORDER) {
free_high = (pcp->free_count >= (batch + pcp->high_min / 2) &&
(pcp->flags & PCPF_PREV_FREE_HIGH_ORDER) &&
(!(pcp->flags & PCPF_FREE_HIGH_BATCH) ||
pcp->count >= batch));
pcp->flags |= PCPF_PREV_FREE_HIGH_ORDER;
} else if (pcp->flags & PCPF_PREV_FREE_HIGH_ORDER) {
pcp->flags &= ~PCPF_PREV_FREE_HIGH_ORDER;
}
// PCP high ์ด๊ณผ ์ ๋๋ ์ธ
high = nr_pcp_high(pcp, zone, batch, free_high);
if (pcp->count < high)
return true;
// ์ด๊ณผ๋ถ์ buddy๋ก ๋ฐํ
to_free = nr_pcp_free(pcp, batch, high, free_high);
while (to_free > 0 && pcp->count > 0) {
to_free_batched = min(to_free, batch);
free_pcppages_bulk(zone, to_free_batched, pcp, pindex);
to_free -= to_free_batched;
}
// ZONE_BELOW_HIGH ํด์ ๊ฒ์ฌ
if (test_bit(ZONE_BELOW_HIGH, &zone->flags) &&
zone_watermark_ok(zone, 0, high_wmark_pages(zone),
ZONE_MOVABLE, 0)) {
clear_bit(ZONE_BELOW_HIGH, &zone->flags);
}
return true;
}
๋ถ๊ธฐ ๋ก์ง:
1. alloc_factor ์ ๋ฐ์ผ๋ก ๊ฐ์ (ํ ๋น ๋น๋ ์ต์ )
2. PCP ๋ฆฌ์คํธ์ ํ์ด์ง ์ถ๊ฐ
3. high-order ํ์ด์ง ์ฐ์ ๋๋ ์ธ ๊ฒ์ฌ
4. PCP high ์ด๊ณผ ์ free_pcppages_bulk()๋ก buddy์ ๋ฐํ
5. ์กด ์ํฐ๋งํฌ ์ถฉ์กฑ ์ ZONE_BELOW_HIGH ํ๋๊ทธ ํด์
๋ฐ์ดํฐ ๊ตฌ์กฐ ๊ณ์ธต
ํธ์ถ ํ๋ฆ
Slow Path ์์ธ ํ๋ฆ
PCP (Per-CPU Pages) ํ๋ฆ
alloc_pages(gfp, order)
โโ __alloc_pages_noprof() [mm/page_alloc.c:5279]
โโ __alloc_frozen_pages_noprof() [mm/page_alloc.c:5214]
โโ prepare_alloc_pages()
โโ get_page_from_freelist() [mm/page_alloc.c:3808] โ Fast Path
โ โโ for_each_zone_zonelist_nodemask()
โ โโ zone_watermark_fast()
โ โโ rmqueue()
โ โโ __rmqueue() [mm/page_alloc.c:2477]
โ โโ __rmqueue_smallest() โ 1์์
โ โ โโ get_page_from_free_area()
โ โ โโ page_del_and_expand()
โ โ โโ expand() โ ๋ถํ
โ โโ __rmqueue_cma_fallback() โ 2์์
โ โโ __rmqueue_claim() โ 3์์
โ โโ __rmqueue_steal() โ 4์์
โ โโ prep_new_page()
โโ __alloc_pages_slowpath() โ Slow Path (fast ์คํจ ์)
โโ wake_all_kswapds() โ kswapd ๋ฐฑ๊ทธ๋ผ์ด๋ ํ์ ํธ๋ฆฌ๊ฑฐ
โโ __alloc_pages_direct_reclaim() โ ์ง์ ํ์
โ โโ __perform_reclaim()
โ โ โโ try_to_free_pages()
โ โโ get_page_from_freelist()
โโ __alloc_pages_direct_compact() โ ์ง์ compaction
โโ __alloc_pages_may_oom() โ OOM Killer ๋ฐ๋
free_pages(addr, order)
โโ __free_pages() [mm/page_alloc.c:5367]
โโ ___free_pages()
โโ __free_frozen_pages() [mm/page_alloc.c:2964]
โโ pcp_allowed_order() ์ฒดํฌ
โ โโ ์ด๊ณผ ์ โ __free_pages_ok() (buddy ์ง์ ๋ฐํ)
โโ get_pfnblock_migratetype()
โ โโ ISOLATE โ free_one_page() (์ฆ์ ๋ฐํ)
โโ pcp_spin_trylock()
โโ ์ฑ๊ณต โ free_frozen_page_commit() โ PCP์ ์ถ๊ฐ
โ โโ pcp->alloc_factor ์ ๋ฐ์ผ๋ก ๊ฐ์
โ โโ PCP ๋ฆฌ์คํธ์ ์ถ๊ฐ
โ โโ PCP high ์ด๊ณผ ์ โ free_pcppages_bulk()
โ โโ ์กด ์ํฐ๋งํฌ ์ถฉ์กฑ ์ โ ZONE_BELOW_HIGH ํด์
โโ ์คํจ โ free_one_page() (buddy ์ง์ ๋ฐํ)
โโ __free_one_page() [mm/page_alloc.c:978]
โโ while (order < MAX_PAGE_ORDER)
โโ find_buddy_page_pfn()
โโ __del_page_from_free_list()
โโ __add_to_free_list() โ ๋ณํฉ ํ ์ถ๊ฐ
์กฐ๊ฑด๋ณ ๋น๊ต
ํ ๋น ํ๋๊ทธ (GFP)๋ณ ๋์
| GFP ํ๋๊ทธ | ์ํฐ๋งํฌ | direct reclaim | compaction | OOM | PCP ์ฌ์ฉ |
|---|---|---|---|---|---|
| `GFP_KERNEL` | LOW | O | O | O | O |
| `GFP_ATOMIC` | MIN (์์ฝ ์ ๊ทผ) | X | X | X | X |
| `GFP_NOWAIT` | LOW | X | X | X | O |
| `GFP_NOIO` | LOW | O (I/O ์ ์ธ) | O | O | O |
| `GFP_NOFS` | LOW | O (FS ์ ์ธ) | O | O | O |
| `GFP_DMA` | MIN | O | O | O | O |
Migratetype Fallback ์์
| ์์ฒญ ํ์ | 1์ฐจ fallback | 2์ฐจ fallback |
|---|---|---|
| UNMOVABLE | RECLAIMABLE | MOVABLE |
| MOVABLE | RECLAIMABLE | UNMOVABLE |
| RECLAIMABLE | UNMOVABLE | MOVABLE |
Zone Watermark ๋์
| ์กฐ๊ฑด | ๋์ |
|---|---|
| free โฅ HIGH | ์ ์ ํ ๋น ๊ฐ๋ฅ |
| LOW โค free < HIGH | kswapd wake (background reclaim) |
| MIN โค free < LOW | direct reclaim ๋ฐ์ ๊ฐ๋ฅ |
| free < MIN | OOM ๋๋ ํ ๋น ์คํจ |
์ธ๋ถ/๋ด๋ถ ๋จํธํ ๋น๊ต
| ์ข ๋ฅ | ์์ธ | ๋ํ ์งํ | ์ปค๋ ๋์ |
|---|---|---|---|
| ์ธ๋ถ ๋จํธํ | free ๊ณต๊ฐ์ด ์์ ์กฐ๊ฐ์ผ๋ก ํฉ์ด์ง | `/proc/buddyinfo`, `/proc/extfraginfo` | buddy ๋ณํฉ, compaction |
| ๋ด๋ถ ๋จํธํ | ์์ฒญ๋ณด๋ค ํฐ ๋ธ๋ก์ ํ ๋น | `/proc/slabinfo`, `kmalloc-*` ์ฌ์ฉ๋ฅ | SLUB size class, cache reuse |
Buddy ๊ด๋ จ API ๊ณ์ธต
| API | ๋ฐํ ํํ | ๋ฌผ๋ฆฌ ์ฐ์์ฑ | ๋ด๋ถ ๊ฒฝ๋ก | ์ฃผ ์ฉ๋ |
|---|---|---|---|---|
| `alloc_page(gfp)` | `struct page *` | 1 page ์ฐ์ | `alloc_pages(gfp, 0)` | ๋จ์ผ ํ์ด์ง ํ ๋น |
| `alloc_pages(gfp, order)` | `struct page *` | `2^order` pages ์ฐ์ | Buddy ์ง์ | ๊ณ ์ฐจ ํ์ด์ง/compound page ๊ธฐ๋ฐ |
| `__get_free_pages(gfp, order)` | `unsigned long` ์ฃผ์ | `2^order` pages ์ฐ์ | `alloc_pages()` + `page_address()` | ์ปค๋ ์ ํ ์ฃผ์๊ฐ ํ์ํ ์ ์์ค ๊ฒฝ๋ก |
| `kmalloc(size, gfp)` | `void *` | ๋ฌผ๋ฆฌ ์ฐ์ | SLUB โ Buddy | ์์ ์ปค๋ ์ค๋ธ์ ํธ |
| `vmalloc(size)` | `void *` | ๋ฌผ๋ฆฌ ๋ถ์ฐ์, ๊ฐ์ ์ฐ์ | ํ์ด์ง๋ณ Buddy โ vmap | ํฐ ๋ฒํผ, ๋ฌผ๋ฆฌ ์ฐ์ ๋ถํ์ |
GFP ์ปจํ ์คํธ ์ ์ฝ
| ํธ์ถ ์ปจํ ์คํธ | ๊ถ์ฅ GFP | ์ฌ๋ฆฝ ๊ฐ๋ฅ | Buddy ๋์์ ์๋ฏธ |
|---|---|---|---|
| ์ผ๋ฐ ํ๋ก์ธ์ค ์ปจํ ์คํธ | `GFP_KERNEL` | O | direct reclaim, compaction, OOM๊น์ง ์ง์ ๊ฐ๋ฅ |
| ์ธํฐ๋ฝํธ/hardirq | `GFP_ATOMIC` | X | ์์ฝ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ๊ฐ๋ฅํ์ง๋ง ์คํจ ๊ฐ๋ฅ์ฑ์ด ๋์ |
| softirq/timer | `GFP_ATOMIC` | X | reclaim์ผ๋ก ์ ๋ค ์ ์์ผ๋ฏ๋ก fast/๊ธด๊ธ ๊ฒฝ๋ก ์์ฃผ |
| spinlock ๋ณด์ ์ค | `GFP_ATOMIC` | X | `GFP_KERNEL` ์ฌ์ฉ ์ scheduling while atomic ์ํ |
| block I/O ๋ด๋ถ | `GFP_NOIO` | O | ํ์๋ ๊ฐ๋ฅํ์ง๋ง I/O ์ฌ๊ท๋ฅผ ๋ง์ |
| ํ์ผ์์คํ ๋ด๋ถ | `GFP_NOFS` | O | FS ์ฌ์ง์ ์ ๋ง๊ณ ํ์ ๋ฒ์๋ฅผ ์ ํ |
์ฆ์๋ณ ์ง๋จ ํ
| ์ฆ์ | ์ฐ์ ์ ๊ฒ | Buddy ๊ด์ ํด์ | ํ์ ์กฐ์น |
|---|---|---|---|
| `page allocation failure: order:N` | `dmesg`, `/proc/buddyinfo` | ์ด free๋ ์์ด๋ ํด๋น order ์ด์์ ์ฐ์ ๋ธ๋ก ๋ถ์กฑ | compaction ์ํ, THP/CMA ์์ฒญ, migratetype ์ค์ผ ํ์ธ |
| ๊ณ ์ฐจ order๊ฐ ๊ณ์ 0 | `/proc/buddyinfo`, `/proc/extfraginfo` | ์ธ๋ถ ๋จํธํ ๋๋ MOVABLE ๋ถ์กฑ | `compact_*` vmstat, `compact_memory`, THP defrag ์ ์ฑ ํ์ธ |
| `allocstall` ์ฆ๊ฐ | `/proc/vmstat` | fast path ์คํจ ํ direct reclaim ๋น๋ฒ | reclaim ๋ณ๋ชฉ, working set, memcg limit ํ์ธ |
| PCP count ๊ณผ๋/๋ถ๊ท ํ | `/proc/zoneinfo` pagesets | CPU๋ณ ์บ์์ free page๊ฐ ๋จธ๋ฌผ๋ฌ zone free list ๊ฐ์์ฑ์ด ๋ฎ์ | drain ๋์, CPU hotplug, zone pressure ํ์ธ |
| OOM ์ `kswapd` ๊ณผํ์ฑ | `dmesg`, PSI, `pgscan/pgsteal` | watermark ์๋๋ก ๋จ์ด์ ธ ํ์์ ํ ๋น์ด ๊ฒฝํฉ | slab/page cache ์ฆ๊ฐ, swap/zswap, memcg ์ด๋ฒคํธ ํ์ธ |
Linux 7.0 ์๋ฌธ ์์ธ
Buddy PFN ๊ณ์ฐ๊ณผ ๊ฒ์ฆ
buddy_pfn = pfn ^ (1 << order) ๊ณต์์ mm/internal.h์ ๊ทธ๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ๊ฐ์ order์์ ํ ๋นํธ๋ง ๋ค์ง์ผ๋ฉด ๊ฐ์ ์์ ๋ธ๋ก์ ๊ณต์ ํ๋ ์ธ์ buddy๋ฅผ O(1)์ ์ฐพ์ ์ ์์ต๋๋ค.
// mm/internal.h:755-759
static inline unsigned long
__find_buddy_pfn(unsigned long page_pfn, unsigned int order)
{
return page_pfn ^ (1 << order);
}
// mm/internal.h:775-788
static inline struct page *find_buddy_page_pfn(struct page *page,
unsigned long pfn, unsigned int order, unsigned long *buddy_pfn)
{
unsigned long __buddy_pfn = __find_buddy_pfn(pfn, order);
struct page *buddy;
buddy = page + (__buddy_pfn - pfn);
if (buddy_pfn)
*buddy_pfn = __buddy_pfn;
if (page_is_buddy(page, buddy, order))
return buddy;
return NULL;
}
๋ถํ (split) ์๋ฌธ
expand()๋ ์์ order ๋ธ๋ก์ ๋ฎ์ order๋ก ๋ด๋ฆฌ๋ฉด์ ๋ค์ชฝ half๋ฅผ free list์ ์ถ๊ฐํฉ๋๋ค. set_page_guard()๊ฐ true์ด๋ฉด debug guard page๋ก ๋จ๊ธฐ๊ณ free list ์ถ๊ฐ๋ฅผ ๊ฑด๋๋๋๋ค.
// mm/page_alloc.c:1732-1758
static inline unsigned int expand(struct zone *zone, struct page *page, int low,
int high, int migratetype)
{
unsigned int size = 1 << high;
unsigned int nr_added = 0;
while (high > low) {
high--;
size >>= 1;
VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);
/*
* Mark as guard pages (or page), that will allow to
* merge back to allocator when buddy will be freed.
* Corresponding page table entries will not be touched,
* pages will stay not present in virtual address space
*/
if (set_page_guard(zone, &page[size], high))
continue;
__add_to_free_list(&page[size], zone, high, migratetype, false);
set_buddy_order(&page[size], high);
nr_added += size;
}
return nr_added;
}
๋ณํฉ(coalescing) ์๋ฌธ
ํด์ ๊ฒฝ๋ก๋ ๊ฐ์ order์ buddy๊ฐ free์ธ์ง ํ์ธํ๊ณ , ๋ณํฉ ๊ฐ๋ฅํ migratetype์ด๋ฉด ๊ธฐ์กด buddy๋ฅผ free list์์ ์ ๊ฑฐํ ๋ค order๋ฅผ ํ๋์ฉ ์ฌ๋ฆฝ๋๋ค.
// mm/page_alloc.c:998-1059
while (order < MAX_PAGE_ORDER) {
int buddy_mt = migratetype;
if (compaction_capture(capc, page, order, migratetype)) {
account_freepages(zone, -(1 << order), migratetype);
return;
}
buddy = find_buddy_page_pfn(page, pfn, order, &buddy_pfn);
if (!buddy)
goto done_merging;
if (unlikely(order >= pageblock_order)) {
/*
* We want to prevent merge between freepages on pageblock
* without fallbacks and normal pageblock. Without this,
* pageblock isolation could cause incorrect freepage or CMA
* accounting or HIGHATOMIC accounting.
*/
buddy_mt = get_pfnblock_migratetype(buddy, buddy_pfn);
if (migratetype != buddy_mt &&
(!migratetype_is_mergeable(migratetype) ||
!migratetype_is_mergeable(buddy_mt)))
goto done_merging;
}
/*
* Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
* merge with it and move up one order.
*/
if (page_is_guard(buddy))
clear_page_guard(zone, buddy, order);
else
__del_page_from_free_list(buddy, zone, order, buddy_mt);
if (unlikely(buddy_mt != migratetype)) {
/*
* Match buddy type. This ensures that an
* expand() down the line puts the sub-blocks
* on the right freelists.
*/
change_pageblock_range(buddy, order, migratetype);
}
combined_pfn = buddy_pfn & pfn;
page = page + (combined_pfn - pfn);
pfn = combined_pfn;
order++;
}
done_merging:
set_buddy_order(page, order);
if (fpi_flags & FPI_TO_TAIL)
to_tail = true;
else if (is_shuffle_order(order))
to_tail = shuffle_pick_tail();
else
to_tail = buddy_merge_likely(pfn, buddy_pfn, page, order);
__add_to_free_list(page, zone, order, migratetype, to_tail);
PCP ํ ๋น ์๋ฌธ
Linux 7.0์ rmqueue()๋ pcp_allowed_order(order)์ด๋ฉด ๋จผ์ per-CPU list๋ฅผ ์๋ํ๊ณ , ์คํจํ๊ฑฐ๋ PCP ๋์ order๊ฐ ์๋๋ฉด buddy ๊ฒฝ๋ก(rmqueue_buddy)๋ก ๋ด๋ ค๊ฐ๋๋ค.
// mm/page_alloc.c:3336-3363
struct page *__rmqueue_pcplist(struct zone *zone, unsigned int order,
int migratetype,
unsigned int alloc_flags,
struct per_cpu_pages *pcp,
struct list_head *list)
{
struct page *page;
do {
if (list_empty(list)) {
int batch = nr_pcp_alloc(pcp, zone, order);
int alloced;
alloced = rmqueue_bulk(zone, order,
batch, list,
migratetype, alloc_flags);
pcp->count += alloced << order;
if (unlikely(list_empty(list)))
return NULL;
}
page = list_first_entry(list, struct page, pcp_list);
list_del(&page->pcp_list);
pcp->count -= 1 << order;
} while (check_new_pages(page, order));
return page;
}
// mm/page_alloc.c:3417-3425
if (likely(pcp_allowed_order(order))) {
page = rmqueue_pcplist(preferred_zone, zone, order,
migratetype, alloc_flags);
if (likely(page))
goto out;
}
page = rmqueue_buddy(preferred_zone, zone, order, alloc_flags,
migratetype);
Slow path ํต์ฌ ๋ถ๊ธฐ ์๋ฌธ
slow path๋ ๋จผ์ ์กฐ์ ๋ alloc_flags๋ก ๋ค์ fast path๋ฅผ ์๋ํ๊ณ , ์คํจํ๋ฉด direct reclaim, direct compaction, ์ฌ์๋ ํ๋จ, OOM, __GFP_NOFAIL ์ฒ๋ฆฌ ์์๋ก ์งํํฉ๋๋ค.
// mm/page_alloc.c:4796-4807
retry:
/* ๋ฃจํํ๋ ๋์ kswapd๊ฐ ์ค์๋ก ์ ๋ค์ง ์๋๋ก ๋ณด์ฅ */
if (alloc_flags & ALLOC_KSWAPD)
wake_all_kswapds(order, gfp_mask, ac);
/*
* The adjusted alloc_flags might result in immediate success, so try
* that first
*/
page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
if (page)
goto got_pg;
// mm/page_alloc.c:4844-4856
/* ์ง์ ํ์๋ฅผ ์๋ํ ํ ํ ๋น */
if (!compact_first) {
page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags,
ac, &did_some_progress);
if (page)
goto got_pg;
}
/* ์ง์ ์ปดํฉ์
์ ์๋ํ ํ ํ ๋น */
page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,
compact_priority, &compact_result);
if (page)
goto got_pg;
// mm/page_alloc.c:4906-4925
if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags,
did_some_progress > 0, &no_progress_loops))
goto retry;
/*
* It doesn't make any sense to retry for the compaction if the order-0
* reclaim is not able to make any progress because the current
* implementation of the compaction depends on the sufficient amount
* of free memory (see __compaction_suitable)
*/
if (did_some_progress > 0 && can_compact &&
should_compact_retry(ac, order, alloc_flags,
compact_result, &compact_priority,
&compaction_retries))
goto retry;
/* ํ์/์ปดํฉ์
์ผ๋ก ๋์ฒด๋ฅผ ๋ฐฉ์งํ์ง ๋ชปํจ */
if (defrag_mode && (alloc_flags & ALLOC_NOFRAGMENT)) {
alloc_flags &= ~ALLOC_NOFRAGMENT;
goto retry;
}
// mm/page_alloc.c:4936-4939
/* ํ์์ ์คํจํจ, ํ๋ก์ธ์ค ์ข
๋ฃ ์์ */
page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress);
if (page)
goto got_pg;
// mm/page_alloc.c:4966-4987
if (unlikely(nofail)) {
/*
* Lacking direct_reclaim we can't do anything to reclaim memory,
* we disregard these unreasonable nofail requests and still
* return NULL
*/
if (!can_direct_reclaim)
goto fail;
/*
* Help non-failing allocations by giving some access to memory
* reserves normally used for high priority non-blocking
* allocations but do not use ALLOC_NO_WATERMARKS because this
* could deplete whole memory reserves which would just make
* the situation worse.
*/
page = __alloc_pages_cpuset_fallback(gfp_mask, order, ALLOC_MIN_RESERVE, ac);
if (page)
goto got_pg;
cond_resched();
goto retry;
}