Files

238 lines
13 KiB
Markdown

---
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 와 스토리지 지연을 같은 시간축에 놓는 질문이다.
- **메모리 증상 하나로 계층을 단정하지 않는다**
이 세 갈래가 그 기준이 요구하는 계층 구분을 만든다.
## 본문
<!-- body:start -->
## 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 압박이 한 가지로 들어 있는데, 그것을 이 호스트에서 재라고 적은 항목은 열넷 가운데 없다. 이 글이 갈라 놓은 세 계층에서 확인 계획이 붙은 것은 게스트 쪽과 호스트 쪽 둘이다.
원문이 커널 버전을 적지 않아 이 글도 특정 버전에 고정하지 않았다. 여기 적은 것은 그 문서가 서술한 처리 구조이고, 이 서버에서 어떤 값이 나오는지는 위 두 확인을 돌려야 안다.
<!-- body:end -->