Linux Memory Management를 소스에서 읽는 순서

malloc()으로 받은 주소에 값을 쓰는 일과 RAM에서 page를 확보하는 일은 같은 시점에 일어나지 않을 수 있습니다. File을 읽을 때도 저장 장치에서 매번 data를 가져오는 것은 아닙니다. 이 차이를 따라가려면 API의 사용법에서 한 걸음 더 들어가, kernel이 주소와 physical memory를 언제 연결하고 언제 회수하는지 살펴보아야 합니다.

Memory management는 kernel 내부의 독립적인 부품에 그치지 않습니다. Kernel의 다른 subsystem과 userspace가 동작하는 기반이며, allocation, mapping, fault, cache, reclaim이 서로 영향을 주는 영역입니다. 기능 이름만 외우면 각각은 이해한 듯해도 실제 동작을 연결하기 어렵습니다.

개념을 잡고 해당 구현으로 내려갑니다

Lorenzo Stoakes의 The Linux Memory Manager는 기본 개념에서 출발해 실제 source code를 따라 올라가는 bottom-up 접근을 취합니다. 높은 수준의 개요만으로 구현을 대신하지 않고, code의 개별 동작을 개념 설명과 함께 읽는 방식입니다. 이 연재도 data structure가 무엇을 표현하는지 확인한 뒤 그 상태를 변경하는 경로를 살펴보겠습니다.

이를테면 physical memory에서는 먼저 page와 folio가 어떤 단위를 나타내는지, node와 zone이 어떤 제약을 담는지 구분해야 합니다. 그다음 allocator가 요청을 어느 free list에서 만족시키고, 실패했을 때 어떤 조건으로 다음 경로를 선택하는지 읽을 수 있습니다. Virtual memory에서도 주소 범위의 정책을 나타내는 VMA와 실제 translation을 나타내는 page table을 분리해서 이해해야 합니다.

Source commentary와 개념 설명은 서로를 보완합니다. 함수 호출 순서만 적으면 왜 그런 검사가 필요한지 놓치기 쉽고, 개념도만 보면 lock, reference count, retry, failure path가 사라지기 쉽습니다. 설명과 code를 오가며 정상 경로뿐 아니라 상태가 바뀌는 경계도 확인하겠습니다.

Kernel version을 고정해서 읽습니다

Kernel은 계속 바뀝니다. 같은 기능을 설명하더라도 함수 이름, 인자, data structure와 최적화 경로가 version마다 달라질 수 있습니다. 따라서 함수 이름이 같다는 이유만으로 서로 다른 version의 구현을 한 경로로 이어 붙여서는 안 됩니다.

이 연재에서는 각 글의 참고 자료에 확인한 source와 version을 남기고, version 차이가 설명에 영향을 주는 부분은 본문에서도 구분하겠습니다. 특정 구현을 읽는 목적은 그 version의 호출 순서를 외우는 데 있지 않습니다. 바뀐 code를 다시 찾아 읽을 수 있도록, 기능을 지탱하는 원리와 source를 탐색하는 방법을 함께 익히는 데 있습니다.

예제의 의미도 구분해야 합니다. 개념적인 흐름도는 실행 가능한 kernel code가 아니며, 실제 code를 인용한 부분과 같은 것으로 읽으시면 안 됩니다. 실험 결과를 설명할 때는 실행한 환경과 관찰한 범위 안에서 해석하겠습니다.

어느 정도의 배경지식이 필요한가요

C의 pointer, structure, union과 기본적인 운영체제 개념을 알고 계시면 출발하실 수 있습니다. 처음부터 memory management 전체를 알아야 하는 것은 아닙니다. 궁금한 기능을 출발점으로 삼되, 설명에 등장하는 allocation 단위와 mapping 관계를 확인하면서 범위를 넓혀 가시면 됩니다.

이 주제는 kernel 개발자에게만 필요한 지식도 아닙니다. Userspace에서 보이는 memory 사용량, page fault와 file I/O의 동작을 이해하려는 개발자에게도 연결되는 내용입니다. 구현 안에 숨어 있는 전제와 관행을 명시적으로 읽어 내는 것이 중요합니다.

Allocation에서 회수까지 연결합니다

처음에는 physical memory의 표현과 allocation을 살펴봅니다. 이어 virtual address가 page table을 거쳐 physical memory에 연결되는 방식, kernel과 userspace의 주소 공간, direct mapping과 vmalloc()을 구분하겠습니다.

Process memory에서는 mm_struct, VMA, Copy-on-Write를 통해 process가 가진 주소 공간을 읽습니다. 그다음 mmap()과 brk()가 mapping을 만들고 바꾸는 과정, VMA의 split과 merge를 살펴봅니다. Page fault에서는 그런 mapping에 실제 접근했을 때 kernel이 어떤 일을 수행하는지 이어서 다룹니다.

Physical page에서 이를 사용하는 VMA를 찾아가는 관계는 reverse mapping에서 살펴봅니다. Kernel이 user memory에 접근하거나 그 범위를 조작하는 방법도 별도로 다룹니다. 주소를 알고 있다는 사실만으로 접근 권한과 page의 수명이 보장되지는 않기 때문입니다.

File-backed memory는 page cache와 VFS의 접점에서 읽기, readahead와 read-behind 등 관련 동작을 살펴봅니다. 쓰기 쪽에서는 dirty data의 추적, writeback, synchronization과 dirty throttling으로 이어집니다. 이후 direct·indirect reclaim, demand paging과 higher-order allocation의 부족을 연결하고, swap-out과 swap-in을 살펴봅니다. Allocation을 더는 만족시키기 어려울 때 OOM 처리에서 어떤 선택이 이루어지는지도 확인하겠습니다.

마지막에는 이 내부 동작을 실제 관찰과 연결합니다. procfs와 sysfs, memory tunable, process memory 통계와 OOM report를 읽되, 수치가 무엇을 세는지와 무엇까지 말해 주지는 않는지도 함께 살펴보겠습니다. API, data structure, 상태 변화와 관찰 결과가 하나의 설명으로 이어지는 것이 목표입니다.

참고 자료

  • Lorenzo Stoakes, The Linux Memory Manager, Early Access edition, 2025, Chapter 1, “Introduction”.
  • Linux kernel documentation: 각 주제의 문서와 해당 글에서 명시한 version의 source를 함께 참고합니다.