리눅스 메모리 관리 기초: Christopher Lameter (Jump Trading) 강연 요약

13 minute read

MM101: Introduction to Linux Memory Management

이 포스트는 Jump Trading LLC의 크리스토퍼 라메터(Christopher Lameter)가 발표한 “MM101: Introduction to Linux Memory Management” 강연 내용을 학습 및 공유 목적으로 상세히 정리한 글입니다.

이 강연은 리눅스 커널의 복잡한 메모리 관리 기법들을 설명하기에 앞서, 개발자와 시스템 엔지니어가 반드시 알아야 할 메모리 관리의 물리적/논리적 기초 개념을 다룹니다. 물리 메모리와 가상 메모리의 차이, 페이지 폴트, 페이지 캐시, 스와핑, 오버커밋, 그리고 성능 최적화에 이르기까지 핵심적인 가이드라인을 정리했습니다.


1. 리눅스 메모리의 기본 단위: 물리 메모리와 페이지 (Physical Memory & Pages)

리눅스 커널은 메모리를 선형적(linear)으로만 다루지 않고, 물리 메모리를 일정한 크기의 조각으로 나누어 관리합니다. 이 물리적인 최소 단위를 페이지(Page)라고 합니다.

  • 페이지와 페이지 프레임 번호 (Page Frame Number, PFN)
    • 페이지 프레임: 물리 메모리를 나누어 놓은 수많은 4KB 크기의 격자 칸들을 의미합니다. 서류 캐비닛의 서랍 칸 하나하나로 비유할 수 있습니다.
    • 물리 PFN (Page Frame Number): 물리 메모리의 첫 번째 프레임부터 마지막 프레임까지 순차적으로 부여되는 인덱스 번호(0부터 N까지)입니다. 물리 주소는 PFN페이지 내 오프셋(Offset)의 조합으로 식별됩니다. (예: 12번 프레임의 오프셋 10)
    • 페이지 프레임의 개수: 4GB의 물리 RAM을 가진 시스템은 대략 100만 개((4 \times 10^9 / 4096))의 물리 페이지 프레임을 갖습니다. 시스템 메모리가 테라바이트(TB) 급으로 늘어남에 따라 커널이 관리해야 할 페이지 프레임의 개수가 수억 개에 달하게 되어 이를 효율적으로 관리하는 것이 큰 당면 과제입니다.
  • 페이지 크기 (Page Sizes)
    • Intel x86/x86_64 아키텍처: 1970~1980년대부터 전통적으로 4KB 크기를 기본 페이지 크기로 채택하여 사용하고 있습니다.
    • 기타 아키텍처: IBM POWER나 ARM 등 일부 다른 하드웨어 아키텍처는 64KB 등 더 큰 기본 페이지 크기를 지원하여 관리 대상 페이지의 개수를 줄이고 효율성을 높이기도 합니다.

2. 가상 메모리와 페이지 테이블 (Virtual Memory & Page Tables)

하드웨어의 MMU(Memory Management Unit)와 운영체제는 물리 메모리의 한계를 극복하고 보안 및 안정성을 확보하기 위해 가상 메모리(Virtual Memory) 시스템을 제공합니다.

