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 호출 흐름
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_meminfosi_meminfo_nodeshow_free_areas__show_mem
**대상**시스템 전체특정 NUMA 노드메모리 영역시스템 전체
**출력 형식**sysinfo 구조체sysinfo 구조체printk 상세printk 요약
**사용처**/proc/meminfonumactldmesg/디버그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_NODESOOM 상황의 cpuset 기반 필터링
panic_show_mem`show_mem()`0panic 시 전체 출력

관련 문서

  • 00-overview.html - 메모리 관리 개요
  • 01-page_alloc.html - 버디 할당자
  • 05-page_reclaim.html - 페이지 회수
  • 08-oom.html - OOM Killer
  • 13-numa.html - NUMA
  • 69-vmstat.html - VM 통계
  • 73-mm_init.html - MM 초기화
  • 72-internal.html - 메모리 관리 내부
  • 관련 소스 파일

  • 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 호출자