관리 메뉴

근묵자흑

Linux 메모리 관리 서브시스템의 이해와 실무 적용 본문

Linux/memory

Linux 메모리 관리 서브시스템의 이해와 실무 적용

Luuuuu 2025. 8. 4. 14:14

본 문서는 Linux 커널의 메모리 관리 서브시스템에 대한 포괄적인 분석을 제공한다. Ubuntu 24.04 LTS를 기준으로 물리 메모리와 가상 메모리의 추상화, 커널의 다층적 할당 전략, 캐싱 메커니즘, 그리고 현대적인 메모리 관리 기능들을 체계적으로 다룬다. 시스템 관리자로서 필요한 실무적 관점에서 메모리 문제를 진단하고 해결하는 방법론을 함께 제시한다.

1. 서론

Linux 운영체제에서 메모리 관리는 시스템 성능과 안정성을 결정짓는 핵심 요소이다. 커널은 제한된 물리적 자원 위에 우아한 추상화 계층을 구축하여, 성능, 효율성, 안정성이라는 상충하는 요구사항들 사이에서 정교한 균형을 유지한다. 본 문서는 이러한 복잡한 메커니즘을 해부하여, 시스템 관리자가 실무에서 직면하는 메모리 관련 문제들을 이해하고 해결할 수 있는 지식 기반을 제공하고자 한다.

2. Linux 메모리 관리의 기본 개념

2.1 물리 메모리와 가상 메모리: 위대한 추상화

Linux 시스템에서 메모리는 두 가지 관점으로 이해되어야 한다. **물리 메모리(Physical Memory)**는 시스템에 실제로 설치된 RAM 모듈을 의미하며, 모든 작업이 궁극적으로 이를 통과하므로 그 관리는 성능에 직접적인 영향을 미친다. 반면 **가상 메모리(Virtual Memory)**는 각 프로세스에게 독립적이고 연속적인 주소 공간을 제공하는 추상화 계층으로, 물리적 제약으로부터 프로그래밍 모델을 해방시킨다.

가상 메모리 시스템의 도입은 단순한 메모리 확장 이상의 의미를 갖는다:

  • 프로세스 격리: 각 프로세스는 다른 프로세스나 커널의 메모리에 접근할 수 없는 격리된 환경에서 실행된다
  • 메모리 보호: 잘못된 메모리 접근은 하드웨어 수준에서 차단되어 시스템 안정성을 보장한다
  • 효율적 자원 활용: 수요 기반 페이징(demand paging)과 공유 라이브러리를 통해 실제 사용되는 메모리만을 물리 메모리에 유지한다

2.2 커널 공간과 사용자 공간의 분리

Linux는 메모리를 **커널 공간(Kernel Space)**과 **사용자 공간(User Space)**으로 엄격히 분리한다. 이러한 분리는 시스템의 안정성과 보안성을 보장하는 근본적인 아키텍처 결정이다.

x86_64 아키텍처에서 가상 주소 공간은 다음과 같이 분할된다:

0x0000000000000000 - 0x00007fffffffffff : 사용자 공간 (128TB)
0xffff800000000000 - 0xffffffffffffffff : 커널 공간 (128TB)

사용자 프로세스가 커널 서비스를 필요로 할 때는 반드시 시스템 호출(system call)을 통해야 하며, 이 과정에서 CPU는 특권 모드로 전환되어 커널 코드를 실행한다.

2.3 페이징과 주소 변환 메커니즘

Linux는 페이징 기법을 사용하여 가상 주소를 물리 주소로 변환한다. 메모리는 일반적으로 4KB 크기의 페이지(page) 단위로 관리되며, 이 변환 과정은 다음과 같은 계층 구조를 따른다:

  1. 페이지 테이블: 각 프로세스는 고유한 페이지 테이블을 가지며, 이는 가상 페이지 번호를 물리 페이지 프레임 번호로 매핑한다
  2. Translation Lookaside Buffer (TLB): CPU 내부의 특수 캐시로, 최근 사용된 주소 변환 정보를 저장하여 변환 속도를 가속화한다
  3. 페이지 워크(Page Walk): TLB 미스 발생 시 메모리에 있는 페이지 테이블을 순회하여 매핑을 찾는 과정

