보통 시스템에 메모리가 얼마나 남아 있는지 점검하고 싶을 때가 있어요. 이런 상황에서 메모리를 디버깅할 때 가장 많이 활용되는 파일이 '/proc/meminfo'입니다.
cat "/proc/meminfo" 명령어를 치면 상세한 메모리 정보를 파악할 수 있거든요.
이번에는 "/proc/meminfo"란 노드가 proc 파일 시스템에 메모리 정보를 어떻게 읽어 오는 지 소개합니다.
/proc/meminfo 해석
'cat /proc/meminfo' 명령어를 입력하면 출력되는 결과와 관련 정보는 다음과 같습니다.
root@raspberrypi:/home/pi# cat /proc/meminfo
MemTotal: 1986000 kB // 전체 물리 메모리크기 (1986000 kB/1024 = 1939MB)
MemFree: 1506308 kB // 사용 가능한 메모리 크기 (1506308 kB/1024 = 1471MB)
MemAvailable: 1694404 kB
Buffers: 31308 kB // 디스크 접근용 버퍼 크기(파일 시스템용 메타데이터)
Cached: 257216 kB // 페이지 캐시 사이즈
SwapCached: 0 kB // 스왑 중인 페이지 사이즈
SwapTotal: 945532 kB // 전체 스왑 영역 크기
SwapFree: 664176 kB // 사용 가능한 스왑
Active: 203072 kB // active LRU 크기
Inactive: 171568 kB // inactive LRU 크기
Active(anon): 86532 kB // active-anon LRU
Inactive(anon): 41124 kB // inactive-anon LRU
Active(file): 116540 kB // active-file LRU
Inactive(file): 130444 kB // inactive-file LRU
Unevictable: 16 kB // 회수불가능한페이지
Mlocked: 16 kB // mlock() 시스템 콜로 lock 걸린 페이지
HighTotal: 1232896 kB // 전체 High 메모리(유저 스페이스에서 접근) 크기
HighFree: 878004 kB // 사용 가능한 High 메모리(유저 스페이스에서 접근) 크기
LowTotal: 753104 kB // 전체 Low 메모리 크기(커널에서 엑세스)
LowFree: 628304 kB // 사용 가능한 Low 메모리 크기(커널에서 엑세스)
SwapTotal: 102396 kB
SwapFree: 102396 kB
Dirty: 596 kB // 디스크에 기록해야 할 페이지
Writeback: 0 kB // 디스크에 기록 중인 페이지
AnonPages: 86152 kB // 익명 매핑된 사용자 페이지
Mapped: 95420 kB // 파일 매핑된 사용자 페이지
Shmem: 41536 kB // 공유 메모리 페이지
Slab: 55408 kB // 슬랩 할당자가 관리하는 커널 페이지
SReclaimable: 24300 kB // 회수 가능한 슬랩 페이지
SUnreclaim: 31108 kB // 회수 불가능한 슬랩 페이지
KernelStack: 1856 kB // 커널 스택으로 사용 중인 페이지
PageTables: 4308 kB // (하위) 페이지 테이블로 사용 중인 페이지
NFS_Unstable: 0 kB // NFS에서 사용
Bounce: 0 kB // 디스크 장치에서 사용
WritebackTmp: 0 kB // FUSE 파일시스템에서 사용
CommitLimit: 1095396 kB // 오버커밋이 허용된 크기(/proc/sys/vm/overcommit_memory 확인 필요)
Committed_AS: 911844 kB // 실제 오버 커밋된 가상 메모리의 크기
VmallocTotal: 245760 kB // 전체 vmalloc영역의 크기
VmallocUsed: 0 kB // 실제로 할당된 vmalloc 영역
VmallocChunk: 0 kB // 사용 가능한 가장 큰 vmalloc 영역의 크기
Percpu: 640 kB
CmaTotal: 262144 kB
CmaFree: 239556 kB
갑자기 시스템의 메모리 정보를 확인해야 할 때 바로 확인하면 좋은 정보인 것 같습니다.
그런데 이 정보를 보면 한 가지 의문이 생깁니다.
"이 정보를 출력해 주는 주인공은 누구일까?"
리눅스 시스템 어딘가에서 이 정보를 출력할텐데요. 그 주인공은 리눅스 커널입니다. 이어서 /proc/meminfo 파일을 통해 시스템 메모리 정보를 출력하는 커널 코드를 소개합니다.
메모리 정보를 출력하는 커널 코드 분석
우선 커널이 부팅하는 과정에서 아래 함수에서 meminfo란 이름으로 proc 파일 시스템으로 등록합니다.
https://elixir.bootlin.com/linux/v4.19.30/source/fs/proc/meminfo.c
static int __init proc_meminfo_init(void)
{
proc_create("meminfo", 0, NULL, &meminfo_proc_fops);
return 0;
}
static int meminfo_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, meminfo_proc_show, NULL);
}
static const struct file_operations meminfo_proc_fops = {
.open = meminfo_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
아래 코드를 보면 meminfo_proc_show() 함수에서 메모리 세부 정보를 출력한다는 사실을 알 수 있습니다.
static int meminfo_proc_show(struct seq_file *m, void *v)
{
struct sysinfo i;
unsigned long committed;
long cached;
...
show_val_kb(m, "MemTotal: ", i.totalram);
show_val_kb(m, "MemFree: ", i.freeram);
show_val_kb(m, "MemAvailable: ", available);
show_val_kb(m, "Buffers: ", i.bufferram);
show_val_kb(m, "Cached: ", cached);
show_val_kb(m, "SwapCached: ", total_swapcache_pages());
show_val_kb(m, "Active: ", pages[LRU_ACTIVE_ANON] +
pages[LRU_ACTIVE_FILE]);
show_val_kb(m, "Inactive: ", pages[LRU_INACTIVE_ANON] +
pages[LRU_INACTIVE_FILE]);
show_val_kb(m, "Active(anon): ", pages[LRU_ACTIVE_ANON]);
show_val_kb(m, "Inactive(anon): ", pages[LRU_INACTIVE_ANON]);
show_val_kb(m, "Active(file): ", pages[LRU_ACTIVE_FILE]);
show_val_kb(m, "Inactive(file): ", pages[LRU_INACTIVE_FILE]);
가끔 메모리 leak으로 이슈가 생길 경우 섬세한 디버깅 패치를 작성해서 메모리가 어느 시점에 변동되는지 점검해야 하거든요. 5초 간격으로 호출되는 간단한 타이머 함수를 호출해서 메모리 잔여량을 체크하는 함수를 작성하는거죠.
이 때 proc 파일 시스템으로 등록된 meminfo_proc_show 내에서 호출된 함수 조각을 잘 활용하면 매우 유용할 것 같아요. 아래 함수로 High 메모리 잔여양과 전체 High Memory 양을 확인할 수 있어요.
https://elixir.bootlin.com/linux/v4.19.30/source/mm/page_alloc.c
void si_meminfo(struct sysinfo *val)
{
val->totalram = totalram_pages;
val->sharedram = global_page_state(NR_SHMEM);
val->freeram = global_page_state(NR_FREE_PAGES);
val->bufferram = nr_blockdev_pages();
val->totalhigh = totalhigh_pages;
val->freehigh = nr_free_highpages();
val->mem_unit = PAGE_SIZE;
}
전체 low memory 양은 val->totalram - val->totalhigh,
free low memory 양은 val->freeram - val->freehigh 으로 계산할 수 있죠.
아래 메모리 정보는 아래 정보로 계산이 되니 아래 API를 참고해서 시간에 따라 메모리가 얼마나 남아 있는지 체크해도 좋을 것 같아요.
"MemTotal: %8lu kB\n" // val->totalram
"MemFree: %8lu kB\n" // val->freeram
"MemAvailable: %8lu kB\n" // val->freeram + 페이지 캐시 + global_page_state(NR_SLAB_RECLAIMABLE)
"Buffers: %8lu kB\n" // val->bufferram
"Cached: %8lu kB\n" // global_page_state(NR_FILE_PAGES) - total_swapcache_pages() - val->bufferram
"SwapCached: %8lu kB\n" // total_swapcache_pages()
"Active: %8lu kB\n" // global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE)
"Inactive: %8lu kB\n" // global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE)
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자

최근 덧글