
x86 메모리 관리의 마법, Self-referencing Page Tables 기법 파헤치기
x86 아키텍처의 Self-referencing Page Tables 기법을 통해 운영체제가 어떻게 복잡한 페이지 테이블을 효율적으로 관리하고 수정하는지 알아봅니다.
김영태
테크리드

안녕하세요, 8년차 개발자 김테크입니다.
오늘은 평소보다 조금 더 깊은 우물인 운영체제(OS) 커널과 메모리 관리 이야기를 해보려고 합니다. 백엔드 개발이나 인프라 구성을 하다 보면 메모리 누수나 가상 메모리 부족 같은 이슈를 겪게 되는데, 이 기저에 있는 작동 원리를 이해하면 트러블슈팅에 큰 도움이 됩니다. 최근 흥미롭게 살펴본 x86 아키텍처의 Self-referencing Page Tables(자기 참조 페이지 테이블) 기법에 대해 쉽게 풀어보겠습니다.
64비트 아키텍처가 표준이 되면서 가상 주소 공간(VAS)은 상상 이상으로 거대해졌습니다. 이를 관리하기 위해 메모리 관리 유닛(MMU)은 페이지 테이블이라는 지도를 보고 가상 주소를 물리 주소로 변환합니다. x86 시스템에서는 보통 4단계(또는 그 이상)의 트리 구조를 타고 내려가며 주소를 찾습니다. 이를 페이지 테이블 탐색(Page Table Walk)이라고 부릅니다.

여기서 OS 개발자들을 골치 아프게 하는 딜레마가 발생합니다. OS 커널도 결국 프로그램이므로 가상 주소 공간 위에서 동작합니다. 그런데 OS가 메모리를 할당하거나 해제하려면 이 페이지 테이블 자체를 수정해야 합니다. 문제는 페이지 테이블도 물리 메모리 어딘가에 존재한다는 점입니다. OS가 페이지 테이블을 수정하려면, 페이지 테이블이 있는 물리 주소에 접근할 수 있어야 하는데, OS는 가상 주소만 볼 수 있습니다.
즉, OS는 페이지 테이블을 수정하기 위해 페이지 테이블 자체를 가상 주소 공간 어딘가에 또 매핑해두어야 합니다. 이를 위해 별도의 관리가 필요하고, 64비트처럼 단계가 깊어지면 이 매핑을 관리하는 코드가 매우 복잡해지고 메모리도 낭비됩니다.
이 문제를 해결하는 아주 우아한 트릭이 바로 Self-referencing Page Tables입니다.
핵심 아이디어는 간단합니다. 최상위 루트 테이블(PML4 혹은 PGD)의 엔트리 중 하나가 하위 테이블이 아닌 자기 자신(루트 테이블)을 가리키게 만드는 것입니다.

이렇게 하면 어떤 마법이 일어날까요? MMU가 주소를 변환할 때 이 자기 참조 엔트리를 만나면, 마치 하위 레벨로 내려간 것처럼 동작하지만 실제로는 제자리에 머물게 됩니다. 결과적으로 전체 탐색 과정이 한 단계씩 밀리는 효과가 발생합니다.
예를 들어, 일반적인 탐색이 4단계를 거쳐 물리 데이터 프레임에 도달한다면, 자기 참조 경로를 탄 탐색은 페이지 테이블 그 자체가 있는 물리 프레임에서 멈추게 됩니다. 덕분에 OS는 복잡한 수동 매핑 없이도 미리 계산된 특정 가상 주소 영역을 통해 모든 페이지 테이블에 선형적으로 접근하고 수정할 수 있게 됩니다.
이 기법이 x86에서 가능한 이유는 인텔의 아키텍처 설계 덕분입니다. x86은 모든 페이징 레벨에서 테이블의 크기가 동일하고, 플래그(Flag) 비트의 구성이 매우 일관적입니다. 즉, 루트 테이블을 하위 테이블인 것처럼 속여도 하드웨어는 아무런 문제 없이 이를 해석합니다.
이 방식의 장점은 명확합니다.
첫째, 코드가 획기적으로 단순해집니다. 32비트와 64비트 시스템 각각에 대해 복잡한 페이지 테이블 매핑 로직을 따로 짤 필요 없이, 하나의 논리로 통합할 수 있습니다. 유지보수성이 좋아지는 것은 당연합니다.
둘째, 메모리를 절약할 수 있습니다. 페이지 테이블 관리를 위해 별도로 할당해야 했던 물리 프레임 낭비를 줄일 수 있습니다.
실제로 교육용 OS인 eduOS와 같은 프로젝트에서도 이 기법을 도입하여 32비트와 64비트 모드를 아우르는 간결한 메모리 관리 시스템을 구현했다고 합니다. 복잡한 시스템 엔지니어링 문제를 '자기 참조'라는 간단한 발상의 전환으로 해결한 훌륭한 사례입니다.
실무에서 우리가 직접 커널 코드를 짤 일은 드물겠지만, 이러한 원리를 이해하는 것은 시스템의 한계를 이해하는 데 큰 자산이 됩니다. 가상 메모리가 어떻게 관리되는지, 그 비용이 어디서 발생하는지 알면 대용량 트래픽을 처리하는 서버의 메모리 튜닝을 할 때도 훨씬 넓은 시야를 가질 수 있습니다.
오늘도 효율적인 시스템을 고민하는 모든 개발자분들을 응원합니다.