현대 x86_64 시스템은 4단계 또는 5단계 페이지 테이블을 사용하며, 각 단계는 가상 주소의 특정 비트를 인덱스로 사용한다.

2.4 수요 기반 페이징과 페이지 폴트

Linux의 메모리 효율성은 수요 기반 페이징(demand paging) 메커니즘에 크게 의존한다. 프로세스가 실제로 메모리 페이지에 접근하기 전까지는 물리 메모리가 할당되지 않으며, 이는 다음과 같은 과정을 통해 구현된다:

페이지 폴트의 종류와 처리:

  • 마이너 폴트(Minor Fault): 요청된 페이지가 이미 물리 메모리에 존재하지만 프로세스의 페이지 테이블에 매핑되지 않은 경우. 단순히 페이지 테이블 업데이트만으로 해결된다
  • 메이저 폴트(Major Fault): 페이지를 디스크에서 읽어와야 하는 경우. I/O 작업이 필요하므로 상당한 지연이 발생한다

시스템 관리자는 다음 명령으로 페이지 폴트 발생 빈도를 모니터링할 수 있다:

# 프로세스별 페이지 폴트 통계
ps -eo pid,min_flt,maj_flt,cmd | sort -nrk3 | head -20

3. 커널 메모리 할당 메커니즘

3.1 버디 시스템: 물리 페이지 관리의 핵심

**버디 시스템(Buddy System)**은 Linux 커널의 주요 물리 메모리 할당자로, 외부 단편화를 최소화하면서도 효율적인 메모리 할당을 가능하게 한다. 이 시스템은 2의 거듭제곱 크기의 블록(1, 2, 4, 8... 페이지)으로 메모리를 관리한다.

작동 원리:

  1. 각 크기별로 여유 블록의 연결 리스트를 유지한다
  2. N 페이지 요청 시, N보다 크거나 같은 가장 작은 블록을 찾는다
  3. 필요한 경우 큰 블록을 "버디"로 분할하여 정확한 크기를 만든다
  4. 블록 해제 시, 인접한 버디가 자유 상태라면 병합하여 더 큰 블록을 형성한다

버디 시스템의 현재 상태는 다음 명령으로 확인할 수 있다:

cat /proc/buddyinfo

3.2 메모리 존(Zone)과 하드웨어 제약

물리 메모리는 하드웨어 제약을 반영하여 여러 **존(zone)**으로 나뉜다:

  • ZONE_DMA: 16MB 이하, 레거시 ISA 디바이스를 위한 영역
  • ZONE_DMA32: 4GB 이하, 32비트 DMA가 가능한 디바이스용
  • ZONE_NORMAL: 일반적인 커널 및 사용자 메모리
  • ZONE_HIGHMEM: 32비트 시스템에서만 사용 (x86_64에서는 사용 안 함)

각 존은 독립적인 버디 할당자를 운영하며, 시스템은 가능한 한 상위 존에서 메모리를 할당하려 시도한다.

3.3 슬랩 할당자: 커널 객체 관리

버디 시스템이 페이지 단위의 큰 메모리를 관리한다면, **슬랩 할당자(Slab Allocator)**는 작은 커널 객체들을 효율적으로 관리한다. Ubuntu 24.04는 SLUB 할당자를 기본으로 사용하며, 이는 다음과 같은 특징을 갖는다:

핵심 개념:

  • 객체 캐싱: 자주 사용되는 객체 타입별로 전용 캐시를 유지
  • 사전 초기화: 객체를 미리 초기화하여 할당/해제 오버헤드 감소
  • CPU별 캐시: 멀티프로세서 환경에서 경합을 줄이기 위한 최적화

슬랩 캐시의 현재 상태는 다음으로 확인할 수 있다:

# 슬랩 캐시 정보 확인
cat /proc/slabinfo
# 실시간 슬랩 사용량 모니터링
slabtop

3.4 kmalloc과 vmalloc: 커널의 동적 메모리 할당