[가상 주소 공간 (Virtual Address)]
     (프로세스 A)  ---> [페이지 테이블 A] --+---> [물리 메모리 (Physical RAM)]
     (프로세스 B)  ---> [페이지 테이블 B] --|---> (서로 격리된 PFN 매핑)
                                          +---> (공유 라이브러리인 경우 동일 PFN 가리킴)
  • 페이지 테이블 (Page Table)의 역할과 프로세스 격리
    • 프로세스마다 고유한 가상 메모리 공간(Virtual Address Space)을 가집니다.
    • 가상 주소를 물리 주소로 변환(Translation)하기 위해 커널은 프로세스별로 독립된 페이지 테이블(Page Table)을 유지합니다.
    • 예를 들어 프로세스 A의 가상 페이지 2번은 물리 페이지 6번을 가리키도록 설정하고, 프로세스 B의 가상 페이지 2번은 물리 페이지 90번을 가리키도록 하여 서로의 메모리 영역에 개입하지 못하도록 완벽하게 격리시킵니다. 이는 프로세스 안정성뿐만 아니라 보안(Security)의 기초가 됩니다.
  • 온디맨드 페이징 (On-Demand Paging)
    • 프로세스가 시작될 때 바이너리 전체를 한 번에 물리 메모리에 올리지 않습니다. 처음에는 페이지 테이블 엔트리(PTE)를 비워두고 프로세스를 시작합니다.
    • 프로세스가 실제로 특정 메모리 주소(코드, 데이터 등)를 참조(Access)하려 할 때 비로소 메모리를 할당하는 방식을 온디맨드 페이징이라고 합니다.
    • mmap의 물리 메모리 할당 시점: mmap() 호출 시점에는 가상 주소 범위를 예약하고 파일과의 대응 관계만 설정할 뿐, 실제 물리 메모리(RAM)를 즉시 할당하지 않습니다. 실제 데이터가 적재되는 물리 페이지는 프로세스가 해당 영역에 처음 접근해 페이지 폴트를 발생시키는 순간 온디맨드 페이징을 통해 공급됩니다.
  • 페이지 폴트 (Page Fault) 메커니즘
    • 동작 원리: CPU가 접근하려는 가상 주소의 페이지 테이블 엔트리가 비어 있거나 유효하지 않을 때, 하드웨어(MMU)는 페이지 폴트 인터럽트를 발생시키고 CPU 제어권을 커널로 넘깁니다.
    • 메이저 페이지 폴트 (Major Page Fault): 참조하려는 데이터가 물리 메모리에 없어서 디스크(실행 바이너리 파일 또는 스왑 영역)로부터 블록을 읽어와야 하는 상황입니다. 디스크 I/O가 동반되므로 매우 느립니다.
    • 마이너 페이지 폴트 (Minor Page Fault): 데이터를 디스크에서 직접 읽어올 필요는 없지만(예: 커널의 페이지 캐시에는 이미 존재하지만 내 가상 페이지 테이블에는 매핑이 안 된 경우, 혹은 새 메모리 할당 시), 페이지 테이블 엔트리만 새로 채우고 매핑해 주는 가벼운 연산입니다.

3. 프로세스 메모리 맵과 페이지 공유 (Process Memory Map & Sharing)

하나의 프로세스가 실행되면 커널은 가상 메모리 공간에 다양한 성격의 메모리 영역(Segment)들을 나누어 맵(Map)을 형성하고 관리합니다.

  • 프로세스 메모리 맵 구조
    • 텍스트/바이너리 영역 (Binary/Code): 실행할 기계어 명령어가 들어 있는 읽기 전용 영역입니다.
    • 스택 (Stack): 함수 호출 시 반환 주소, 로컬 변수 등을 저장하며 메모리 상위 주소에서 아래 방향으로 확장됩니다.
    • 힙 (Heap): 동적 메모리 할당(malloc 등)을 위한 공간으로 아래에서 위 방향으로 확장됩니다.
    • 공유 라이브러리 영역 (Shared Libraries): glibc 등 공용 라이브러리가 매핑되는 공간입니다.
  • 페이지 공유 (Shared Mappings)
    • 동일한 바이너리 코드나 동일한 공유 라이브러리를 여러 프로세스가 동시에 사용할 때, 시스템은 굳이 각 프로세스마다 별도의 물리 메모리를 할당하지 않습니다.
    • 물리 메모리 상에 해당 라이브러리(예: libc.so)의 코드가 들어 있는 단 하나의 페이지 프레임만 올려두고, 이를 사용하는 모든 프로세스의 페이지 테이블이 동일한 물리 주소를 가리키도록 설정합니다.
    • 이 공유 페이지들은 기본적으로 읽기 전용(Read-Only)으로 설정되어 코드 오염을 방지합니다.
    • 공유 페이지의 개별 권한 설정: 여러 프로세스의 가상 주소가 동일한 물리 페이지(PFN)를 공유하더라도, 각 프로세스의 페이지 테이블 엔트리(PTE)에 정의된 접근 권한은 다를 수 있습니다. 예를 들어 프로세스 A는 Read/Write 권한으로, 프로세스 B는 Read-only 권한으로 동일한 물리 메모리 페이지를 가리킬 수 있습니다. 이때 프로세스 B가 쓰기를 시도하면 권한 위반으로 페이지 폴트가 발생합니다.
  • 쓰기 시 복사 (Copy-on-Write, COW)
    • 개념: 여러 프로세스가 공유하고 있는 페이지에 어떤 프로세스가 쓰기(Write) 연산을 시도하면, 하드웨어가 읽기 전용 보호 상태를 감지하여 커널에 폴트를 알립니다.
    • 동작: 커널은 쓰기를 시도한 프로세스를 위해 새로운 물리 페이지를 할당하고 기존 내용을 복사한 뒤, 해당 프로세스의 페이지 테이블이 새 페이지를 가리키도록 업데이트하고 쓰기 권한을 부여합니다. 나머지 프로세스들은 여전히 원본 물리 페이지를 가리킵니다.
    • 권한 위반 및 예외 처리: CPU의 MMU가 페이지 테이블에 명시된 권한을 실시간 검사합니다. 프로그램이 권한을 위반하는 접근(예: Read-only 페이지에 Write 시도)을 수행하면 CPU는 하드웨어 예외(Exception)를 발생시키고 제어권을 커널로 넘깁니다. 커널은 예외 원인을 분석하여 1) 쓰기 시 복사(COW)를 처리하여 새 페이지를 할당하거나, 2) 단순 온디맨드 할당을 수행하거나, 3) 유효하지 않은 접근일 경우 프로세스에 SIGSEGV 시그널을 보내 비정상 종료시킵니다.
    • 적용 사례 (Fork): 프로세스가 자식 프로세스를 생성하는 fork() 시스템 콜을 호출할 때 이 방식이 적극적으로 사용됩니다. 자식 프로세스는 부모의 페이지 테이블을 그대로 복사해 오며 모든 매핑을 COW(읽기 전용)로 지정합니다. 실제 쓰기 변경이 발생하는 부분만 물리 메모리가 추가 할당되므로, fork 시 메모리 복사 부담을 획기적으로 줄여줍니다.

