--- id: e80853fa-98b8-4cc3-a2df-427c7147794b kind: CONCEPT slug: page-fault-layers-in-a-vm title: VM 에서 page fault 는 세 계층에서 따로 일어난다 topic: memory-virtualization topicName: 메모리 가상화 project: virtualization status: 초안 basisVersion: x86-64 Intel EPT 기준의 second-stage translation · Linux Guest/Host kernel 의 page fault 처리 · AMD 는 NPT 계열로 대응 studio: "https://hyeonworks.com/studio/documents/e80853fa-98b8-4cc3-a2df-427c7147794b/edit" assets: - key: page-fault-layers-in-a-vm file: ../../../final/assets/diagrams/page-fault-layers-in-a-vm/page-fault-layers-in-a-vm.svg sourceRevision: no-commit · 밖에서 반입한 문서 한 편. 고정할 저장소 리비전이 없다 source: - final/document.md#35-tlb-주소-변환-결과의-cpu-cache - final/document.md#45-guest-page-fault - final/document.md#46-page-fault의-대표적인-원인 - final/document.md#47-ept-violation - final/document.md#48-guest-page-fault와-ept-violation-비교 - final/document.md#49-host-page-fault도-별도로-존재한다 - final/document.md#82-핵심-claim-registry - final/document.md#86-문제를-진단할-때의-분류 --- # VM 에서 page fault 는 세 계층에서 따로 일어난다 가상 머신 안에서 메모리 접근이 완료되지 못하면 그 사건을 받는 곳이 셋이다. GVA(Guest Virtual Address)를 GPA(Guest Physical Address)로 바꾸는 첫 단계에서 막히면 Guest Page Fault 가 나서 게스트 커널이 처리한다. 그 GPA 를 HPA(Host Physical Address)로 바꾸는 두 번째 단계에서 막히면 EPT Violation 이 나고, VM Exit 뒤 KVM 이 받는다. QEMU 도 호스트의 일반 프로세스여서 자기가 마련한 메모리에 대해 Host Page Fault 를 따로 겪는다. 이름이 모두 fault 라 한 덩어리로 읽히지만 일어나는 단계도 처리하는 쪽도 다르고, 셋 중 어느 것이 늘었는지에 따라 볼 지표가 갈린다. ## 관계 - **Guest 메모리 주소가 물리 RAM 에 닿기까지 — GVA · GPA · HPA** 정상일 때 접근이 어떤 경로를 지나는지를 그 글이 세운다. 이 글은 그 경로가 완료되지 않을 때를 맡는다. - **Host 메모리 압박이 Guest 와 스토리지까지 내려가는 경로** 호스트 쪽 fault 가 늘어나는 배경인 reclaim 과 swap 을 그 글이 다룬다. - **게스트 page fault 증가는 workload 변화를 따라가는가** 게스트 쪽 fault 지표를 이 호스트에서 재는 질문이다. 이 글은 원인 분류까지만 적었다. - **호스트 major fault 와 스토리지 지연은 같은 시간축에서 함께 움직이는가** 호스트 쪽 major fault 와 스토리지 지연을 같은 시간축에 놓는 질문이다. - **메모리 증상 하나로 계층을 단정하지 않는다** 이 세 갈래가 그 기준이 요구하는 계층 구분을 만든다. ## 본문 ## TLB Miss 와 Page Fault 는 다른 사건이다 주소 변환 결과는 CPU 의 TLB(Translation Lookaside Buffer, 주소 변환 캐시)에 담긴다. 거기에 찾는 변환이 없으면 TLB Miss 가 나지만, 그것으로 접근이 실패하지는 않는다. ```text label="TLB Miss 가 났을 때" TLB에 translation cache가 없음 ↓ Page Table을 조회 ↓ 정상 mapping 존재 ↓ 계속 실행 ``` Page Fault 는 조건이 다르다. 페이지 테이블 상태로는 지금 접근을 정상으로 끝낼 수 없어서, 커널이 끼어들어 페이지를 마련하거나 매핑을 고쳐야 명령을 다시 시도할 수 있다. TLB Miss 는 테이블을 한 번 더 읽으면 풀리는데, 이쪽은 커널 처리를 부른다. ## 첫 번째 단계 — Guest Page Fault 이 fault 는 GVA 를 GPA 로 바꾸는 첫 번째 변환 단계에서 일어난다. ```text label="첫 번째 단계에서 접근이 완료되지 못할 때" GVA ↓ Guest Page Table ↓ 현재 접근을 완료할 수 없음 ↓ Guest #PF ↓ Guest Kernel Page Fault Handler ``` 게스트 프로세스가 아직 물리 페이지가 붙지 않은 가상 메모리 영역에 처음 접근하면 이 사건이 난다. 게스트 안의 Keycloak 을 예로 들면, 새 영역의 첫 접근에서 fault 가 나고 게스트 커널이 페이지를 확보해 매핑을 갱신한 뒤 명령을 다시 시도한다. ```text label="Guest 프로세스가 새 영역에 처음 접근할 때" Keycloak ↓ 새 Virtual Memory 영역에 첫 접근 ↓ Guest Page Table ↓ 현재 usable physical mapping 없음 ↓ Page Fault ↓ Guest Kernel ↓ Page 확보 / mapping 갱신 ↓ Instruction 재시도 ``` 이 사건 자체가 프로그램 오류를 뜻하지는 않는다. 정상적인 메모리 관리에서도 늘 일어난다. ## Guest Page Fault 를 부르는 다섯 가지 원문은 대표적인 원인을 다섯으로 나눈다. 앞의 넷은 커널이 처리해 실행이 이어지고, 마지막 하나만 프로세스로 신호가 간다. | 원인 | 게스트 커널이 무엇을 하나 | |---|---| | Demand Paging | 아직 물리 페이지가 필요하지 않았던 영역에 첫 접근이 오면 페이지를 준비한다 | | Swap-in | 필요한 페이지가 게스트 RAM 에 없으면 게스트 swap 에서 읽어 RAM 을 복원하고 페이지 테이블을 갱신한다 | | Permission Fault | 읽기 전용 페이지에 쓰기가 들어오는 접근을 걸러 낸다 | | Copy-on-Write | 쓰기에서 난 fault 를 의도적으로 이용해 페이지를 복제하고 쓰기 가능한 매핑을 새로 만든다 | | Invalid Access | 정상 매핑으로 해결할 수 없는 접근이면 `SIGSEGV` 등으로 이어질 수 있다 | 권한이 fault 를 부르는 까닭은 페이지 테이블 엔트리가 매핑만 담고 있지 않기 때문이다. 같은 엔트리에 읽기·쓰기·실행 권한이 함께 들어 있다. ```text label="Page Table Entry 에 함께 들어 있는 값" Physical Frame: 1234 Present: 1 Writable: 0 Executable: 0 ``` 이 엔트리로 매핑된 페이지에 쓰기가 들어가면 fault 가 날 수 있고, Copy-on-Write 는 그 성질을 거꾸로 이용해 쓰기가 들어온 순간에만 페이지를 복제한다. 다섯 가운데 Invalid Access 만 결과가 다르다. 게스트 커널이 정상적인 매핑으로 해결할 수 없는 접근이면 `SIGSEGV` 로 이어질 수 있고, 그래서 Page Fault 와 Segmentation Fault 는 같은 것이 아니다. ## 두 번째 단계 — EPT Violation 이번에는 게스트 페이지 테이블 변환이 성공해 GPA 까지 얻었다고 하자. 그런데 그 GPA 에 대한 두 번째 단계 접근을 지금 EPT(Extended Page Tables) 조건으로 끝낼 수 없으면 EPT Violation 이 된다. ```text label="Guest 변환은 성공했는데 second-stage 에서 막힐 때" GVA ↓ Guest Page Table ↓ GPA ← Guest translation 성공 ↓ EPT ↓ EPT Violation ↓ VM Exit ↓ KVM ``` 게스트 안에서는 첫 단계가 정상으로 끝났으므로 게스트 커널의 fault 처리기가 이 사건을 보지 않는다. 제어권이 VM Exit 으로 VMX(Virtual Machine Extensions) Root 쪽으로 넘어가 KVM 이 처리한다. ## 두 사건이 갈리는 다섯 항목 | 무엇을 견주나 | Guest Page Fault | EPT Violation | |---|---|---| | 문제 위치 | GVA → GPA | GPA → HPA | | 관련 테이블 | 게스트 페이지 테이블 | EPT | | 기본 관점 | 게스트 가상 메모리 | 가상화 메모리 매핑 | | 주요 처리 계층 | 게스트 커널 | VM Exit 후 KVM 쪽 | | 앱 오류를 뜻하는가 | 반드시 아님 | 반드시 아님 | Guest Page Fault 는 게스트가 자기 가상 메모리를 처리하는 사건이고, EPT Violation 은 두 번째 단계 가상화 변환에서 하이퍼바이저 처리가 필요한 사건이다. 마지막 줄이 둘 다 「반드시 아님」인 까닭은 앞에서 본 대로다. demand paging 이나 copy-on-write 처럼 정상 동작이 두 계층 모두에서 fault 를 만든다. ## 세 번째 — Host Page Fault QEMU 도 호스트의 일반 사용자 공간 프로세스이므로 QEMU 가 마련한 메모리에는 호스트의 가상 메모리 관리가 그대로 적용된다. 게스트 RAM 을 담고 있는 그 영역도 호스트 페이지 테이블을 지나 호스트 물리 주소로 이어지고, 그 경로에서 demand allocation 이나 reclaim, swap 때문에 fault 가 날 수 있다. ```text label="QEMU backing 쪽에서 나는 fault" QEMU / Guest RAM Backing ↓ Host Virtual Memory ↓ Host Page Fault ↓ Host Kernel ↓ 필요한 Host page 처리 ``` 게스트 안에서는 이 사건이 fault 로 보이지 않는다. 게스트는 자기 메모리 접근이 오래 걸렸다고만 관측하고, 그 지연의 원인은 호스트 쪽 지표에 남는다. 그래서 가상 머신 메모리를 분석할 때는 Guest Page Fault 와 Host Page Fault, 그리고 EPT 관련 가상화 사건을 적어도 셋으로 갈라 놓는다. ![Guest Page Table 에서 갈래가 둘로 나뉘어 하나는 Guest Page Fault 로 Guest Kernel 에 가고 다른 하나는 GPA 를 들고 EPT 로 내려가며, EPT 에서는 EPT Violation 이 VM Exit 을 거쳐 KVM 으로 가고, 따로 놓인 QEMU 의 Guest RAM Backing 에서는 Host Page Fault 가 Host Kernel 로 가는 갈래도. 받는 곳의 이름이 그대로 처리 계층의 이름이다.](../../../final/assets/diagrams/page-fault-layers-in-a-vm/page-fault-layers-in-a-vm.svg) 그림에서 QEMU 쪽 갈래는 위의 두 갈래와 선으로 이어져 있지 않다. 근거 문서가 EPT 로 얻은 HPA 와 QEMU 가 마련한 메모리의 호스트 물리 주소를 같은 것이라고 적지 않아서 그 선을 긋지 않았다. ## 메모리 지연이나 OOM(Out Of Memory) 이 보일 때 셋을 어디에 놓나 증상 하나로 "메모리 부족"을 결론내리지 않고 어느 계층의 사건인지부터 가른다. 세 사건은 이 분류에서 서로 다른 가지에 들어간다. ```text label="메모리 문제를 계층으로 가르는 분류 (fault 가 걸리는 가지)" 문제 │ ├─ Guest Virtual Memory? │ ├─ Page Fault │ ├─ Guest reclaim │ ├─ Guest swap │ └─ Guest OOM │ ├─ Virtualization Translation? │ ├─ EPT-related event │ ├─ TLB pressure │ └─ Huge-page/mapping 특성 │ └─ Host Memory? ├─ Host reclaim ├─ Host swap ├─ Host major fault └─ Host OOM ``` 원문의 분류에는 Dynamic Memory 와 NUMA(Non-Uniform Memory Access) 가지가 더 있고, 위에 옮긴 셋이 fault 가 걸리는 가지다. 게스트 쪽 fault 가 늘었으면 게스트의 reclaim 과 swap 을 같이 보고, 호스트 쪽 major fault 가 늘었으면 호스트의 reclaim 과 swap 을 같이 본다. 어느 쪽 지표를 먼저 여느냐가 이 구분에서 정해진다. 다만 이 호스트에서는 어느 쪽도 아직 열지 않았다. 게스트 쪽 fault 지표도 호스트 쪽 major fault 도 읽은 적이 없어서, 이 분류는 아직 어느 지표부터 열지 정하는 데만 쓰인다. ## 이 글이 확정하지 않는 것 이 호스트에서 잰 값은 하나도 없다. 게스트 쪽 fault 지표도 호스트 쪽 major fault 도 읽지 않았고, 앞의 다섯 원인 가운데 무엇이 이 환경에서 실제로 일어나는지도 세지 않았다. 원문 제2부는 개념 서술이고 실제 서버에 딸린 값은 OQ(Open Question) 번호가 붙은 확인 목록 열넷으로 미뤄져 있다. 게스트 쪽은 OQ-13 이 받는다. page-fault 관련 지표를 관측한 뒤 그 증가가 무엇에서 왔는지를 네 갈래로 갈라 보는 확인이고, 증가 자체를 오류로 읽지 않는 것이 조건이다. ```text label="Guest page fault 가 늘었을 때 갈라 보는 것 (OQ-13)" 정상 demand paging? COW? Guest swap-in? application working-set 증가? ``` 호스트 쪽은 OQ-14 가 받는다. 호스트 메모리 압박 실험을 하면서 아래 넷을 같은 시간축에 놓고 견준다. ```text label="같은 시간축에 놓고 견줄 값 (OQ-14)" Host Fault + Swap activity + Storage latency + Guest application latency ``` 가운데 계층은 그 목록이 받지 않는다. 위 분류에는 EPT 관련 사건과 TLB 압박이 한 가지로 들어 있는데, 그것을 이 호스트에서 재라고 적은 항목은 열넷 가운데 없다. 이 글이 갈라 놓은 세 계층에서 확인 계획이 붙은 것은 게스트 쪽과 호스트 쪽 둘이다. 원문이 커널 버전을 적지 않아 이 글도 특정 버전에 고정하지 않았다. 여기 적은 것은 그 문서가 서술한 처리 구조이고, 이 서버에서 어떤 값이 나오는지는 위 두 확인을 돌려야 안다.