커널 코드는 두 가지 주요 함수를 통해 동적 메모리를 할당한다:

kmalloc():

  • 물리적으로 연속된 메모리 블록을 할당
  • DMA가 필요한 하드웨어 작업에 필수적
  • 일반적으로 128KB 이하의 할당에 사용
  • 슬랩 할당자를 통해 구현됨

vmalloc():

  • 가상적으로만 연속된 메모리 영역을 할당
  • 큰 버퍼나 모듈 로딩에 사용
  • 페이지 테이블 설정으로 인한 오버헤드 존재
  • 물리적 연속성이 불필요한 경우에 적합

이러한 커널의 다층적 할당 전략은 하드웨어 요구사항과 메모리 단편화라는 현실적 제약 사이에서 최적의 균형을 찾기 위한 설계이다.

4. 캐싱, 스와핑 및 I/O 성능

4.1 페이지 캐시: Linux의 I/O 가속기

Linux는 유휴 RAM을 적극적으로 활용하여 디스크 I/O 성능을 향상시킨다. **페이지 캐시(Page Cache)**는 최근에 읽거나 쓴 파일 데이터를 메모리에 유지하여, 후속 접근 시 디스크 I/O를 회피할 수 있게 한다.

페이지 캐시의 특성:

  • 투명성: 애플리케이션 수정 없이 자동으로 작동
  • 적응성: 메모리 압박 시 즉시 회수 가능
  • 통합성: 읽기와 쓰기 캐시가 통합되어 일관성 보장

시스템 관리자는 다음 명령으로 캐시 상태를 모니터링할 수 있다:

# 메모리 사용량과 캐시 확인
free -h
# 상세한 메모리 정보
cat /proc/meminfo | grep -E "^(Cached|Buffers|Active|Inactive)"

4.2 더티 페이지와 라이트백 메커니즘

파일에 대한 쓰기 작업은 즉시 디스크에 반영되지 않고 **더티 페이지(Dirty Page)**로 표시된다. 이러한 지연 쓰기 전략은 다음과 같은 이점을 제공한다:

  • 여러 쓰기 작업을 하나로 병합하여 I/O 효율성 향상
  • 단기간 내에 재작성되는 데이터에 대한 불필요한 디스크 쓰기 방지
  • 애플리케이션 응답성 향상

커널은 다음 조건에서 더티 페이지를 디스크로 플러시한다:

  • 더티 메모리가 임계값 초과 (dirty_ratio, dirty_bytes)
  • 페이지가 일정 시간 이상 더티 상태 유지 (dirty_expire_centisecs)
  • 주기적인 플러시 (dirty_writeback_centisecs)
  • 명시적인 sync 호출

관련 파라미터는 다음과 같이 조정할 수 있다:

# 현재 설정 확인
sysctl vm.dirty_ratio
sysctl vm.dirty_background_ratio
# 설정 변경 (영구 적용은 /etc/sysctl.conf 수정)
echo 10 > /proc/sys/vm/dirty_ratio

4.3 스왑 공간과 메모리 압박 관리

스왑(Swap) 공간은 물리 메모리가 부족할 때 덜 사용되는 메모리 페이지를 디스크에 저장하는 메커니즘이다. 이는 시스템의 가용 메모리를 효과적으로 확장하지만, 성능 저하를 수반할 수 있다.

스왑 사용의 최적화:

  • swappiness 파라미터는 커널이 스왑을 사용하는 경향성을 제어한다 (0-100)
  • 낮은 값은 스왑 사용을 억제하고, 높은 값은 적극적인 스왑을 유도한다
  • 일반적으로 서버 환경에서는 10-30의 값이 권장된다
# 스왑 사용량 확인
free -h
swapon -s
# swappiness 조정
cat /proc/sys/vm/swappiness
echo 10 > /proc/sys/vm/swappiness

4.4 kswapd와 메모리 회수

kswapd는 백그라운드에서 실행되는 커널 스레드로, 시스템의 여유 메모리를 적정 수준으로 유지하는 역할을 담당한다. 이는 다음과 같은 워터마크를 기준으로 작동한다:

  • min: 이 수준 이하로 떨어지면 직접 회수(direct reclaim) 발생
  • low: kswapd가 활성화되는 임계값
  • high: kswapd가 목표로 하는 여유 메모리 수준