4. 스와핑과 메모리 회수 (Swapping & Memory Reclamation)

시스템의 물리 메모리가 부족해지면 운영체제는 덜 사용된 물리 페이지를 비워내어 다른 다급한 요청에 재할당해야 합니다. 이를 메모리 회수(Reclamation)라고 합니다.

  • 페이지 종류에 따른 회수 방식 차이
    • 파일 백업 페이지 (File-backed Pages): 디스크에 원본 파일이 있는 페이지들(예: 프로그램 코드, 파일 읽기 캐시)입니다. 데이터를 따로 보존할 필요 없이 페이지 테이블의 매핑만 해제(Invalidate)하고 바로 물리 메모리를 비워낼 수 있습니다. 나중에 다시 접근하면 디스크에서 읽어오면(Page Fault) 되기 때문입니다.
    • 익명 페이지 (Anonymous Pages): 디스크의 파일과 연결되어 있지 않고 런타임에 동적으로 만들어진 데이터 페이지들(예: Heap 할당 데이터, Stack 데이터)입니다. 이 페이지들은 그냥 버리면 데이터가 영구 유실되므로 비워내기 전에 반드시 디스크의 특정 공간에 저장해 두어야 합니다.
  • 스와핑 (Swapping)
    • 익명 페이지를 메모리에서 몰아내기 위해, 그 내용을 디스크의 지정된 특수 공간인 스왑 디바이스/스왑 영역(Swap Space)에 복사한 후 페이지 매핑을 지우고 물리 메모리를 확보하는 기법입니다.
    • 스왑 아웃된 메모리에 프로세스가 다시 접근하면, 커널은 실행을 잠시 멈추고 디스크 스왑 영역에서 해당 데이터를 다시 물리 메모리로 읽어들이는 스왑 인(Swap In) 과정을 거쳐 복구합니다.
    • 스와핑은 성능에 엄청난 악영향을 미칩니다. 메모리가 매우 부족해 스와핑 빈도가 증가하면 디스크 I/O 병목으로 인해 시스템 전체가 멈칫하며 느려지는 쓰레싱(Thrashing) 상태에 빠지게 됩니다.
  • 제로 페이지 (Zero Page) 최적화
    • 프로그램들이 수십 GB의 메모리를 요청한 뒤 0으로 초기화만 해두는 경우가 많습니다.
    • 리눅스는 이를 최적화하기 위해, 항상 0으로 채워져 있는 단 하나의 물리 페이지인 제로 페이지(Zero Page)를 공유 매핑해 줍니다.
    • 프로세스가 실제로 이 영역에 0이 아닌 값을 쓰는(Modify) 순간 Copy-on-Write가 일어나며 비로소 개별 물리 메모리 페이지가 할당됩니다. 덕분에 5GB의 메모리를 0으로 초기화하여 할당하더라도 실제 쓰기가 일어나기 전까지는 단 4KB의 물리 메모리만 소비하게 됩니다.

