74. 메모리 정보 표시 (show_mem)
개요
show_mem.c는 커널에서 메모리 상태 정보를 출력하는 유틸리티 함수들을 제공합니다. 주로 /proc/meminfo, dmesg, 또는 커널 panic 시 호출되어 시스템의 메모리 사용 현황을 한눈에 파악할 수 있게 합니다. 이 모듈은 메모리 관리 디버깅과 시스템 모니터링의 핵심 기반을 제공합니다.
건물 관리자가 각 층의 빈 방 수, 사용 중인 방, 수리가 필요한 방을 점검하는 것처럼, show_mem.c의 함수들은 시스템의 모든 메모리 영역(zone, node)을 순회하며 자유 페이지, 할당된 페이지, 회수 가능한 페이지 등의 통계를 수집하고 출력합니다. show_mem() 인라인 함수는 include/linux/mm.h에서 __show_mem()을 호출하는 진입점으로, 커널 panic이나 SysRq 키 입력 시 자동으로 호출됩니다.
소스 파일: mm/show_mem.c (460줄), include/linux/mm.h, include/linux/mmzone.h, include/uapi/linux/sysinfo.h
// mm/show_mem.c:21-24
atomic_long_t _totalram_pages __read_mostly;
EXPORT_SYMBOL(_totalram_pages);
unsigned long totalreserve_pages __read_mostly;
unsigned long totalcma_pages __read_mostly;
// include/linux/mm.h:3768-3771
static inline void show_mem(void)
{
__show_mem(0, NULL, MAX_NR_ZONES - 1);
}
빠른 점검 명령
# 총 메모리 페이지 수 확인
cat /proc/meminfo | grep MemTotal
# 메모리 사용량 상세 정보
cat /proc/meminfo
# zone별 메모리 상태
cat /proc/zoneinfo
# NUMA 노드별 메모리 정보
numactl --hardware
# 메모리 블록 상태 확인
lsmem
# 페이지 테이블 크기 확인
cat /proc/pagetypeinfo
# CMA 메모리 상태
cat /proc/meminfo | grep Cma
# 스왑 메모리 상태
cat /proc/swaps
# 메모리 압력 지표 (PSI)
cat /proc/pressure/memory
# 메모리 할당 프로파일링
cat /sys/kernel/debug/page_owner
# HugeTLB 메모리 정보
cat /proc/meminfo | grep Huge
# 커널 메모리 정보 (slab)
cat /proc/meminfo | grep Slab
# 직접 매핑 영역 확인
grep DirectMap /proc/meminfo
# 메모리 존별 통계 요약
grep -E "^(Node|Normal|DMA|HighMem)" /proc/zoneinfo
핵심 자료구조
1. sysinfo 구조체
// include/uapi/linux/sysinfo.h:8-23
struct sysinfo {
__kernel_long_t uptime; // 부팅 후 경과 시간(초)
__kernel_ulong_t loads[3]; // 1, 5, 15분 부하 평균
__kernel_ulong_t totalram; // 총 사용 가능 메인 메모리
__kernel_ulong_t freeram; // 사용 가능한 메모리
__kernel_ulong_t sharedram; // 공유 메모리 양
__kernel_ulong_t bufferram; // 버퍼가 사용 중인 메모리
__kernel_ulong_t totalswap; // 총 스왑 공간 크기
__kernel_ulong_t freeswap; // 사용 가능한 스왑 공간
__u16 procs; // 현재 프로세스 수
__u16 pad; // 패딩(m68k용)
__kernel_ulong_t totalhigh; // 총 하이 메모리 크기
__kernel_ulong_t freehigh; // 사용 가능한 하이 메모리
__u32 mem_unit; // 메모리 단위(바이트)
char _f[20-2*sizeof(__kernel_ulong_t)-sizeof(__u32)]; // 패딩
};
si_meminfo()는 이 구조체의 totalram, freeram, sharedram, bufferram, totalhigh, freehigh, mem_unit 필드를 채웁니다.
2. zone 구조체 (참조)
// include/linux/mmzone.h:400-450 (참조)
struct zone {
unsigned long _watermark[NR_WMARK]; // 워터마크
long lowmem_reserve[MAX_NR_ZONES]; // 저메모리 예약
struct per_cpu_pages __percpu *per_cpu_pageset; // PCP 페이지 리스트
unsigned long zone_start_pfn; // 존 시작 PFN
atomic_long_t managed_pages; // 관리되는 페이지 수
unsigned long spanned_pages; // 범위 페이지 수
unsigned long present_pages; // 존재하는 페이지 수
const char *name; // 존 이름
struct free_area free_area[NR_PAGE_ORDERS]; // 자유 영역
// ...
};
3. free_area 구조체 (참조)
// include/linux/mmzone.h (참조)
struct free_area {
struct list_head free_list[MIGRATE_TYPES]; // 마이그레이션 유형별 자유 리스트
unsigned long nr_free; // 자유 페이지 수
};
4. pg_data_t 구조체 (참조)
// include/linux/mmzone.h (참조)
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; // 존 배열
unsigned long node_start_pfn; // 노드 시작 PFN
unsigned long node_spanned_pages; // 노드 범위 페이지 수
unsigned long node_present_pages; // 노드 존재 페이지 수
int node_id; // 노드 ID
// ...
} pg_data_t;
5. 마이그레이션 유형
// mm/show_mem.c:137-148
static const char types[MIGRATE_TYPES] = {
[MIGRATE_UNMOVABLE] = 'U', // 이동 불가
[MIGRATE_MOVABLE] = 'M', // 이동 가능
[MIGRATE_RECLAIMABLE] = 'E', // 회수 가능
[MIGRATE_HIGHATOMIC] = 'H', // 고원자성
[MIGRATE_CMA] = 'C', // CMA (Contiguous Memory Allocator)
[MIGRATE_ISOLATE] = 'I', // 격리됨
};
핵심 함수
1. si_mem_available()
// mm/show_mem.c:32-72
long si_mem_available(void)
{
long available;
unsigned long pagecache;
unsigned long wmark_low = 0;
unsigned long reclaimable;
struct zone *zone;
// 모든 존의 저워터마크 합산
for_each_zone(zone)
wmark_low += low_wmark_pages(zone);
// 사용자 공간 할당에 사용 가능한 메모리 추정
available = global_zone_page_state(NR_FREE_PAGES) - totalreserve_pages;
// 페이지 캐시 계산 (최소 절반 또는 저워터마크 유지)
pagecache = global_node_page_state(NR_ACTIVE_FILE) +
global_node_page_state(NR_INACTIVE_FILE);
pagecache -= min(pagecache / 2, wmark_low);
available += pagecache;
// 회수 가능한 슬래브 및 커널 메모리 계산
reclaimable = global_node_page_state_pages(NR_SLAB_RECLAIMABLE_B) +
global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE);
reclaimable -= min(reclaimable / 2, wmark_low);
available += reclaimable;
if (available < 0)
available = 0;
return available;
}
역할: 스왑이나 OOM 없이 사용자 공간에 할당할 수 있는 메모리 양을 추정합니다.
2. si_meminfo()
// mm/show_mem.c:75-86
void si_meminfo(struct sysinfo *val)
{
val->totalram = totalram_pages();
val->sharedram = global_node_page_state(NR_SHMEM);
val->freeram = global_zone_page_state(NR_FREE_PAGES);
val->bufferram = nr_blockdev_pages();
val->totalhigh = totalhigh_pages();
val->freehigh = nr_free_highpages();
val->mem_unit = PAGE_SIZE;
}
역할: /proc/meminfo에 표시되는 메모리 정보를 시스템 전체에 대해 수집합니다.
3. si_meminfo_node()
// mm/show_mem.c:89-113 (CONFIG_NUMA)
void si_meminfo_node(struct sysinfo *val, int nid)
{
int zone_type;
unsigned long managed_pages = 0;
unsigned long managed_highpages = 0;
unsigned long free_highpages = 0;
pg_data_t *pgdat = NODE_DATA(nid);
for (zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) {
struct zone *zone = &pgdat->node_zones[zone_type];
managed_pages += zone_managed_pages(zone);
if (is_highmem(zone)) {
managed_highpages += zone_managed_pages(zone);
free_highpages += zone_page_state(zone, NR_FREE_PAGES);
}
}
val->totalram = managed_pages;
val->sharedram = node_page_state(pgdat, NR_SHMEM);
val->freeram = sum_zone_node_page_state(nid, NR_FREE_PAGES);
val->totalhigh = managed_highpages;
val->freehigh = free_highpages;
val->mem_unit = PAGE_SIZE;
}
역할: 특정 NUMA 노드에 대한 메모리 정보를 수집합니다.
4. show_free_areas()
// mm/show_mem.c:180-399
static void show_free_areas(unsigned int filter, nodemask_t *nodemask, int max_zone_idx)
{
unsigned long free_pcp = 0;
int cpu, nid;
struct zone *zone;
pg_data_t *pgdat;
// 모든 zone의 PCP(Per-CPU Pages) 카운트 합산
for_each_populated_zone(zone) {
if (zone_idx(zone) > max_zone_idx)
continue;
if (show_mem_node_skip(filter, zone_to_nid(zone), nodemask))
continue;
for_each_online_cpu(cpu)
free_pcp += per_cpu_ptr(zone->per_cpu_pageset, cpu)->count;
}
// 글로벌 메모리 상태 출력 (페이지 단위)
printk("active_anon:%lu inactive_anon:%lu isolated_anon:%lu\n"
" active_file:%lu inactive_file:%lu isolated_file:%lu\n"
" unevictable:%lu dirty:%lu writeback:%lu\n"
" slab_reclaimable:%lu slab_unreclaimable:%lu\n"
" mapped:%lu shmem:%lu pagetables:%lu\n"
" sec_pagetables:%lu bounce:%lu\n"
" kernel_misc_reclaimable:%lu\n"
" free:%lu free_pcp:%lu free_cma:%lu\n",
global_node_page_state(NR_ACTIVE_ANON),
global_node_page_state(NR_INACTIVE_ANON),
global_node_page_state(NR_ISOLATED_ANON),
global_node_page_state(NR_ACTIVE_FILE),
global_node_page_state(NR_INACTIVE_FILE),
global_node_page_state(NR_ISOLATED_FILE),
global_node_page_state(NR_UNEVICTABLE),
global_node_page_state(NR_FILE_DIRTY),
global_node_page_state(NR_WRITEBACK),
global_node_page_state_pages(NR_SLAB_RECLAIMABLE_B),
global_node_page_state_pages(NR_SLAB_UNRECLAIMABLE_B),
global_node_page_state(NR_FILE_MAPPED),
global_node_page_state(NR_SHMEM),
global_node_page_state(NR_PAGETABLE),
global_node_page_state(NR_SECONDARY_PAGETABLE),
0UL,
global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE),
global_zone_page_state(NR_FREE_PAGES),
free_pcp,
global_zone_page_state(NR_FREE_CMA_PAGES));
// 노드별 상세 통계 출력 (kB 단위)
for_each_online_pgdat(pgdat) {
if (show_mem_node_skip(filter, pgdat->node_id, nodemask))
continue;
if (!node_has_managed_zones(pgdat, max_zone_idx))
continue;
printk("Node %d"
" active_anon:%lukB"
" inactive_anon:%lukB"
" active_file:%lukB"
" inactive_file:%lukB"
" unevictable:%lukB"
" isolated(anon):%lukB"
" isolated(file):%lukB"
" mapped:%lukB"
" dirty:%lukB"
" writeback:%lukB"
" shmem:%lukB"
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
" shmem_thp:%lukB"
" shmem_pmdmapped:%lukB"
" anon_thp:%lukB"
#endif
" kernel_stack:%lukB"
#ifdef CONFIG_SHADOW_CALL_STACK
" shadow_call_stack:%lukB"
#endif
" pagetables:%lukB"
" sec_pagetables:%lukB"
" all_unreclaimable? %s"
" Balloon:%lukB"
"\n",
pgdat->node_id,
K(node_page_state(pgdat, NR_ACTIVE_ANON)),
K(node_page_state(pgdat, NR_INACTIVE_ANON)),
K(node_page_state(pgdat, NR_ACTIVE_FILE)),
K(node_page_state(pgdat, NR_INACTIVE_FILE)),
K(node_page_state(pgdat, NR_UNEVICTABLE)),
K(node_page_state(pgdat, NR_ISOLATED_ANON)),
K(node_page_state(pgdat, NR_ISOLATED_FILE)),
K(node_page_state(pgdat, NR_FILE_MAPPED)),
K(node_page_state(pgdat, NR_FILE_DIRTY)),
K(node_page_state(pgdat, NR_WRITEBACK)),
K(node_page_state(pgdat, NR_SHMEM)),
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
K(node_page_state(pgdat, NR_SHMEM_THPS)),
K(node_page_state(pgdat, NR_SHMEM_PMDMAPPED)),
K(node_page_state(pgdat, NR_ANON_THPS)),
#endif
node_page_state(pgdat, NR_KERNEL_STACK_KB),
#ifdef CONFIG_SHADOW_CALL_STACK
node_page_state(pgdat, NR_KERNEL_SCS_KB),
#endif
K(node_page_state(pgdat, NR_PAGETABLE)),
K(node_page_state(pgdat, NR_SECONDARY_PAGETABLE)),
str_yes_no(kswapd_test_hopeless(pgdat)),
K(node_page_state(pgdat, NR_BALLOON_PAGES)));
}
// 존별 상세 정보 출력 (kB 단위)
for_each_populated_zone(zone) {
int i;
if (zone_idx(zone) > max_zone_idx)
continue;
if (show_mem_node_skip(filter, zone_to_nid(zone), nodemask))
continue;
free_pcp = 0;
for_each_online_cpu(cpu)
free_pcp += per_cpu_ptr(zone->per_cpu_pageset, cpu)->count;
show_node(zone);
printk(KERN_CONT
"%s"
" free:%lukB"
" boost:%lukB"
" min:%lukB"
" low:%lukB"
" high:%lukB"
" reserved_highatomic:%luKB"
" free_highatomic:%luKB"
" active_anon:%lukB"
" inactive_anon:%lukB"
" active_file:%lukB"
" inactive_file:%lukB"
" unevictable:%lukB"
" writepending:%lukB"
" zspages:%lukB"
" present:%lukB"
" managed:%lukB"
" mlocked:%lukB"
" bounce:%lukB"
" free_pcp:%lukB"
" local_pcp:%ukB"
" free_cma:%lukB"
"\n",
zone->name,
K(zone_page_state(zone, NR_FREE_PAGES)),
K(zone->watermark_boost),
K(min_wmark_pages(zone)),
K(low_wmark_pages(zone)),
K(high_wmark_pages(zone)),
K(zone->nr_reserved_highatomic),
K(zone->nr_free_highatomic),
K(zone_page_state(zone, NR_ZONE_ACTIVE_ANON)),
K(zone_page_state(zone, NR_ZONE_INACTIVE_ANON)),
K(zone_page_state(zone, NR_ZONE_ACTIVE_FILE)),
K(zone_page_state(zone, NR_ZONE_INACTIVE_FILE)),
K(zone_page_state(zone, NR_ZONE_UNEVICTABLE)),
K(zone_page_state(zone, NR_ZONE_WRITE_PENDING)),
#if IS_ENABLED(CONFIG_ZSMALLOC)
K(zone_page_state(zone, NR_ZSPAGES)),
#else
0UL,
#endif
K(zone->present_pages),
K(zone_managed_pages(zone)),
K(zone_page_state(zone, NR_MLOCK)),
0UL,
K(free_pcp),
K(this_cpu_read(zone->per_cpu_pageset->count)),
K(zone_page_state(zone, NR_FREE_CMA_PAGES)));
printk("lowmem_reserve[]:");
for (i = 0; i < MAX_NR_ZONES; i++)
printk(KERN_CONT " %ld", zone->lowmem_reserve[i]);
printk(KERN_CONT "\n");
}
// 버디 할당자 자유 영역 출력 (order별 페이지 수와 마이그레이션 유형)
for_each_populated_zone(zone) {
unsigned int order;
unsigned long nr[NR_PAGE_ORDERS], flags, total = 0;
unsigned char types[NR_PAGE_ORDERS];
if (zone_idx(zone) > max_zone_idx)
continue;
if (show_mem_node_skip(filter, zone_to_nid(zone), nodemask))
continue;
show_node(zone);
printk(KERN_CONT "%s: ", zone->name);
spin_lock_irqsave(&zone->lock, flags);
for (order = 0; order < NR_PAGE_ORDERS; order++) {
struct free_area *area = &zone->free_area[order];
int type;
nr[order] = area->nr_free;
total += nr[order] << order;
types[order] = 0;
for (type = 0; type < MIGRATE_TYPES; type++) {
if (!free_area_empty(area, type))
types[order] |= 1 << type;
}
}
spin_unlock_irqrestore(&zone->lock, flags);
for (order = 0; order < NR_PAGE_ORDERS; order++) {
printk(KERN_CONT "%lu*%lukB ",
nr[order], K(1UL) << order);
if (nr[order])
show_migration_types(types[order]);
}
printk(KERN_CONT "= %lukB\n", K(total));
}
// HugeTLB 메모리 정보 출력
for_each_online_node(nid) {
if (show_mem_node_skip(filter, nid, nodemask))
continue;
hugetlb_show_meminfo_node(nid);
}
printk("%ld total pagecache pages\n", global_node_page_state(NR_FILE_PAGES));
show_swap_cache_info();
}
역할: 시스템의 전체 메모리 영역 상태를 상세히 출력합니다.
5. __show_mem()
// mm/show_mem.c:401-460
void __show_mem(unsigned int filter, nodemask_t *nodemask, int max_zone_idx)
{
unsigned long total = 0, reserved = 0, highmem = 0;
struct zone *zone;
printk("Mem-Info:\n");
show_free_areas(filter, nodemask, max_zone_idx);
for_each_populated_zone(zone) {
total += zone->present_pages;
reserved += zone->present_pages - zone_managed_pages(zone);
if (is_highmem(zone))
highmem += zone->present_pages;
}
printk("%lu pages RAM\n", total);
printk("%lu pages HighMem/MovableOnly\n", highmem);
printk("%lu pages reserved\n", reserved);
#ifdef CONFIG_CMA
printk("%lu pages cma reserved\n", totalcma_pages);
#endif
#ifdef CONFIG_MEMORY_FAILURE
printk("%lu pages hwpoisoned\n", atomic_long_read(&num_poisoned_pages));
#endif
#ifdef CONFIG_MEM_ALLOC_PROFILING
static DEFINE_SPINLOCK(mem_alloc_profiling_spinlock);
if (spin_trylock(&mem_alloc_profiling_spinlock)) {
struct codetag_bytes tags[10];
size_t i, nr;
// 메모리 할당 프로파일링: 상위 10개 할당 사이트 출력
nr = alloc_tag_top_users(tags, ARRAY_SIZE(tags), false);
if (nr) {
pr_notice("Memory allocations (profiling is currently turned %s):\n",
mem_alloc_profiling_enabled() ? "on" : "off");
for (i = 0; i < nr; i++) {
struct codetag *ct = tags[i].ct;
struct alloc_tag *tag = ct_to_alloc_tag(ct);
struct alloc_tag_counters counter = alloc_tag_read(tag);
char bytes[10];
string_get_size(counter.bytes, 1, STRING_UNITS_2, bytes, sizeof(bytes));
if (ct->modname)
pr_notice("%12s %8llu %s:%u [%s] func:%s\n",
bytes, counter.calls, ct->filename,
ct->lineno, ct->modname, ct->function);
else
pr_notice("%12s %8llu %s:%u func:%s\n",
bytes, counter.calls, ct->filename,
ct->lineno, ct->function);
}
}
spin_unlock(&mem_alloc_profiling_spinlock);
}
#endif
}
역할: 메모리 정보의 메인 진입점으로, 전체 메모리 상태를 요약 출력합니다. CONFIG_MEM_ALLOC_PROFILING이 활성화되면 상위 10개 메모리 할당 사이트도 함께 출력합니다.
6. show_mem_node_skip()
// mm/show_mem.c:119-133
static bool show_mem_node_skip(unsigned int flags, int nid, nodemask_t *nodemask)
{
if (!(flags & SHOW_MEM_FILTER_NODES))
return false;
// 노드 마스크가 없으면 현재 cpuset 마스크를 사용
if (!nodemask)
nodemask = &cpuset_current_mems_allowed;
return !node_isset(nid, *nodemask);
}
역할: SHOW_MEM_FILTER_NODES 플래그가 설정된 경우, 현재 프로세스의 cpuset에 속하지 않는 NUMA 노드를 출력에서 제외합니다.
7. node_has_managed_zones()
// mm/show_mem.c:162-169
static bool node_has_managed_zones(pg_data_t *pgdat, int max_zone_idx)
{
int zone_idx;
for (zone_idx = 0; zone_idx <= max_zone_idx; zone_idx++)
if (zone_managed_pages(pgdat->node_zones + zone_idx))
return true;
return false;
}
역할: 특정 NUMA 노드가 관리되는 메모리를 가진 zone을 하나 이상 가지고 있는지 확인합니다. 관리되는 페이지가 없는 노드는 출력에서 건너뜁니다.
호출 흐름
show_mem() [커널 panic/디버그]
│
├── si_meminfo() ──────────────→ /proc/meminfo
│ └── totalram_pages()
│ └── global_node_page_state()
│ └── global_zone_page_state()
│
├── si_meminfo_node() ────────→ NUMA 노드별 정보
│ └── zone_managed_pages()
│ └── node_page_state()
│
├── __show_mem() ─────────────→ dmesg 출력
│ └── show_free_areas()
│ └── show_migration_types()
│ └── show_node()
│ └── zone_page_state()
│ └── free_area_empty()
│
└── si_mem_available() ───────→ 사용 가능 메모리 추정
└── low_wmark_pages()
└── global_zone_page_state()
조건별 비교
| 항목 | si_meminfo | si_meminfo_node | show_free_areas | __show_mem |
|---|---|---|---|---|
| **대상** | 시스템 전체 | 특정 NUMA 노드 | 메모리 영역 | 시스템 전체 |
| **출력 형식** | sysinfo 구조체 | sysinfo 구조체 | printk 상세 | printk 요약 |
| **사용처** | /proc/meminfo | numactl | dmesg/디버그 | panic/dmesg |
| **NUMA 지원** | 전체 합산 | 노드별 분리 | 노드별 분리 | 전체 합산 |
| **상세도** | 요약 | 요약 | 매우 상세 | 중간 |
호출자별 show_mem 사용 방식
| 호출자 | 함수 | filter | 설명 |
|---|---|---|---|
| SysRq-M | `show_mem()` | 0 | 전체 노드 출력 |
| warn_alloc() | `__show_mem()` | SHOW_MEM_FILTER_NODES | 할당 실패 시 cpuset 기반 필터링 |
| OOM Killer | `__show_mem()` | SHOW_MEM_FILTER_NODES | OOM 상황의 cpuset 기반 필터링 |
| panic_show_mem | `show_mem()` | 0 | panic 시 전체 출력 |
관련 문서
관련 소스 파일
mm/show_mem.c - 주요 구현 (460줄)include/linux/mm.h - show_mem() 인라인 함수, 함수 선언include/uapi/linux/sysinfo.h - sysinfo 구조체 정의include/linux/mmzone.h - 존/노드 구조체 정의fs/proc/meminfo.c - /proc/meminfo 인터페이스mm/vmstat.c - VM 통계 함수들mm/oom_kill.c - OOM Killer 호출자mm/page_alloc.c - warn_alloc() 호출자drivers/tty/sysrq.c - SysRq-M 호출자