메모리 회수 과정은 LRU(Least Recently Used) 알고리즘을 기반으로 하며, 활성(active)과 비활성(inactive) 리스트를 구분하여 관리한다.

5. 고급 메모리 관리 기능

5.1 메모리 오버커밋과 OOM Killer

Linux는 기본적으로 **메모리 오버커밋(Memory Overcommit)**을 허용한다. 이는 프로세스가 실제로 사용 가능한 것보다 더 많은 가상 메모리를 요청할 수 있음을 의미한다. 이 낙관적 접근은 대부분의 경우 효율적이지만, 때로는 문제를 야기할 수 있다.

오버커밋 정책:

  • 0 (heuristic): 커널이 휴리스틱을 사용하여 결정 (기본값)
  • 1 (always): 항상 오버커밋 허용
  • 2 (never): 오버커밋 금지, 엄격한 계산 적용

시스템이 실제로 메모리가 부족해지면 OOM Killer가 활성화된다:

# 오버커밋 설정 확인
cat /proc/sys/vm/overcommit_memory
cat /proc/sys/vm/overcommit_ratio
# OOM 점수 확인
cat /proc/<pid>/oom_score

5.2 NUMA 아키텍처와 메모리 지역성

현대의 다중 소켓 시스템은 NUMA(Non-Uniform Memory Access) 아키텍처를 채택한다. 이는 각 CPU가 로컬 메모리에 더 빠르게 접근할 수 있는 구조로, 메모리 접근 패턴이 성능에 큰 영향을 미친다.

NUMA 시스템에서의 최적화 전략:

  • 프로세스와 메모리를 동일한 NUMA 노드에 바인딩
  • 메모리 할당 정책을 애플리케이션 특성에 맞게 조정
  • NUMA 밸런싱을 통한 자동 최적화 활용
# NUMA 토폴로지 확인
numactl --hardware
numastat
# 프로세스의 NUMA 바인딩
numactl --cpubind=0 --membind=0 <command>

5.3 거대 페이지와 TLB 최적화

표준 4KB 페이지는 대용량 메모리 시스템에서 TLB 압박을 야기할 수 있다. **거대 페이지(Huge Pages)**는 이 문제를 해결하는 메커니즘이다:

거대 페이지의 유형:

  • 표준 거대 페이지: 명시적으로 예약하고 애플리케이션에서 직접 사용
  • 투명 거대 페이지(THP): 커널이 자동으로 관리하는 동적 거대 페이지
# 거대 페이지 설정 확인
cat /proc/meminfo | grep Huge
# THP 상태 확인
cat /sys/kernel/mm/transparent_hugepage/enabled

5.4 메모리 압축과 단편화 해결

시간이 지남에 따라 메모리 단편화는 큰 연속 블록 할당을 어렵게 만든다. Linux는 다음과 같은 메커니즘으로 이를 해결한다:

메모리 압축(Memory Compaction):

  • 이동 가능한 페이지를 재배치하여 연속 공간 확보
  • 백그라운드에서 자동 실행되거나 수동으로 트리거 가능
# 메모리 압축 상태 확인
cat /proc/pagetypeinfo
# 수동 압축 트리거
echo 1 > /proc/sys/vm/compact_memory

5.5 cgroups v2와 메모리 제어

Ubuntu 24.04는 cgroups v2를 기본으로 사용하여 프로세스 그룹의 메모리 사용을 세밀하게 제어할 수 있다:

# 메모리 제한 설정 (systemd 서비스)
systemctl set-property nginx.service MemoryMax=2G
systemctl set-property nginx.service MemoryHigh=1.8G
# cgroup 메모리 사용량 확인
systemd-cgtop

6. 메모리 사용량 모니터링 및 분석

6.1 /proc/meminfo의 이해