5. 페이지 캐시와 더티 페이지 관리 (Page Cache & Dirty Pages)

리눅스는 디스크 I/O 속도 한계를 극복하기 위해 메모리의 일부를 파일 데이터 저장 버퍼로 활용하는 페이지 캐시(Page Cache) 기술을 제공합니다.

  • 읽기 최적화: 페이지 캐시와 읽기 미리 가져오기 (Read-Ahead)
    • 페이지 캐시: 한 번 디스크에서 읽어온 파일 데이터는 메모리의 페이지 캐시 영역에 유지되어, 차후 동일 파일 접근 시 디스크 I/O 없이 초고속으로 메모리에서 즉시 서빙됩니다.
    • 읽기 미리 가져오기 (Read-Ahead): 프로세스가 파일의 특정 부분을 읽을 때, 향후 순차적으로 다음 부분도 읽을 확률이 높다는 가정 하에 커널은 배경에서 약 512KB 등 더 큰 블록을 한꺼번에 디스크에서 미리 메모리로 읽어옵니다. 이는 디스크 헤더 이동 헤드 오버헤드가 큰 물리 디스크 환경에서 특히 지연을 크게 감소시켜 주는 유용한 기법입니다.
  • 쓰기 최적화: 더티 페이지 (Dirty Pages)와 쓰기백 (Writeback)
    • 더티 페이지 (Dirty Page): 프로세스가 파일 매핑 메모리에 값을 썼으나, 아직 실제 디스크에는 기록되지 않고 메모리에만 수정된 채 남아 있는 페이지입니다.
    • 쓰기백 (Writeback): 백그라운드 스레드(pdflush/flusher 데몬 등)가 주기적으로 메모리를 스캔하여 더티 페이지들을 모아 디스크에 물리적으로 기록하고 이를 ‘깨끗한 페이지(Clean Page)’로 변경하는 프로세스입니다.
    • mmap 수정 데이터의 디스크 반영: 파일 공유 매핑 상태에서 메모리 데이터를 수정하면 해당 페이지는 캐시 상에서 즉시 더티 페이지로 마킹됩니다. 이후 커널의 쓰기백 스레드가 파일 시스템 및 디바이스 드라이버를 거쳐 비동기적으로 디스크에 기록합니다.
    • 데이터 일관성 경고: 더티 페이지가 디스크로 가기 전에 시스템 전원이 꺼지면 메모리에만 있던 데이터는 유실됩니다. 이를 강제로 디스크에 완전 동기화하는 명령어/시스템 콜이 바로 sync, fsyncmsync입니다. 데이터베이스 등 영속성이 중요하고 안전한 설계를 위해서는 이 쓰기백 주기와 강제 동기화 시점을 정밀하게 제어해야 합니다.

6. 시스템 메모리 모니터링: /proc/meminfo 상세 분석

리눅스 시스템 전체의 메모상태를 파악하기 위해 모든 모니터링 툴(free, top, vmstat 등)은 /proc/meminfo 가상 파일을 읽어서 정보를 파악합니다.

지표명 설명 상세 가이드라인
MemTotal 시스템의 전체 물리 RAM 크기 시스템이 인식한 최종 메모리 양
MemAvailable 새로운 프로세스에 할당 가능한 가상 메모리 여유분 단순 free 메모리뿐만 아니라, 필요 시 즉시 회수 가능한 페이지 캐시 영역까지 포함한 실질적 가용 공간
Cached 페이지 캐시로 사용 중인 메모리 크기 디스크에서 읽어와 메모리에 올려둔 파일 데이터 양 (대부분 언제든 회수 가능)
SwapCached 스왑 디바이스에도 존재하고, 실제 메모리에도 올라와 있는 페이지 크기 메모리가 넉넉해지더라도 바로 디스크 스왑을 지우지 않고 유지함으로써 나중에 메모리 부족 시 쓰기 없이 즉시 에빅션할 수 있도록 남겨둔 최적화용 영역
Active / Inactive 최근에 사용된 메모리 / 오랫동안 사용되지 않은 메모리 커널은 LRU(Least Recently Used) 알고리즘을 변형하여 페이지들을 Active 리스트와 Inactive 리스트로 관리합니다. 메모리 부족 시 Inactive 리스트에 있는 페이지들이 우선적으로 회수되거나 스왑 아웃됩니다. 만약 Inactive에 있던 페이지를 프로세스가 다시 참조하면 즉시 Active로 옮겨집니다.
PageTables 가상 주소와 물리 주소를 맵핑하는 페이지 테이블 자체의 메모리 점유량 수 TB 급의 거대한 가상 주소 공간을 매핑하는 대규모 프로세스가 많을 경우 이 페이지 테이블을 유지하는 자체 비용(PTE overhead)이 수십~수백 MB 이상으로 증가할 수 있습니다.
Dirty 디스크에 아직 쓰이지 않은 미동기화 메모리 크기 이 수치가 0이 아닐 때 응급 전원 차단을 수행하면 최근 수정 데이터 유실 위험이 있습니다.
Writeback 현재 디스크로 동기화 작업이 진행 중인 페이지 크기 대용량 복사 시 이 수치가 일시적으로 높게 상승합니다.

