22. ๋ฉ๋ชจ๋ฆฌ Hotplug
๊ด๋ จ ์์ค:mm/memory_hotplug.c,include/linux/memory_hotplug.h,include/linux/memory.h
๊ด๋ จ ๋ฌธ์: 00-๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ฐ์ ยท 04-Memblock ยท 13-NUMA ยท 11-Compaction ยท 20-Migration ยท 18-CMA ยท 24-ํฐ์ด๋ง
๊ฐ์ (Overview)
๋ฉ๋ชจ๋ฆฌ Hotplug๋ ๋ฐํ์์ ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ปค๋์ ์ถ๊ฐ(Hot-Add)ํ๊ฑฐ๋ ์ ๊ฑฐ(Hot-Remove)ํ๋ ๋ฉ์ปค๋์ฆ์
๋๋ค. ๊ฐ์ํ ํ๊ฒฝ(KVM, Hyper-V, Xen), CXL ์ฅ์น, DIMM ์ฌ๋กฏ ๊ต์ฒด ์์คํ
์์ ๋ฉ๋ชจ๋ฆฌ ์ฉ๋์ ๋์ ์ผ๋ก ์กฐ์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. Linux 7.0์์๋ mm/memory_hotplug.c๊ฐ ์ด ์ ์ฒด ๋ผ์ดํ์ฌ์ดํด์ ๊ด๋ฆฌํ๋ฉฐ, ACPI ์ด๋ฒคํธ, sysfs, virtio-mem ๊ฐ์ ๋ค์ํ ํธ๋ฆฌ๊ฑฐ๋ฅผ ์ง์ํฉ๋๋ค.
๋ฉ๋ชจ๋ฆฌ Hotplug๋ ํฌ๊ฒ ๋ ๋จ๊ณ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. (1) Hot-Add: ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ๋ ์ธ์ง๊ฐ ์ปค๋์ ๋ฑ๋ก๋๊ณ struct page ๋ฐฐ์ด(vmemmap)์ด ์์ฑ๋ฉ๋๋ค. ์ด ์์ ์์ ๋ฉ๋ชจ๋ฆฌ๋ ์์ง ์ฌ์ฉ ๋ถ๊ฐ ์ํ์
๋๋ค. (2) Online: ๋ฑ๋ก๋ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก์ Buddy Allocator์ free list์ ์ถ๊ฐํ์ฌ ํ ๋น ๊ฐ๋ฅํ ์ํ๋ก ๋ง๋ญ๋๋ค. ๋ฐ๋๋ก ์ ๊ฑฐ ์์๋ Offline(๋ชจ๋ ํ์ด์ง ๋ง์ด๊ทธ๋ ์ด์
) โ Hot-Remove(์ปค๋ ๋ฑ๋ก ํด์ ) ์์๋ก ์งํ๋ฉ๋๋ค. ์คํ๋ผ์ธ ์คํจ์ ์ฃผ๋ ์์ธ์ MIGRATE_UNMOVABLE ํ์
์ ํ์ด์ง(์ปค๋ slab ๊ฐ์ฒด, ํ์ด์ง ํ
์ด๋ธ ๋ฑ)๊ฐ ํด๋น ๋ธ๋ก์ ์กด์ฌํ๋ ๊ฒฝ์ฐ์ด๋ฉฐ, ์ด๋ฅผ ๋ฐฉ์งํ๋ ค๋ฉด ํซํ๋ฌ๊ทธ ๋์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ZONE_MOVABLE์ ๋ฐฐ์นํด์ผ ํฉ๋๋ค.
์ผ์์ ์ผ๋ก ๋ณด๋ฉด ๋ฉ๋ชจ๋ฆฌ Hotplug๋ ์ด์ ์ค์ธ ๋์๊ด์ ์ ์๊ฐ๋ฅผ ๊ฝ๊ฑฐ๋ ๋ค์ ๋นผ๋ ์์
๊ณผ ๋น์ทํฉ๋๋ค. ์๊ฐ๋ฅผ ๋ค์ฌ๋๋ ๊ฒ๋ง์ผ๋ก๋ ์ฑ
์ ๋ฐ๋ก ๋น๋ฆด ์ ์๊ณ (Hot-Add), ์๊ฐ ์์ ์ฑ
์ ๋ค๋ฅธ ๊ตฌ์ญ์ผ๋ก ๋ชจ๋ ์ฎ๊ฒจ์ผ ์์ ํ๊ฒ ๋น์ธ ์ ์์ต๋๋ค(Offline โ Hot-Remove). kernelcore=์ movablecore=๋ ์ฒ์๋ถํฐ ์๊ฐ์ ๋น์จ์ ๋๋๋ ๋ถํ
ํ๋ผ๋ฏธํฐ์ด๊ณ , online_policy์ /sys/devices/system/memory/auto_online_blocks๋ ์๋ก ๋ค์ด์จ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ด๋ ๊ตฌ์ญ์ ๋์ง ์ ํ๋ ์ด์ ์ค์์น์
๋๋ค.
/* ์ฃผ์ ์์ค ํ์ผ ๊ฒฝ๋ก */
mm/memory_hotplug.c /* ํต์ฌ Hotplug ๋ก์ง (2435์ค) */
include/linux/memory_hotplug.h /* API ์ ์ธ, mhp_params, MMOP_* ์ด๊ฑฐ */
include/linux/memory.h /* struct memory_block, memory_group ์ ์ */
mm/sparse.c /* SPARSEMEM ์น์
๊ด๋ฆฌ (sparse_add_section) */
drivers/base/memory.c /* sysfs ์ธํฐํ์ด์ค, memory_block ๋๋ฐ์ด์ค */
๋น ๋ฅธ ์ ๊ฒ ๋ช ๋ น
# ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ์ํ ์ ์ฒด ํ์ธ
cat /sys/devices/system/memory/memory*/state | sort | uniq -c | sort -rn
# ํน์ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ์จ๋ผ์ธ/์คํ๋ผ์ธ
echo online > /sys/devices/system/memory/memory32/state
echo offline > /sys/devices/system/memory/memory32/state
# ZONE_MOVABLE ์จ๋ผ์ธ (๋์ค์ ์ ๊ฑฐ ๊ฐ๋ฅ)
echo online_movable > /sys/devices/system/memory/memory32/state
# ์๋ ์จ๋ผ์ธ ์ ์ฑ
ํ์ธ
cat /sys/devices/system/memory/auto_online_blocks
# online_movable / online_kernel / offline
# ํ์ฌ ๋ฉ๋ชจ๋ฆฌ Hotplug ๊ด๋ จ ์ปค๋ ํ๋ผ๋ฏธํฐ
cat /proc/cmdline | tr ' ' '\n' | grep -E 'memhp|movable|hotplug'
# kernelcore/movablecore/movable_node ์ค์ ํ์ธ
cat /proc/cmdline | tr ' ' '\n' | grep -E 'kernelcore|movablecore|memhp_default_state|movable_node'
# NUMA ๋
ธ๋๋ณ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ์ ๋ณด
cat /sys/devices/system/node/node*/meminfo | head -20
# ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ์ ์ฒด ๋งต (/proc/iomem์์ System RAM ํ์ธ)
sudo cat /proc/iomem | grep -i "System RAM"
# ๋ฉ๋ชจ๋ฆฌ Hotplug ๊ด๋ จ ์ปค๋ ๋ก๊ทธ
dmesg | grep -Ei 'hotplug|memory block|online|offline'
# ์ปค๋ ์ค์ ์์ Hotplug ๊ด๋ จ ์ต์
ํ์ธ
zgrep -E 'CONFIG_MEMORY_HOTPLUG|CONFIG_MEMORY_HOTREMOVE|CONFIG_MHP_MEMMAP_ON_MEMORY' /proc/config.gz 2>/dev/null
# ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ํฌ๊ธฐ ํ์ธ (๋ณดํต 128MB)
cat /sys/devices/system/memory/block_size_bytes | xargs printf "%d\n"
# ์๋ ์จ๋ผ์ธ ๋น์จ ์ค์
cat /sys/module/memory_hotplug/parameters/auto_movable_ratio
cat /sys/module/memory_hotplug/parameters/online_policy
# memmap_on_memory ๋ชจ๋ ํ์ธ
cat /sys/module/memory_hotplug/parameters/memmap_on_memory
ํต์ฌ ์๋ฃ๊ตฌ์กฐ
struct memory_block โ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ๋๋ฐ์ด์ค
๋ฉ๋ชจ๋ฆฌ Hotplug์ ๊ธฐ๋ณธ ๋จ์์
๋๋ค. sysfs์ /sys/devices/system/memory/memoryN์ผ๋ก ๋
ธ์ถ๋๋ฉฐ, ํ๋์ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก์ ํ๋ ์ด์์ SPARSEMEM ์น์
์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
/* include/linux/memory.h:77-96 */
struct memory_block {
unsigned long start_section_nr; /* ์์ ์น์
๋ฒํธ */
enum memory_block_state state; /* MEM_ONLINE, MEM_OFFLINE ๋ฑ */
int online_type; /* MMOP_ONLINE_KERNEL ๋ฑ */
int nid; /* ์ด ๋ธ๋ก์ NUMA ๋
ธ๋ ID */
struct zone *zone; /* ๋จ์ผ zone (์์ผ๋ฉด NULL) */
struct device dev; /* sysfs ๋๋ฐ์ด์ค ๊ตฌ์กฐ์ฒด */
struct vmem_altmap *altmap; /* memmap_on_memory์ฉ ๋์ฒด ๋งต */
struct memory_group *group; /* ์์ ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ฃน */
struct list_head group_next; /* ๊ทธ๋ฃน ๋ด ๋ค์ ๋ธ๋ก */
#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)
atomic_long_t nr_hwpoison; /* ํ๋์จ์ด ์ค๋ฅ ํ์ด์ง ์ */
#endif
};
struct memory_group โ ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ฃน
ํ๋์ ๋ฉ๋ชจ๋ฆฌ ๋๋ฐ์ด์ค(DIMM, CXL ๋ฆฌ์ ๋ฑ)๊ฐ ๊ด๋ฆฌํ๋ ์ฌ๋ฌ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก์ ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ฌถ์ต๋๋ค. ์ ์ (static)๊ณผ ๋์ (dynamic) ๋ ๊ฐ์ง ์ ํ์ด ์์ต๋๋ค.
/* include/linux/memory.h:51-65 */
struct memory_group {
int nid; /* ๊ทธ๋ฃน ์ ์ฒด ๋
ธ๋ ID */
struct list_head memory_blocks; /* ์์ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ๋ชฉ๋ก */
unsigned long present_kernel_pages; /* ZONE_MOVABLE ์ธ ์จ๋ผ์ธ ํ์ด์ง ์ */
unsigned long present_movable_pages; /* ZONE_MOVABLE ๋ด ์จ๋ผ์ธ ํ์ด์ง ์ */
bool is_dynamic; /* ์ ์ vs ๋์ ๊ทธ๋ฃน */
union {
struct {
unsigned long max_pages; /* ์ ์ : ์ต๋ ํ์ด์ง ์ */
} s;
struct {
unsigned long unit_pages; /* ๋์ : ๋จ์ ํ์ด์ง ์ (์ถ๊ฐ/์ ๊ฑฐ ๋จ์) */
} d;
};
};
struct mhp_params โ Hot-Add ํ์ฅ ํ๋ผ๋ฏธํฐ
add_memory_resource()์ ์ ๋ฌ๋๋ ๋งค๊ฐ๋ณ์๋ฅผ ๋ด์ต๋๋ค. ํ์ด์ง ํ
์ด๋ธ ๋ณดํธ ํ๋๊ทธ, ๋์ฒด memmap ํ ๋น์, ZONE_DEVICE ๋งต์ ์ง์ ํฉ๋๋ค.
/* include/linux/memory_hotplug.h:68-72 */
struct mhp_params {
struct vmem_altmap *altmap; /* memmap ๋ฐฐ์ด์ ๋์ฒด ํ ๋น์ (์ ํ) */
pgprot_t pgprot; /* ์ ํ์ด์ง ํ
์ด๋ธ์ ๋ณดํธ ํ๋๊ทธ (ํ์) */
struct dev_pagemap *pgmap; /* ZONE_DEVICE์ฉ ํ์ด์ง ๋งต (์ ํ) */
};
enum memory_block_state โ ๋ธ๋ก ์ํ ๋จธ์
sysfs๋ฅผ ํตํด ์ฌ์ฉ์ ๊ณต๊ฐ์ ํ ์คํธ๋ก ๋ ธ์ถ๋๋ ์ํ์ ๋๋ค.
/* include/linux/memory.h:67-75 */
enum memory_block_state {
MEM_ONLINE, /* ์จ๋ผ์ธ โ Buddy free list์ ์ถ๊ฐ๋จ */
MEM_GOING_OFFLINE, /* ์คํ๋ผ์ธ ์งํ ์ค */
MEM_OFFLINE, /* ์คํ๋ผ์ธ โ ์ฌ์ฉ ๋ถ๊ฐ */
MEM_GOING_ONLINE, /* ์จ๋ผ์ธ ์งํ ์ค */
MEM_CANCEL_ONLINE, /* ์จ๋ผ์ธ ์ทจ์๋จ */
MEM_CANCEL_OFFLINE, /* ์คํ๋ผ์ธ ์ทจ์๋จ */
};
๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ์ํ ์ ์ด
| ์ ์ด | ํธ๋ฆฌ๊ฑฐ | ํต์ฌ ๋์ |
|---|---|---|
| Hot-Add โ Registered | `add_memory_resource()` | memblock ์ถ๊ฐ, memory_block ๋๋ฐ์ด์ค ์์ฑ, ์์ง ํ ๋น ๋ถ๊ฐ |
| Registered โ Online | `online_pages()` | zone ์ฐ๊ฒฐ, Buddy free list ์ถ๊ฐ, ์ํฐ๋งํฌ/kswapd/kcompactd ๊ฐฑ์ |
| Online โ Offline | `offline_pages()` | ํ์ด์ง ๋ง์ด๊ทธ๋ ์ด์ , ๊ฒฉ๋ฆฌ, Buddy ์ ๊ฑฐ |
| Offline โ Hot-Remove | `try_remove_memory()` | memory_block ๋๋ฐ์ด์ค ์ ๊ฑฐ, arch ๋ฉ๋ชจ๋ฆฌ ํด์ , memblock ์ ๋ฆฌ |
MMOP_* ์จ๋ผ์ธ ํ์ ์ด๊ฑฐ
๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก์ ์ด๋ค Zone์ ์จ๋ผ์ธ์ํฌ์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
/* include/linux/memory_hotplug.h:23-32 */
enum {
MMOP_OFFLINE = 0, /* ์คํ๋ผ์ธ */
MMOP_ONLINE, /* ๊ธฐ๋ณธ ์จ๋ผ์ธ โ zone ์๋ ๊ฒฐ์ */
MMOP_ONLINE_KERNEL, /* ZONE_NORMAL(์ปค๋)์ ์จ๋ผ์ธ */
MMOP_ONLINE_MOVABLE, /* ZONE_MOVABLE์ ์จ๋ผ์ธ (์ ๊ฑฐ ๊ฐ๋ฅ) */
};
auto_movable_stats โ ์๋ MOVABLE ํต๊ณ
auto-movable ์จ๋ผ์ธ ์ ์ฑ
์์ MOVABLE:KERNEL ๋น์จ์ ํ๋จํ๊ธฐ ์ํ ํต๊ณ ๊ตฌ์กฐ์
๋๋ค.
/* mm/memory_hotplug.c:789-792 */
struct auto_movable_stats {
unsigned long kernel_early_pages; /* ๋ถํ
์ด๊ธฐ ์ปค๋ ์์ญ ํ์ด์ง ์ */
unsigned long movable_pages; /* ZONE_MOVABLE ํ์ด์ง ์ */
};
ํต์ฌ ํจ์
1. online_pages() โ ๋ฉ๋ชจ๋ฆฌ ์จ๋ผ์ธ
move_pfn_range_to_zone()๋ก pfn ๋ฒ์๋ฅผ zone์ ์ฐ๊ฒฐํ ๋ค, online_pages_range()๋ฅผ ํตํด Buddy์ ํ์ด์ง๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์จ๋ผ์ธ ์๋ฃ ํ zonelist ์ฌ๊ตฌ์ฑ, watermarks ์ด๊ธฐํ, kswapd/kcompactd ์์์ ์ํํฉ๋๋ค.
/* mm/memory_hotplug.c:1144-1257 */
int online_pages(unsigned long pfn, unsigned long nr_pages,
struct zone *zone, struct memory_group *group)
{
struct memory_notify mem_arg = {
.start_pfn = pfn,
.nr_pages = nr_pages,
};
struct node_notify node_arg = {
.nid = NUMA_NO_NODE,
};
const int nid = zone_to_nid(zone);
int need_zonelists_rebuild = 0;
unsigned long flags;
int ret;
if (WARN_ON_ONCE(!nr_pages || !pageblock_aligned(pfn) ||
!IS_ALIGNED(pfn + nr_pages, PAGES_PER_SECTION)))
return -EINVAL;
move_pfn_range_to_zone(zone, pfn, nr_pages, NULL, MIGRATE_MOVABLE,
true);
if (!node_state(nid, N_MEMORY)) {
node_arg.nid = nid;
ret = node_notify(NODE_ADDING_FIRST_MEMORY, &node_arg);
ret = notifier_to_errno(ret);
if (ret)
goto failed_addition;
}
ret = memory_notify(MEM_GOING_ONLINE, &mem_arg);
ret = notifier_to_errno(ret);
if (ret)
goto failed_addition;
spin_lock_irqsave(&zone->lock, flags);
zone->nr_isolate_pageblock += nr_pages / pageblock_nr_pages;
spin_unlock_irqrestore(&zone->lock, flags);
if (!populated_zone(zone)) {
need_zonelists_rebuild = 1;
setup_zone_pageset(zone);
}
online_pages_range(pfn, nr_pages);
adjust_present_page_count(pfn_to_page(pfn), group, nr_pages);
if (node_arg.nid >= 0)
node_set_state(nid, N_MEMORY);
if (!node_state(nid, N_NORMAL_MEMORY) && zone_idx(zone) <= ZONE_NORMAL)
node_set_state(nid, N_NORMAL_MEMORY);
if (need_zonelists_rebuild)
build_all_zonelists(NULL);
undo_isolate_page_range(pfn, pfn + nr_pages);
shuffle_zone(zone);
init_per_zone_wmark_min();
kswapd_run(nid);
kcompactd_run(nid);
if (node_arg.nid >= 0)
node_notify(NODE_ADDED_FIRST_MEMORY, &node_arg);
writeback_set_ratelimit();
memory_notify(MEM_ONLINE, &mem_arg);
return 0;
failed_addition:
remove_pfn_range_from_zone(zone, pfn, nr_pages);
return ret;
}
๋ถ๊ธฐ ๋ก์ง:
!node_state(nid, N_MEMORY) โ ์ต์ด ๋ฉ๋ชจ๋ฆฌ ์ถ๊ฐ ๋
ธ๋ ์๋ฆผ!populated_zone(zone) โ zonelist ์ฌ๊ตฌ์ฑ + pageset ์ค์ remove_pfn_range_from_zone()์ผ๋ก ๋กค๋ฐฑ2. offline_pages() โ ๋ฉ๋ชจ๋ฆฌ ์คํ๋ผ์ธ
๋ชจ๋ ํ์ด์ง๋ฅผ ๋ค๋ฅธ ๋ธ๋ก์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ ๋ค, Buddy์์ ์ ๊ฑฐํฉ๋๋ค. PCP ๋นํ์ฑํ, LRU ์บ์ ๋นํ์ฑํ, ํ์ด์ง ๊ฒฉ๋ฆฌ ์์๋ก ์งํ๋ฉ๋๋ค.
/* mm/memory_hotplug.c:1901-2117 */
int offline_pages(unsigned long start_pfn, unsigned long nr_pages,
struct zone *zone, struct memory_group *group)
{
unsigned long pfn, managed_pages, system_ram_pages = 0;
const unsigned long end_pfn = start_pfn + nr_pages;
struct pglist_data *pgdat = zone->zone_pgdat;
const int node = zone_to_nid(zone);
struct memory_notify mem_arg = {
.start_pfn = start_pfn,
.nr_pages = nr_pages,
};
struct node_notify node_arg = {
.nid = NUMA_NO_NODE,
};
unsigned long flags;
char *reason;
int ret;
unsigned long normal_pages = 0;
enum zone_type zt;
if (WARN_ON_ONCE(!nr_pages || !pageblock_aligned(start_pfn) ||
!IS_ALIGNED(start_pfn + nr_pages, PAGES_PER_SECTION)))
return -EINVAL;
walk_system_ram_range(start_pfn, nr_pages, &system_ram_pages,
count_system_ram_pages_cb);
if (system_ram_pages != nr_pages) {
ret = -EINVAL;
reason = "memory holes";
goto failed_removal;
}
if (WARN_ON_ONCE(page_zone(pfn_to_page(start_pfn)) != zone ||
page_zone(pfn_to_page(end_pfn - 1)) != zone)) {
ret = -EINVAL;
reason = "multizone range";
goto failed_removal;
}
zone_pcp_disable(zone);
lru_cache_disable();
ret = start_isolate_page_range(start_pfn, end_pfn,
PB_ISOLATE_MODE_MEM_OFFLINE);
if (ret) {
reason = "failure to isolate range";
goto failed_removal_pcplists_disabled;
}
if (nr_pages >= pgdat->node_present_pages) {
node_arg.nid = node;
ret = node_notify(NODE_REMOVING_LAST_MEMORY, &node_arg);
ret = notifier_to_errno(ret);
if (ret) {
reason = "node notifier failure";
goto failed_removal_isolated;
}
}
ret = memory_notify(MEM_GOING_OFFLINE, &mem_arg);
ret = notifier_to_errno(ret);
if (ret) {
reason = "notifier failure";
goto failed_removal_isolated;
}
do {
pfn = start_pfn;
do {
if (signal_pending(current)) {
ret = -EINTR;
reason = "signal backoff";
goto failed_removal_isolated;
}
cond_resched();
ret = scan_movable_pages(pfn, end_pfn, &pfn);
if (!ret)
do_migrate_range(pfn, end_pfn);
} while (!ret);
if (ret != -ENOENT) {
reason = "unmovable page";
goto failed_removal_isolated;
}
ret = dissolve_free_hugetlb_folios(start_pfn, end_pfn);
if (ret) {
reason = "failure to dissolve huge pages";
goto failed_removal_isolated;
}
ret = test_pages_isolated(start_pfn, end_pfn,
PB_ISOLATE_MODE_MEM_OFFLINE);
} while (ret);
managed_pages = __offline_isolated_pages(start_pfn, end_pfn);
spin_lock_irqsave(&zone->lock, flags);
zone->nr_isolate_pageblock -= nr_pages / pageblock_nr_pages;
spin_unlock_irqrestore(&zone->lock, flags);
lru_cache_enable();
zone_pcp_enable(zone);
adjust_managed_page_count(pfn_to_page(start_pfn), -managed_pages);
adjust_present_page_count(pfn_to_page(start_pfn), group, -nr_pages);
init_per_zone_wmark_min();
if (zone_idx(zone) <= ZONE_NORMAL) {
for (zt = 0; zt <= ZONE_NORMAL; zt++)
normal_pages += pgdat->node_zones[zt].present_pages;
if (!normal_pages)
node_clear_state(node, N_NORMAL_MEMORY);
}
if (node_arg.nid >= 0)
node_clear_state(node, N_MEMORY);
if (!populated_zone(zone)) {
zone_pcp_reset(zone);
build_all_zonelists(NULL);
}
if (node_arg.nid >= 0) {
kcompactd_stop(node);
kswapd_stop(node);
node_notify(NODE_REMOVED_LAST_MEMORY, &node_arg);
}
writeback_set_ratelimit();
memory_notify(MEM_OFFLINE, &mem_arg);
remove_pfn_range_from_zone(zone, start_pfn, nr_pages);
return 0;
failed_removal_isolated:
undo_isolate_page_range(start_pfn, end_pfn);
memory_notify(MEM_CANCEL_OFFLINE, &mem_arg);
if (node_arg.nid != NUMA_NO_NODE)
node_notify(NODE_CANCEL_REMOVING_LAST_MEMORY, &node_arg);
failed_removal_pcplists_disabled:
lru_cache_enable();
zone_pcp_enable(zone);
failed_removal:
return ret;
}
๋ถ๊ธฐ ๋ก์ง:
system_ram_pages != nr_pages โ ๋ฉ๋ชจ๋ฆฌ ํ ์กด์ฌ โ ์คํ๋ผ์ธ ๋ถ๊ฐsignal_pending(current) โ ์๊ทธ๋๋ก ์ธํ ์ค๋จ โ -EINTRscan_movable_pages() ๋ฐํ๊ฐ -EBUSY โ ์ด๋ ๋ถ๊ฐ ํ์ด์ง ์กด์ฌ โ ์คํจ3. add_memory_resource() โ ๋ฉ๋ชจ๋ฆฌ ์ถ๊ฐ (๋ฆฌ์์ค ๊ธฐ๋ฐ)
๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ๋ ์ธ์ง๊ฐ ์ปค๋์ ๋ฑ๋ก๋๋ ์ ์ฒด ๊ณผ์ ์ ๋ด๋นํฉ๋๋ค. memblock ์ถ๊ฐ, ๋ ธ๋ ์ด๊ธฐํ, arch ๋ฉ๋ชจ๋ฆฌ ์ถ๊ฐ, ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ๋๋ฐ์ด์ค ์์ฑ, ์จ๋ผ์ธ๊น์ง์ ์ ์ฒด ํ๋ฆ์ ์ ์ดํฉ๋๋ค.
/* mm/memory_hotplug.c:1498-1603 */
int add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
{
struct mhp_params params = { .pgprot = pgprot_mhp(PAGE_KERNEL) };
enum memblock_flags memblock_flags = MEMBLOCK_NONE;
struct memory_group *group = NULL;
u64 start, size;
bool new_node = false;
int ret;
start = res->start;
size = resource_size(res);
ret = check_hotplug_memory_range(start, size);
if (ret)
return ret;
if (mhp_flags & MHP_NID_IS_MGID) {
group = memory_group_find_by_id(nid);
if (!group)
return -EINVAL;
nid = group->nid;
}
if (!node_possible(nid)) {
WARN(1, "node %d was absent from the node_possible_map\n", nid);
return -EINVAL;
}
mem_hotplug_begin();
if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK)) {
if (res->flags & IORESOURCE_SYSRAM_DRIVER_MANAGED)
memblock_flags = MEMBLOCK_DRIVER_MANAGED;
ret = memblock_add_node(start, size, nid, memblock_flags);
if (ret)
goto error_mem_hotplug_end;
}
ret = __try_online_node(nid, false);
if (ret < 0)
goto error_memblock_remove;
if (ret) {
node_set_online(nid);
ret = register_node(nid);
if (WARN_ON(ret)) {
node_set_offline(nid);
goto error_memblock_remove;
}
new_node = true;
}
if ((mhp_flags & MHP_MEMMAP_ON_MEMORY) &&
mhp_supports_memmap_on_memory()) {
ret = create_altmaps_and_memory_blocks(nid, group, start, size);
if (ret)
goto error;
} else {
ret = arch_add_memory(nid, start, size, ¶ms);
if (ret < 0)
goto error;
ret = create_memory_block_devices(start, size, nid, NULL, group);
if (ret) {
arch_remove_memory(start, size, params.altmap);
goto error;
}
}
register_memory_blocks_under_node_hotplug(nid, PFN_DOWN(start),
PFN_UP(start + size - 1));
if (!strcmp(res->name, "System RAM"))
firmware_map_add_hotplug(start, start + size, "System RAM");
mem_hotplug_done();
if (mhp_flags & MHP_MERGE_RESOURCE)
merge_system_ram_resource(res);
if (mhp_get_default_online_type() != MMOP_OFFLINE)
walk_memory_blocks(start, size, NULL, online_memory_block);
return ret;
error:
if (new_node) {
node_set_offline(nid);
unregister_node(nid);
}
error_memblock_remove:
if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK))
memblock_remove(start, size);
error_mem_hotplug_end:
mem_hotplug_done();
return ret;
}
๋ถ๊ธฐ ๋ก์ง:
MHP_NID_IS_MGID โ nid ๋์ memory_group ID ์ฌ์ฉMHP_MEMMAP_ON_MEMORY + ์ง์ ๊ฐ๋ฅ โ altmap ๊ธฐ๋ฐ memmap ํ ๋นMHP_MERGE_RESOURCE โ ์ธ์ ๋ฆฌ์์ค์ ๋ณํฉ4. try_remove_memory() โ ๋ฉ๋ชจ๋ฆฌ ์ ๊ฑฐ
๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก์ด ์คํ๋ผ์ธ ์ํ์ธ์ง ํ์ธํ ๋ค, ๋ฆฌ์์ค ํด์ , ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ๋๋ฐ์ด์ค ์ ๊ฑฐ, arch ๋ฉ๋ชจ๋ฆฌ ์ ๊ฑฐ๋ฅผ ์ํํฉ๋๋ค.
/* mm/memory_hotplug.c:2237-2288 */
static int try_remove_memory(u64 start, u64 size)
{
int rc, nid = NUMA_NO_NODE;
BUG_ON(check_hotplug_memory_range(start, size));
rc = walk_memory_blocks(start, size, &nid, check_memblock_offlined_cb);
if (rc)
return rc;
firmware_map_remove(start, start + size, "System RAM");
mem_hotplug_begin();
rc = memory_blocks_have_altmaps(start, size);
if (rc < 0) {
mem_hotplug_done();
return rc;
} else if (!rc) {
remove_memory_block_devices(start, size);
arch_remove_memory(start, size, NULL);
} else {
remove_memory_blocks_and_altmaps(start, size);
}
if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK))
memblock_remove(start, size);
release_mem_region_adjustable(start, size);
if (nid != NUMA_NO_NODE)
try_offline_node(nid);
mem_hotplug_done();
return 0;
}
5. zone_for_pfn_range() โ ์จ๋ผ์ธ Zone ๊ฒฐ์
๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก์ ์ด๋ค Zone์ ์จ๋ผ์ธ์ํฌ์ง๋ฅผ ๊ฒฐ์ ํ๋ ์ ์ฑ
ํจ์์
๋๋ค. online_policy์ online_type์ ๋ฐ๋ผ ๋ถ๊ธฐ๋ฉ๋๋ค.
/* mm/memory_hotplug.c:1049-1062 */
struct zone *zone_for_pfn_range(int online_type, int nid,
struct memory_group *group, unsigned long start_pfn,
unsigned long nr_pages)
{
if (online_type == MMOP_ONLINE_KERNEL)
return default_kernel_zone_for_pfn(nid, start_pfn, nr_pages);
if (online_type == MMOP_ONLINE_MOVABLE)
return &NODE_DATA(nid)->node_zones[ZONE_MOVABLE];
if (online_policy == ONLINE_POLICY_AUTO_MOVABLE)
return auto_movable_zone_for_pfn(nid, group, start_pfn, nr_pages);
return default_zone_for_pfn(nid, start_pfn, nr_pages);
}
ํธ์ถ ํ๋ฆ
Hot-Add ํ๋ฆ (๋ฉ๋ชจ๋ฆฌ ์ถ๊ฐ)
๋๋ผ์ด๋ฒ/ACPI/sysfs ํธ๋ฆฌ๊ฑฐ
โโ add_memory() /* lock_device_hotplug() ํฌํจ */
โโ __add_memory()
โโ register_memory_resource() /* /proc/iomem์ ๋ฑ๋ก */
โโ add_memory_resource() /* ํต์ฌ ์ถ๊ฐ ๋ก์ง */
โโ memblock_add_node() /* memblock์ ๋ฑ๋ก */
โโ __try_online_node() /* ๋
ธ๋ ์ด๊ธฐํ */
โโ arch_add_memory() /* ์ํคํ
์ฒ๋ณ ํ์ด์ง ํ
์ด๋ธ ์์ฑ */
โโ create_memory_block_devices() /* sysfs ๋ธ๋ก ์์ฑ */
โโ online_memory_block() /* ์๋ ์จ๋ผ์ธ ์ */
โโ device_online()
โโ online_pages() /* Buddy์ ์ถ๊ฐ */
โโ move_pfn_range_to_zone()
โโ online_pages_range()
โ โโ online_page_callback() (= generic_online_page)
โ โโ __free_pages_core()
โโ build_all_zonelists()
โโ init_per_zone_wmark_min()
โโ kswapd_run() / kcompactd_run()
Hot-Remove ํ๋ฆ (๋ฉ๋ชจ๋ฆฌ ์ ๊ฑฐ)
๋๋ผ์ด๋ฒ/ACPI/sysfs ํธ๋ฆฌ๊ฑฐ
โโ remove_memory() / offline_and_remove_memory()
โโ try_remove_memory()
โโ check_memblock_offlined_cb() /* ๋ชจ๋ ๋ธ๋ก ์คํ๋ผ์ธ ํ์ธ */
โโ remove_memory_block_devices() / remove_memory_blocks_and_altmaps()
โโ arch_remove_memory() /* ํ์ด์ง ํ
์ด๋ธ ์ ๊ฑฐ */
โโ memblock_remove()
โโ try_offline_node() /* ๋
ธ๋ ์คํ๋ผ์ธ ์๋ */
offline_pages() /* ๊ฐ๋ณ ๋ธ๋ก ์คํ๋ผ์ธ */
โโ zone_pcp_disable() / lru_cache_disable()
โโ start_isolate_page_range() /* ํ์ด์ง ๊ฒฉ๋ฆฌ */
โโ scan_movable_pages() + do_migrate_range() /* ๋ฐ๋ณต ๋ง์ด๊ทธ๋ ์ด์
*/
โ โโ migrate_pages()
โโ dissolve_free_hugetlb_folios()
โโ test_pages_isolated()
โโ __offline_isolated_pages() /* Buddy์์ ์ ๊ฑฐ */
โโ remove_pfn_range_from_zone()
์กฐ๊ฑด๋ณ ๋น๊ต
์จ๋ผ์ธ ์ ์ฑ ๋น๊ต
| ์ ์ฑ | ์ค์ ์์น | ๋์ | ZONE_MOVABLE | ์ ๊ฑฐ ์ฉ์ด์ฑ |
|---|---|---|---|---|
| `contig-zones` | `online_policy` ๋ชจ๋ ํ๋ผ๋ฏธํฐ | ๊ธฐ์กด zone ๊ฒฝ๊ณ๋ฅผ ๋ฐ๋ผ ์ฐ์ ์จ๋ผ์ธ | ๊ธฐ๋ณธ ๋ถ๊ฐ | ์ด๋ ค์ |
| `auto-movable` | `online_policy` ๋ชจ๋ ํ๋ผ๋ฏธํฐ | MOVABLE:KERNEL ๋น์จ ๊ธฐ๋ฐ ์๋ ๊ฒฐ์ | ๊ฐ๋ฅ | ์ฌ์ |
| `online_movable` | sysfs state | ๋ช ์์ ์ผ๋ก ZONE_MOVABLE์ ์จ๋ผ์ธ | ํญ์ | ๊ฐ์ฅ ์ฌ์ |
| `online_kernel` | sysfs state | ๋ช ์์ ์ผ๋ก ZONE_NORMAL์ ์จ๋ผ์ธ | ๋ถ๊ฐ | ๋งค์ฐ ์ด๋ ค์ |
memmap_on_memory ๋ชจ๋ ๋น๊ต
| ๋ชจ๋ | ๋์ | ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น | vmemmap ์์น | ์ฌ์ฉ ์์ |
|---|---|---|---|---|
| DISABLE (0) | memmap์ ๊ธฐ์กด ๋ฉ๋ชจ๋ฆฌ์ ํ ๋น | ์์ | ์ผ๋ฐ ์ปค๋ ๋ฉ๋ชจ๋ฆฌ | ๊ธฐ๋ณธ๊ฐ |
| ENABLE (1) | Hotplug ๋ฉ๋ชจ๋ฆฌ ์์ฒด์ vmemmap ๋ฐฐ์น | ์์ | Hotplug ๋ฉ๋ชจ๋ฆฌ ๋ด๋ถ | CXL/๋์ฉ๋ DIMM |
| FORCE (2) | pageblock ์ ๋ ฌ ๋ง์ถฐ ๊ฐ์ ๋ฐฐ์น | ํ์ด์ง ๋ญ๋น ๊ฐ๋ฅ | Hotplug ๋ฉ๋ชจ๋ฆฌ ๋ด๋ถ | ์ ๋ ฌ ๋ฌธ์ ํด๊ฒฐ ํ์ ์ |
๋ฉ๋ชจ๋ฆฌ ์ ๊ฑฐ ์คํจ ์์ธ ๋น๊ต
| ์์ธ | ์ฆ์ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
|---|---|---|
| `MIGRATE_UNMOVABLE` ํ์ด์ง ์กด์ฌ | `scan_movable_pages()` โ `-EBUSY` | `online_movable`๋ก ์จ๋ผ์ธ ํ ์ฌ์๋ |
| ๋ฉ๋ชจ๋ฆฌ ํ ์กด์ฌ | `system_ram_pages != nr_pages` | ํ ์๋ ๋ฉ๋ชจ๋ฆฌ๋ง ์คํ๋ผ์ธ ๊ฐ๋ฅ |
| HugeTLB folio ๋ฏธํด์ฒด | `dissolve_free_hugetlb_folios()` ์คํจ | ์ฌ์ ์ HugeTLB ํด์ |
| ์คํ๋ผ์ธ ์ค ์๊ทธ๋ | `signal_pending()` โ `-EINTR` | ์ฌ์๋ ๋๋ ํ๋ก์ธ์ค ์ข ๋ฃ |
| CPU๊ฐ ๋ ธ๋์ ์์ฃผ | `check_cpu_on_node()` โ `-EBUSY` | CPU ๋จผ์ ์คํ๋ผ์ธ/์ด๋ |
๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ฃน ์ ํ ๋น๊ต
| ์ ํ | `is_dynamic` | ์ถ๊ฐ ๋จ์ | ์ ๊ฑฐ ๋จ์ | ๋ํ ์ฌ์ฉ์ฒ |
|---|---|---|---|---|
| ์ ์ (Static) | `false` | ๊ทธ๋ฃน ์ ์ฒด | ๊ทธ๋ฃน ์ ์ฒด | ์ ํต DIMM |
| ๋์ (Dynamic) | `true` | `unit_pages` ๋จ์ | `unit_pages` ๋จ์ | virtio-mem, CXL |
memmap_on_memory ์์ธ
Linux 7.0์์ ๋ฉ๋ชจ๋ฆฌ Hotplug ์ struct page ๋ฐฐ์ด(vmemmap)์ ์ด๋ป๊ฒ ํ ๋นํ ์ง ๊ฒฐ์ ํ๋ ๋ฉ์ปค๋์ฆ์
๋๋ค.
memmap_on_memory = 0 (DISABLE)
โ vmemmap์ ๊ธฐ์กด ์ปค๋ ๋ฉ๋ชจ๋ฆฌ(Direct Mapping ์์ญ)์ ํ ๋น
โ ์ฅ์ : ์ผ๋ฐ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
โ ๋จ์ : ๋์ฉ๋ Hotplug ์ ์ปค๋ ๋ฉ๋ชจ๋ฆฌ ์๋ชจ
memmap_on_memory = 1 (ENABLE)
โ Hotplug ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก์ ์๋ถ๋ถ์ vmemmap ๋ฐฐ์น
โ ์ฅ์ : ์ปค๋ ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ
โ ๋จ์ : ์ค์ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ ๊ฐ์
memmap_on_memory = 2 (FORCE)
โ ENABLE์ ๋์ผํ๋ pageblock ์ ๋ ฌ์ ๊ฐ์
โ ์ฅ์ : ์ ๋ ฌ ๋ฌธ์ ํด๊ฒฐ
โ ๋จ์ : ์ถ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น ๊ฐ๋ฅ
mhp_supports_memmap_on_memory() ํจ์๊ฐ ๋ค์ ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑํ ๋๋ง ํ์ฑํ๋ฉ๋๋ค:
1. memmap_on_memory ํ๋ผ๋ฏธํฐ๊ฐ ํ์ฑํ๋จ
2. vmemmap ํฌ๊ธฐ๊ฐ PAGE_SIZE๋ก ์ ๋ ฌ๋จ
3. vmemmap ํ์ด์ง๊ฐ pageblock_nr_pages๋ก ์ ๋ ฌ๋จ
4. vmemmap ํฌ๊ธฐ๊ฐ ์ ์ฒด ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก๊ณผ ๊ฐ์ง ์์ (์ ํจํ์ง ์์ ํซํ๋ฌ๊ทธ)
5. arch_supports_memmap_on_memory()๊ฐ ์ํคํ
์ฒ ์์ค์์ ์ง์
auto-movable ์จ๋ผ์ธ ์ ์ฑ ์์ธ
Linux 7.0์์ ๋ฉ๋ชจ๋ฆฌ ์จ๋ผ์ธ ์ ZONE_MOVABLE์ ์๋ ๋ฐฐ์นํ๋ ์ ์ฑ ์ ๋๋ค.
kernelcore=์ movablecore=๋ ๋ถํ
๋จ๊ณ์์ ์ปค๋ ๋ฉ๋ชจ๋ฆฌ์ ์ด๋ ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ์ค์ ์ ๋จผ์ ์ก์ต๋๋ค. ๊ทธ ์์์ movable_node์ online_policy="auto-movable"๊ฐ ์ค์ hotplug ๋ฉ๋ชจ๋ฆฌ์ ๋ฐฐ์น ๋ฐฉํฅ์ ๊ฒฐ์ ํ๊ณ , auto_movable_ratio์ auto_movable_numa_aware๊ฐ ์ธ๋ถ ๋น์จ์ ์กฐ์ ํฉ๋๋ค.
online_policy = "auto-movable"
โ MOVABLE : KERNEL ๋น์จ์ ๊ณ์ฐํ์ฌ ZONE_MOVABLE ํ ๋น ์ฌ๋ถ ๊ฒฐ์
๋น์จ ๊ณ์ฐ:
auto_movable_ratio (๊ธฐ๋ณธ 301%) ๊ธฐ์ค
MOVABLE ํ์ด์ง ์ โค (auto_movable_ratio ร KERNEL_EARLY ํ์ด์ง ์) / 100
KERNEL_EARLY = ๋ถํ
์ ์ปค๋ ์์ญ์ ์๋ ํ์ด์ง
โ Hotplug๋ก ์ถ๊ฐ๋ ์ปค๋ ์์ญ์ ์ ์ธ (ํฅํ ์ ๊ฑฐ ๊ฐ๋ฅ์ฑ์ด ์์ผ๋ฏ๋ก)
โ ๋์ ๋ฉ๋ชจ๋ฆฌ ๊ทธ๋ฃน์ ์์ธ ( DRIVER_MANAGED ๋ฉ๋ชจ๋ฆฌ )
NUMA ์ธ์:
auto_movable_numa_aware = true (๊ธฐ๋ณธ๊ฐ)
โ ๋
ธ๋๋ณ ๋น์จ๋ ๊ฒ์ฌํ์ฌ ํน์ ๋
ธ๋์ ์ ๋ฆผ ๋ฐฉ์ง