/proc/meminfo는 시스템 메모리 상태에 대한 가장 상세한 정보를 제공한다. 주요 필드의 의미는 다음과 같다:

  • MemTotal: 전체 사용 가능한 RAM
  • MemFree: 완전히 사용되지 않은 메모리
  • MemAvailable: 스왑 없이 새 애플리케이션이 사용 가능한 메모리
  • Buffers/Cached: 파일 시스템 메타데이터 및 페이지 캐시
  • Active/Inactive: LRU 리스트 기준 메모리 분류
  • Slab: 커널 슬랩 할당자가 사용하는 메모리

6.2 표준 모니터링 도구

효과적인 메모리 분석을 위한 도구 체계:

시스템 전체 모니터링:

# 기본 메모리 상태
free -h
# 시간별 변화 추적
vmstat 1
# 상세 통계
sar -r 1 10

프로세스별 분석:

# 메모리 사용량 상위 프로세스
ps aux --sort=-%mem | head
# 상세 메모리 맵
pmap -x <pid>
# 정확한 메모리 계산 (PSS 포함)
smem -r -s pss

6.3 고급 분석 방법론

메모리 문제의 체계적 접근:

  1. 시스템 수준 관찰: free, vmstat로 전체적인 메모리 압박 확인
  2. 프로세스 식별: top, ps로 문제 프로세스 특정
  3. 상세 분석: pmap, /proc/<pid>/smaps로 메모리 사용 패턴 파악
  4. 근본 원인 추적: perf, bpftrace로 할당 호출 스택 분석
# 메모리 할당 추적 (perf 사용)
perf record -e kmem:kmalloc -a sleep 10
perf report
# BPF 기반 실시간 추적
bpftrace -e 'kprobe:kmalloc { @[kstack] = count(); }'

7. 실무 적용 가이드라인

7.1 일반적인 메모리 문제와 해결책

높은 메모리 사용률:

  • 원인: 메모리 누수, 과도한 캐싱, 부적절한 애플리케이션 설정
  • 진단: smem으로 PSS 기준 실제 사용량 확인
  • 해결: 애플리케이션 메모리 제한 설정, 캐시 파라미터 조정

과도한 스왑 사용:

  • 원인: 물리 메모리 부족, 높은 swappiness 값
  • 진단: vmstat의 si/so 값 모니터링
  • 해결: swappiness 감소, 메모리 증설, 워크로드 분산

OOM Killer 발생:

  • 원인: 메모리 오버커밋, 급격한 메모리 사용 증가
  • 진단: dmesg, /var/log/kern.log 확인
  • 해결: 오버커밋 정책 조정, 메모리 제한 설정, 모니터링 강화

7.2 성능 최적화 권장사항

  1. 적절한 swappiness 설정: 데이터베이스 서버는 1-10, 일반 서버는 10-30
  2. THP 고려사항: 대부분의 데이터베이스는 THP 비활성화 권장
  3. NUMA 최적화: 크리티컬 애플리케이션은 NUMA 노드 바인딩 고려
  4. 메모리 제한 활용: systemd 또는 cgroups로 리소스 격리
  5. 정기적인 모니터링: 메모리 사용 패턴의 베이스라인 확립

8. 결론

Linux 메모리 관리 서브시스템은 복잡하지만 정교하게 설계된 시스템이다. 물리적 하드웨어의 제약과 현대 컴퓨팅의 요구사항 사이에서 최적의 균형을 찾기 위해 진화해왔다. 시스템 관리자로서 이러한 메커니즘을 이해하는 것은 단순히 문제를 해결하는 것을 넘어, 시스템의 잠재력을 최대한 활용하고 안정적인 서비스를 제공하는 데 필수적이다.

본 문서에서 다룬 개념들은 Ubuntu 24.04를 기준으로 하였으나, 대부분의 원리는 다른 Linux 배포판에도 적용된다. 지속적인 학습과 실무 경험을 통해 이러한 지식을 심화시키고, 변화하는 기술 환경에 적응해 나가기를 권한다.

참고 하면 좋을 자료

공식 문서

심화 학습 사이트

실무 도구 및 가이드

한국어 자료

모니터링 도구 공식 사이트

추천 온라인 강의

커뮤니티 및 포럼