7. 프로세스 수준 메모리 감시 및 제어 (Process Memory Details)

개별 프로세스가 소비하는 구체적인 메모리 정보는 /proc/<PID>/status 가상 파일과 리소스 한도를 설정하는 ulimit 등을 통해 모니터링하고 제어할 수 있습니다.

  • /proc/<PID>/status 핵심 메모리 필드
    • VMPeak: 프로세스가 실행 중 기록한 최대 가상 메모리 크기(Peak Virtual Size).
    • VMSize: 현재 매핑되어 있는 총 가상 메모리 크기(Virtual Memory Size).
    • VMLck: 물리 메모리에 강제로 잠금 고정(Mlocked)되어 절대 스왑 아웃되지 않도록 보호된 메모리 양.
    • VMRSS (Resident Set Size): 프로세스가 현재 실제로 점유하고 있는 물리 메모리 크기. (실제 RAM 사용량)
    • VMAnon: 디스크 파일 백업이 없는 순수 익명 힙/스택 데이터 물리 메모리 점유량.
    • VMSwap: 이 프로세스의 메모리 중 물리 RAM에서 밀려나 스왑 디바이스로 가 있는 가상 메모리 양. 이 수치가 높고 계속 증가한다면 메모리 부족으로 해당 프로세스가 병목을 겪고 있음을 뜻합니다.
  • 정확한 실제 프로세스 메모리 기여도 측정 (smaps와 PSS)
    • RSS의 한계: 단순히 /proc/<PID>/status의 RSS(Resident Set Size)만으로는 여러 프로세스가 공유하는 라이브러리 및 메모리가 섞여 있어 개별 프로세스의 정확한 실제 물리 메모리 소비량을 알기 어렵습니다.
    • PSS (Proportional Set Size): 공유 페이지의 물리 메모리 크기를 이를 공유하는 프로세스 개수로 나누어 합산한 실질적인 메모리 점유 측정 지표입니다. 실무에서는 /proc/<PID>/smaps 파일 등을 통해 PSS 값을 확인하는 것이 프로세스의 실제 메모리 기여도(Footprint)를 측정하는 가장 정확한 방법입니다.
  • ulimit을 통한 자원 제어
    • 개별 프로세스가 시스템 전체 메모리를 독차지하여 시스템을 다운시키는 것(OOM)을 막기 위해 쉘 레벨에서 자원 한도를 설정할 수 있습니다.
    • 가상 메모리 크기 제한, 물리 잠금 메모리 크기 제한(ulimit -l), 열 수 있는 파일 디스크립터 한도 등을 정밀하게 조율할 수 있습니다.

8. 커널 가상 메모리 튜닝 매개변수 (Virtual Memory Tuning)

리눅스 커널은 /proc/sys/vm/ 디렉토리 아래의 가상 파일 노브들을 통해 가상 메모리 서브시스템의 핵심 거동을 조율할 수 있는 정책 튜닝 기능을 제공합니다.

  • 오버커밋 정책 설정 (/proc/sys/vm/overcommit_memory)
    • 시스템에 장착된 물리 RAM과 스왑 영역을 합한 크기보다 더 큰 가상 메모리 할당 요청을 받았을 때의 커널 대처 모드입니다.
    • 0 (Heuristic Overcommit - 기본값): 커널이 휴리스틱하게 판단하여, 프로세스들이 메모리 공간만 요청하고 실제 쓰지 않을 것을 감안해 가급적 다 받아주되 지나치게 터무니없는 요청은 거부합니다.
    • 1 (Always Overcommit): 모든 메모리 할당 요청을 무조건 수락합니다. 4GB 메모리 환경에서 100GB 할당 요청도 가상 수준에서는 성공합니다. 실제 메모리를 채워 쓰기 시작할 때 시스템이 스와핑 폭발로 크롤링하다가 멈추게 됩니다.
    • 2 (Strict Overcommit): 절대 무리한 오버커밋을 허용하지 않습니다. 허용할 수 있는 최대 가상 메모리는 [스왑 크기] + ([물리 RAM 크기] * overcommit_ratio / 100) 로 엄격히 계산되며, 이를 초과하는 메모리 할당(malloc) 시 즉시 메모리 부족 에러를 리턴합니다.
  • 더티 페이지 쓰기백 주기 튜닝 매개변수
    • dirty_writeback_centisecs: 수정된 더티 페이지를 디스크로 내보내기 위해 깨어나는 플러셔 스레드의 실행 주기(1/100초 단위). 예: 200으로 설정 시 최대 2초 동안만 메모리에 머물고 동기화가 예약됩니다.
    • dirty_background_ratio: 전체 시스템 메모리 중 더티 페이지가 차지하는 비율이 이 수치(%)에 도달하면, 커널은 배경(Background)에서 플러싱 스레드를 깨워 백그라운드로 데이터를 디스크에 기록하기 시작합니다. (보통 5~10% 권장)
    • dirty_ratio: 더티 페이지 비율이 이 상한선 수치(%)에 다다르면, 커널은 해당 프로세스가 진행 중인 파일 쓰기 연산(I/O)을 강제로 일시 정지(Block)시키고 직접 디스크 동기화 작업(Writeback)을 먼저 수행하도록 강제합니다. (보통 40~60% 권장, 시스템 안정성 가드레일 역할을 함)

9. 고성능 메모리 최적화 및 Q&A 핵심 정리 (Performance Optimization & Tips)

Christopher Lameter는 강연 마무리 Q&A 과정에서 저수준 고성능 네트워크 및 금융 트레이딩과 같이 극도로 레이턴시가 중요한 환경을 위한 구체적인 최적화 팁을 공유했습니다.

  • mmap 기반 파일 처리 vs. 일반 파일 시스템 콜 (fwrite/write)
    • 일반 파일 I/O: write 시스템 콜 시 항상 커널 공간으로의 컨텍스트 스위치(Context Switch)가 동반되며, 커널 버퍼와 유저 버퍼 간 메모리 복사 오버헤드가 발생합니다.
    • mmap: 가상 메모리 주소 영역을 파일 데이터에 다이렉트로 매핑하여 포인터 연산으로 데이터를 직접 다룹니다. 이미 메모리에 올라와 있는 파일 캐시 페이지에 접근할 때는 OS 개입과 시스템 콜이 완전히 생략되므로 극도로 빠릅니다.
    • 속도의 조건성 (Fast vs. Slow): mmap은 페이지가 이미 RAM(페이지 캐시)에 준비되어 있을 때는 시스템 콜이 없어 매우 빠르지만, 페이지가 메모리에 없어 Page Fault가 발생하고 디스크 I/O를 수행해야 할 때는 오히려 큰 오버헤드가 동반됩니다.
  • 지연시간 예방 기법: 선 할당 (MAP_POPULATE & mlock)
    • mmap이 빠르더라도 런타임 실행 중 페이지 폴트가 발생하면 디스크 I/O가 동반되어 순간적으로 심각한 지연 시간(Latency Spike)이 발생할 수 있습니다.
    • MAP_POPULATE 플래그: mmap 호출 시점에 모든 페이지 엔트리를 채우고 미리 데이터를 읽어두도록(Pre-populate) 지시하여, 임계 영역(Critical Section) 실행 중 페이지 폴트가 발생하는 것을 방지합니다.
    • mlock / mlockall 시스템 콜: 특정 메모리 페이지가 메모리 부족 상황에서도 절대 디스크로 스왑 아웃(Swap Out)되거나 회수되지 않도록 물리 RAM에 꽉 묶어두는 보호막 역할을 합니다. 이를 적절히 조합하면 런타임 성능 편차를 최소화할 수 있습니다.
  • DMA와 하드웨어의 협업 (Direct Memory Access)
    • 동작 원리: DMA는 CPU의 개입 없이 주변 장치들이 시스템 RAM과 데이터를 직접 주고받는 기술입니다. CPU가 장치에 전송 명령을 하달하면 장치가 메모리와 직접 데이터를 전송하고, 완료 시 인터럽트로 CPU에 보고합니다. CPU가 데이터를 1바이트씩 직접 복사하지 않으므로 시스템 효율이 극대화됩니다.
    • 주요 적용 장치: 고속 네트워크 카드(NIC), SSD/HDD 컨트롤러, GPU 등 현대의 거의 모든 고성능 I/O 장치들이 DMA를 사용하며, 디바이스 드라이버가 이를 지원하지 않는 특수한 경우에만 예외적으로 CPU 기반 접근이나 Polling이 쓰입니다.
  • TLB (Translation Lookaside Buffer) 캐싱
    • 하드웨어 주소 변환 캐시: 가상 주소를 물리 주소로 바꿀 때 메모리에 있는 페이지 테이블을 매번 탐색하면 속도가 느려지므로, CPU 내부의 초고속 캐시 메모리인 TLB에 최근 변환 결과를 저장해 둡니다.
    • TLB Hit vs. Miss: 가상 메모리 접근 시 TLB에 정보가 있으면(Hit) 즉시 주소가 변환되고, 없으면(Miss) 메모리의 페이지 테이블을 탐색한 뒤 TLB를 갱신합니다.
    • Huge Pages 활용: 메모리 접근 패턴이 산발적이거나 변환 범위가 넓으면 TLB Miss가 증가해 성능이 저하됩니다. 고성능 애플리케이션은 HugeTLB(Huge Pages, 2MB 또는 1GB 단위)를 사용하여 주소 변환 엔트리 자체의 개수를 줄임으로써 TLB 미스를 방지합니다.

10. 종합 요약: 엔드투엔드 가상 메모리 동작 흐름 (End-to-End Virtual Memory Flow)

프로세스가 mmap()을 통해 매핑된 파일의 특정 영역을 처음으로 읽고 수정하여 디스크에 반영하기까지의 전체 라이프사이클을 요약한 흐름도입니다.

sequenceDiagram
    autonumber
    actor App as 애플리케이션
    participant MMU as 하드웨어 (MMU)
    participant Kernel as 리눅스 커널 (OS)
    participant Cache as 페이지 캐시 (RAM)
    participant Disk as 디스크 (Storage)

    Note over App, Disk: 1. 초기 매핑 및 접근
    App->>Kernel: mmap() 시스템 콜 호출 (가상 주소 영역 예약)
    Kernel-->>App: 매핑 완료 (아직 물리 페이지 미할당)
    App->>MMU: 매핑된 주소 읽기(Read) 시도
    MMU->>Kernel: 페이지 테이블 조회 시 Present 비트 없음 -> Page Fault 발생
    
    Note over Kernel, Disk: 2. 페이지 폴트 처리 및 디스크 I/O
    Kernel->>Cache: 데이터 검색 (Page Cache Look-up)
    alt 캐시에 데이터가 없는 경우 (Major Page Fault)
        Kernel->>Disk: 디스크에서 메모리로 파일 블록 읽기 요청 (DMA)
        Disk-->>Cache: 데이터 적재 완료
    else 캐시에 데이터가 있는 경우 (Minor Page Fault)
        Note over Cache: 즉시 메모리 매핑 준비
    end
    
    Note over Kernel, MMU: 3. 페이지 테이블 매핑 및 TLB 갱신
    Kernel->>Kernel: 물리 페이지(PFN)를 해당 프로세스 페이지 테이블에 바인딩
    Kernel->>MMU: TLB 변환 캐시 정보 갱신 (PTE 로드)
    Kernel-->>App: 중단되었던 명령어 재개
    App->>MMU: 메모리 데이터 접근 (TLB Hit -> 고속 변환 완료)

    Note over App, Disk: 4. 데이터 수정 및 비동기 쓰기백 (Writeback)
    App->>MMU: 데이터 수정 (Write) 시도
    MMU->>Cache: 메모리 값 수정 & 해당 페이지를 Dirty 상태로 마킹
    Note over Kernel: 더티 페이지 비율/만료 시간 임계치 도달 (Writeback 데몬 기동)
    Kernel->>Disk: 더티 페이지 동기화 명령 하달
    Disk->>Cache: DMA를 이용해 디스크로 데이터 전송 및 쓰기 완료
    Kernel->>Cache: 페이지 상태를 Clean으로 환원

Leave a comment