feat: 가상화 문서들 추가
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
그림의 정본 — <이름>/{context.json, spec.json, prompt.md}.
|
||||
technical-visualizer 스킬이 만든다. 손으로 SVG 를 그리지 않는다.
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,338 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "guest-block-io-to-virtqueue",
|
||||
"title": "게스트의 write() 가 virtqueue 에 실리기까지",
|
||||
"question": "게스트 애플리케이션의 write() 는 virtqueue 에 실리기 전에 게스트의 어느 계층을 어떤 순서로 지나는가",
|
||||
"type": "flow",
|
||||
"direction": "TB",
|
||||
"audience": [
|
||||
"게스트에서 본 저장소 지연을 계층별로 가르려는 사람",
|
||||
"virtio-blk 경로를 처음 읽는 사람"
|
||||
],
|
||||
"summary": "게스트 애플리케이션의 write() 는 VFS 와 ext4/XFS, Guest Page Cache, Guest Block Layer, /dev/vda, virtio-blk Frontend 를 차례로 지나 virtqueue 에 실린다.",
|
||||
"alt": "게스트 사용자 공간의 PostgreSQL/Keycloak 에서 시작한 write() 가 게스트 커널의 VFS, ext4/XFS, Guest Page Cache, Guest Block Layer, /dev/vda, virtio-blk Frontend 를 거쳐 virtqueue 에 도달하는 위에서 아래로 읽는 흐름도.",
|
||||
"long_description": "위에서 아래로 읽는다. 맨 위 Guest Userspace 구역에 PostgreSQL / Keycloak 이 있고 write() 가 게스트 커널로 내려간다. Guest Kernel 구역 안에서 VFS 가 먼저 받고, ext4 / XFS 가 파일을 block 공간에 배치하고, Guest Page Cache 가 그 내용을 dirty page 로 받아 두었다가 writeback 으로 Guest Block Layer 에 내려보낸다. Guest Block Layer 는 그것을 WRITE 나 FLUSH 요청으로 바꿔 /dev/vda 에 걸고, virtio-blk Frontend 가 그 요청을 virtqueue 에 게시한다. virtqueue 아래의 VM Boundary 부터는 QEMU 와 호스트 계층이라 이 그림에서 잘랐다. 완료가 같은 virtqueue 로 되돌아오는 역방향은 SSOT 의 다른 절이 대는 사실이라 이 그림에 넣지 않았고 본문 문단이 맡는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "128. 전체 구조",
|
||||
"line": 5804
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 주장하는 것은 요청 하나가 한 방향으로 지나가는 계층의 순서다. sequence 는 참여자마다 생명선을 세우고 메시지를 주고받는 그림인데 이 구간에는 되돌아오는 메시지가 없어 생명선이 전부 빈 세로줄이 된다. two-zone-pipeline 은 구역 안쪽 엣지 라벨을 캔버스 위쪽에 고정해 writeback 과 WRITE / FLUSH 가 제자리를 잃는다. 구역은 groups 로 두고 흐름은 component-flow 로 세운다.",
|
||||
"focus_node": "virtqueue"
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"id": "guest-userspace",
|
||||
"label": "Guest Userspace",
|
||||
"kind": "boundary",
|
||||
"description": "게스트 사용자 공간. 저장장치를 직접 다루지 않는다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5807,
|
||||
"end_line": 5809
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "guest-kernel",
|
||||
"label": "Guest Kernel",
|
||||
"kind": "boundary",
|
||||
"description": "게스트 커널 공간. VM Boundary 위쪽 계층이 전부 여기 있다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5815,
|
||||
"end_line": 5833
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "app",
|
||||
"label": "PostgreSQL / Keycloak",
|
||||
"kind": "service",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"group": "guest-userspace",
|
||||
"description": "파일 연산을 요청하는 게스트 프로세스.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5807,
|
||||
"end_line": 5809
|
||||
},
|
||||
{
|
||||
"start_line": 5885,
|
||||
"end_line": 5885
|
||||
},
|
||||
{
|
||||
"start_line": 5811,
|
||||
"end_line": 5813
|
||||
}
|
||||
],
|
||||
"assumption": false,
|
||||
"details": [
|
||||
"read / write",
|
||||
"fsync / sync"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vfs",
|
||||
"label": "VFS",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "guest-kernel",
|
||||
"description": "열린 파일이 어느 filesystem 구현에 속하는지 보고 연산을 넘기는 공통 계층.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5815,
|
||||
"end_line": 5817
|
||||
},
|
||||
{
|
||||
"start_line": 5905,
|
||||
"end_line": 5905
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "fs",
|
||||
"label": "ext4 / XFS",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "guest-kernel",
|
||||
"description": "파일과 디렉터리를 block 공간에 배치하는 게스트 filesystem.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5819,
|
||||
"end_line": 5819
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "page-cache",
|
||||
"label": "Guest Page Cache",
|
||||
"kind": "store",
|
||||
"role": "store",
|
||||
"shape": "box",
|
||||
"group": "guest-kernel",
|
||||
"description": "게스트 RAM 에 있는 cache. write() 가 성공해도 데이터는 아직 여기까지만 확정된다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5821,
|
||||
"end_line": 5821
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "block-layer",
|
||||
"label": "Guest Block Layer",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "guest-kernel",
|
||||
"description": "파일 세계의 요청을 block device 세계의 요청으로 바꾸는 계층.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5825,
|
||||
"end_line": 5825
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vda",
|
||||
"label": "/dev/vda",
|
||||
"kind": "device",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "guest-kernel",
|
||||
"description": "게스트 Linux 가 하나의 block device 로 인식하는 이름.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5829,
|
||||
"end_line": 5829
|
||||
},
|
||||
{
|
||||
"start_line": 5879,
|
||||
"end_line": 5879
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtio-blk",
|
||||
"label": "virtio-blk Frontend",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "guest-kernel",
|
||||
"description": "그 가상 block device 를 제어하는 게스트 커널 driver.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5831,
|
||||
"end_line": 5831
|
||||
},
|
||||
{
|
||||
"start_line": 5789,
|
||||
"end_line": 5789
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtqueue",
|
||||
"label": "virtqueue",
|
||||
"kind": "queue",
|
||||
"role": "queue",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"group": "guest-kernel",
|
||||
"description": "요청이 실리는 자리. 바로 아래가 VM Boundary 다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5833,
|
||||
"end_line": 5835
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "app-to-vfs",
|
||||
"from": "app",
|
||||
"to": "vfs",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 1,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5811,
|
||||
"end_line": 5813
|
||||
},
|
||||
{
|
||||
"start_line": 5890,
|
||||
"end_line": 5890
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vfs-to-fs",
|
||||
"from": "vfs",
|
||||
"to": "fs",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 2,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5817,
|
||||
"end_line": 5819
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "fs-to-page-cache",
|
||||
"from": "fs",
|
||||
"to": "page-cache",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 3,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5819,
|
||||
"end_line": 5821
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "page-cache-to-block-layer",
|
||||
"from": "page-cache",
|
||||
"to": "block-layer",
|
||||
"label": "writeback",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 4,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5823,
|
||||
"end_line": 5825
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "block-layer-to-vda",
|
||||
"from": "block-layer",
|
||||
"to": "vda",
|
||||
"label": "WRITE / FLUSH",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 5,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5827,
|
||||
"end_line": 5829
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vda-to-virtio-blk",
|
||||
"from": "vda",
|
||||
"to": "virtio-blk",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 6,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5829,
|
||||
"end_line": 5831
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtio-blk-to-virtqueue",
|
||||
"from": "virtio-blk",
|
||||
"to": "virtqueue",
|
||||
"label": "Virtio block request",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 7,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5831,
|
||||
"end_line": 5833
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "component-flow 를 고른 이유는 이 절이 답하는 물음이 「write() 가 virtqueue 에 닿기까지 무엇을 어떤 순서로 지나는가」이기 때문이다. Guest Userspace 와 Guest Kernel 은 §128 이 대괄호로 직접 그어 둔 구역이라 groups 로 두었다. 완료가 같은 virtqueue 로 되돌아오는 역방향은 §166 과 §172 가 대는 사실인데 두 절 모두 이 앵커의 준비된 문맥(§127~§129) 밖이라 넣지 않았다 — 그 문맥 안에서 인용할 줄이 없다. VM Boundary 아래의 QEMU·호스트 계층은 같은 §128 안에 있지만 이 기록이 virtqueue 까지만 따라간다고 밝혀 두었고, 그 아래는 다른 기록의 그림이 맡는다. 첫 엣지에 write() 라벨을 달았더니 라벨 상자가 Guest Kernel 구역 이름을 덮어(PNG 로 확인) 라벨을 지우고 그 두 줄을 출발 노드의 details 로 내렸다. virtio-blk Frontend → virtqueue 엣지의 라벨 「Virtio block request」는 이 앵커의 문맥이 화살표만 그려 두어 이름 자체는 §138 이 대는 것이고, 기록 본문도 같은 이름을 쓴다."
|
||||
}
|
||||
}
|
||||
+2606
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,285 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "guest-memory-address-translation-path",
|
||||
"title": "Guest 의 메모리 접근 한 번은 이름이 두 번 바뀌며 네 계층을 지난다",
|
||||
"question": "Guest 프로세스가 읽은 주소는 어느 계층을 차례로 지나 어떤 물리 메모리에 닿는가",
|
||||
"type": "data-flow",
|
||||
"direction": "TB",
|
||||
"audience": [
|
||||
"가상 머신에 준 RAM 이 Host RAM 에서 무엇을 뜻하는지 가르려는 사람",
|
||||
"KVM/QEMU 메모리 경로를 처음 읽는 사람"
|
||||
],
|
||||
"summary": "Guest 구역 안에서 GVA 가 Guest TLB 와 Guest Page Table 을 지나 GPA 가 되고, VM Boundary 를 넘은 뒤 EPT 가 GPA 를 HPA 로 바꾸며, 그 HPA 가 Host RAM 의 physical page 와 그 page 가 속한 NUMA node 로 이어진다.",
|
||||
"alt": "Guest 구역 안에서 Keycloak · PostgreSQL 의 GVA 가 Guest TLB 와 Guest Page Table 을 지나 GPA 가 되고, VM Boundary 를 넘어 EPT 에서 HPA 가 된 뒤 Host RAM 구역의 Host Physical Memory 를 지나 NUMA Node 0 과 NUMA Node 1 로 갈라지는 주소 변환 경로도.",
|
||||
"long_description": "위에서 아래로 읽는다. 맨 위 Guest 구역 안에 Keycloak · PostgreSQL 이 있고 그 접근은 GVA 로 Guest TLB 에 들어간다. TLB 에 변환이 없으면 TLB Miss 로 Guest Page Table 을 조회하고 거기서 GVA 가 GPA 가 된다. 그 GPA 는 VM Boundary 를 넘어 Guest 구역 밖의 EPT 로 가고 EPT 가 GPA 를 HPA 로 바꾼다. HPA 는 Host RAM 구역의 Host Physical Memory 에 닿고, 그 physical page 는 맨 아래에서 NUMA Node 0 또는 NUMA Node 1 가운데 한쪽에 놓인다. 상자 안 작은 글씨는 그 단계가 어느 계층인지와 무엇을 무엇으로 바꾸는지를 적은 것이다. 어느 단계에서 접근이 완료되지 못할 때 무엇이 일어나는지와 QEMU 가 이 경로를 어떻게 준비하는지는 본문의 문단과 표가 맡는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "87. 최종 기준 그림",
|
||||
"line": 4029
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 세우는 것은 한 번의 메모리 접근이 한 방향으로 지나가는 경로와 그 경로가 네 계층으로 나뉜다는 것이다. sequence 는 참여자마다 생명선을 세우고 메시지에 시각 순서를 매기는데 여기서는 참여자끼리 주고받는 메시지가 아니라 주소 하나가 계층을 지나며 이름이 바뀌는 변환 사슬이라 순서를 매길 메시지가 없다. two-zone-pipeline 은 구역이 둘일 때의 문법인데 이 경로는 Guest Userspace · Guest Kernel · KVM/CPU · Host RAM 넷으로 갈리고 Guest Kernel 안쪽에도 TLB Miss 엣지가 있어 구역 안 엣지 라벨이 캔버스 위쪽에 고정된다.",
|
||||
"focus_node": "ept"
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"id": "guest",
|
||||
"label": "Guest",
|
||||
"kind": "boundary",
|
||||
"description": "VM Boundary 안쪽. Guest Userspace 와 Guest Kernel 이 여기 있고 GVA 가 GPA 가 되는 첫 번째 변환이 여기서 끝난다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4034,
|
||||
"end_line": 4053
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-ram",
|
||||
"label": "Host RAM",
|
||||
"kind": "boundary",
|
||||
"description": "변환이 끝난 주소가 닿는 실제 서버 메모리.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4069,
|
||||
"end_line": 4079
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "guest-process",
|
||||
"label": "Keycloak · PostgreSQL",
|
||||
"kind": "actor",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"group": "guest",
|
||||
"description": "Guest 안에서 메모리를 읽는 프로세스. 여기서 GVA 가 출발한다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4034,
|
||||
"end_line": 4038
|
||||
}
|
||||
],
|
||||
"assumption": false,
|
||||
"details": [
|
||||
"Guest Userspace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "guest-tlb",
|
||||
"label": "Guest TLB",
|
||||
"kind": "component",
|
||||
"role": "store",
|
||||
"shape": "box",
|
||||
"group": "guest",
|
||||
"description": "최근 변환 결과를 담아 두는 CPU 안의 캐시.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4043,
|
||||
"end_line": 4045
|
||||
}
|
||||
],
|
||||
"assumption": false,
|
||||
"details": [
|
||||
"Guest Kernel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "guest-page-table",
|
||||
"label": "Guest Page Table",
|
||||
"kind": "component",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"group": "guest",
|
||||
"description": "GVA 를 GPA 로 바꾸는 첫 번째 변환 표.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4048,
|
||||
"end_line": 4053
|
||||
}
|
||||
],
|
||||
"assumption": false,
|
||||
"details": [
|
||||
"Guest Kernel",
|
||||
"GVA → GPA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ept",
|
||||
"label": "EPT",
|
||||
"kind": "component",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"description": "VM Boundary 바깥에서 GPA 를 HPA 로 바꾸는 두 번째 변환 표. KVM 과 CPU 가 맡는다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4055,
|
||||
"end_line": 4067
|
||||
}
|
||||
],
|
||||
"assumption": false,
|
||||
"details": [
|
||||
"KVM / CPU",
|
||||
"GPA → HPA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "host-physical-memory",
|
||||
"label": "Host Physical Memory",
|
||||
"kind": "store",
|
||||
"role": "store",
|
||||
"shape": "box",
|
||||
"group": "host-ram",
|
||||
"description": "HPA 가 가리키는 실제 서버 메모리.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4069,
|
||||
"end_line": 4072
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "numa-node-0",
|
||||
"label": "NUMA Node 0",
|
||||
"kind": "store",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"group": "host-ram",
|
||||
"description": "physical page 가 놓일 수 있는 한쪽 NUMA node.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4073,
|
||||
"end_line": 4075
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "numa-node-1",
|
||||
"label": "NUMA Node 1",
|
||||
"kind": "store",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"group": "host-ram",
|
||||
"description": "physical page 가 놓일 수 있는 다른 쪽 NUMA node.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4073,
|
||||
"end_line": 4075
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "process-to-tlb",
|
||||
"from": "guest-process",
|
||||
"to": "guest-tlb",
|
||||
"label": "GVA",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4036,
|
||||
"end_line": 4043
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "tlb-to-page-table",
|
||||
"from": "guest-tlb",
|
||||
"to": "guest-page-table",
|
||||
"label": "TLB Miss",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4043,
|
||||
"end_line": 4048
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "page-table-to-ept",
|
||||
"from": "guest-page-table",
|
||||
"to": "ept",
|
||||
"label": "VM Boundary",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4053,
|
||||
"end_line": 4062
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "ept-to-host-memory",
|
||||
"from": "ept",
|
||||
"to": "host-physical-memory",
|
||||
"label": "HPA",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4062,
|
||||
"end_line": 4071
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-memory-to-node-0",
|
||||
"from": "host-physical-memory",
|
||||
"to": "numa-node-0",
|
||||
"label": "Physical Page",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4071,
|
||||
"end_line": 4075
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-memory-to-node-1",
|
||||
"from": "host-physical-memory",
|
||||
"to": "numa-node-1",
|
||||
"label": "Physical Page",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 4071,
|
||||
"end_line": 4075
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "data-flow 로 고른 이유는 이 절이 답하는 물음이 「주소 하나가 어디를 지나 어디에 닿는가」이기 때문이다. LR 로 세우면 캔버스가 1889px 에 종횡비 5.37 이라 문서 폭에서 글자가 읽히지 않아 TB 로 세웠다. Guest Userspace · Guest Kernel · KVM / CPU · Host RAM 넷을 모두 group 으로 두면 group 이름이 그 경계를 넘는 엣지 라벨에 먹혀 「Guest Kerne」·「KVM / CP」로 잘렸다. 그래서 경계는 VM Boundary 를 사이에 둔 Guest 와 Host RAM 둘만 상자로 두고, 나머지 두 계층 이름은 그 자리 노드의 details 로 내렸다. SSOT 가 같은 절에 그린 관리 경로(virsh · libvirt · QEMU · KVM)와 자원 압박 경로는 다른 물음이라 이 그림에 넣지 않았다. §87 이 EPT 옆에 적어 둔 「EPT Violation 가능」과 Guest Page Table 옆의 「Guest #PF 가능」도 뺐다 — 이 기록의 본문은 정상 경로를 세우고, 세 계층의 fault 는 짝이 되는 기록이 맡는다. 마지막 Physical RAM 상자는 Host Physical Memory 와 뜻이 겹쳐 빼고 NUMA node 둘을 끝으로 두었다."
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "numa-vcpu-and-memory-placement",
|
||||
"title": "vCPU 를 Node 0 에 묶어도 backing 이 Node 1 에 있으면 접근은 remote 로 남는다",
|
||||
"question": "vCPU pinning 만 한 배치와 memory binding 까지 맞춘 배치에서 같은 memory 접근은 각각 어느 node 의 RAM 에 닿는가",
|
||||
"type": "sequence",
|
||||
"direction": "LR",
|
||||
"audience": [
|
||||
"가상 머신의 지연을 NUMA 배치로 의심하는 사람",
|
||||
"vCPU pinning 을 걸어 두고 효과가 없다고 느끼는 사람"
|
||||
],
|
||||
"summary": "vCPU thread 를 Node 0 CPU 에 pinning 해도 VM1 의 memory backing 이 Node 1 에 놓여 있으면 접근은 remote 로 남고, memory placement 와 binding 까지 Node 0 으로 맞춘 뒤에야 같은 접근이 local 이 된다.",
|
||||
"alt": "QEMU vCPU Thread, VM1 Memory Backing, Node 0 CPU, Node 0 RAM, Node 1 RAM 다섯 참여자 사이의 순서도. 앞 세 메시지는 pinning 만 한 배치에서 접근이 Node 1 RAM 으로 가는 것을, 뒤 두 메시지는 memory 를 Node 0 에 묶은 배치에서 같은 접근이 Node 0 RAM 으로 가는 것을 보인다.",
|
||||
"long_description": "참여자는 왼쪽부터 QEMU vCPU Thread, VM1 Memory Backing, Node 0 CPU, Node 0 RAM, Node 1 RAM 다섯이다. 메시지는 위에서 아래로 읽는다. 1번에서 vCPU thread 가 Node 0 CPU 에 pinning 되고, 2번에서 VM1 의 memory backing 이 Node 1 에 놓인다. 그 배치에서 3번의 memory 접근은 Node 1 RAM 으로 가고 이것이 remote access 다. Node 0 과 Node 1 사이에는 interconnect 가 있어 이 접근만 그 구간을 건넌다. 4번과 5번은 그 뒤에 일어나는 다른 사건이 아니라 배치를 고친 구성이다. memory placement 와 binding 까지 Node 0 으로 맞추면 같은 접근이 Node 0 RAM 으로 가고 local access 가 된다. 접근 비용이 실제로 얼마나 차이 나는지, 이 Host 가 몇 개의 node 를 가졌는지는 그림이 말하지 않는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "75. vCPU와 NUMA의 연결",
|
||||
"line": 3313
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "sequence",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-approval-sequence"
|
||||
],
|
||||
"rationale": "이 절과 다음 절이 세운 것은 배치 하나가 아니라 「묶은 것 → 남은 문제 → 마저 묶은 것 → 달라진 결과」라는 차례다. orchestrator-workers 는 scheduler 를 워커들 위에 두는 그림인데 memory 가 어느 node 에 있는지는 scheduler 가 정하지 않는다는 것이 이 절의 논지라 그 문법을 쓰면 논지가 뒤집힌다. query-fanout 은 CPU 하나가 RAM 둘로 갈라지는 모양은 맞지만 pinning 만 한 배치와 memory 까지 맞춘 배치가 같은 그림에서 구별되지 않는다. sequence 는 메시지에 번호를 붙이므로 두 배치가 어느 쪽이 먼저이고 무엇이 달라졌는지 그대로 드러난다.",
|
||||
"focus_node": "node0-cpu"
|
||||
},
|
||||
"groups": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "qemu-vcpu-thread",
|
||||
"label": "QEMU vCPU Thread",
|
||||
"kind": "process",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"Guest vCPU"
|
||||
],
|
||||
"description": "Guest vCPU 가 Host 에서 갖는 실체. Host scheduler 가 이것을 logical CPU 에 올린다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3315,
|
||||
"end_line": 3324
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vm-memory-backing",
|
||||
"label": "VM1 Memory Backing",
|
||||
"kind": "memory",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"VM1 RAM"
|
||||
],
|
||||
"description": "그 가상 머신의 Host physical backing page 가 놓이는 자리.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3327,
|
||||
"end_line": 3327
|
||||
},
|
||||
{
|
||||
"start_line": 3352,
|
||||
"end_line": 3354
|
||||
},
|
||||
{
|
||||
"start_line": 3381,
|
||||
"end_line": 3382
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "node0-cpu",
|
||||
"label": "Node 0 CPU",
|
||||
"kind": "compute",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"Host Logical CPU"
|
||||
],
|
||||
"description": "vCPU thread 가 실행되는 logical CPU. NUMA Node 0 에 속한다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3322,
|
||||
"end_line": 3330
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "node0-ram",
|
||||
"label": "Node 0 RAM",
|
||||
"kind": "memory",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"Local Memory"
|
||||
],
|
||||
"description": "Node 0 CPU 와 같은 node 의 RAM.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3288,
|
||||
"end_line": 3296
|
||||
},
|
||||
{
|
||||
"start_line": 3373,
|
||||
"end_line": 3382
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "node1-ram",
|
||||
"label": "Node 1 RAM",
|
||||
"kind": "memory",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"Remote Memory"
|
||||
],
|
||||
"description": "다른 node 의 RAM. Node 0 CPU 가 읽으면 interconnect 를 건넌다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3299,
|
||||
"end_line": 3306
|
||||
},
|
||||
{
|
||||
"start_line": 3334,
|
||||
"end_line": 3334
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "vcpu-pinning",
|
||||
"from": "qemu-vcpu-thread",
|
||||
"to": "node0-cpu",
|
||||
"label": "vCPU Pinning",
|
||||
"kind": "control",
|
||||
"order": 1,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3322,
|
||||
"end_line": 3324
|
||||
},
|
||||
{
|
||||
"start_line": 3347,
|
||||
"end_line": 3350
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "backing-on-node1",
|
||||
"from": "vm-memory-backing",
|
||||
"to": "node1-ram",
|
||||
"label": "Memory Placement",
|
||||
"kind": "control",
|
||||
"order": 2,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3327,
|
||||
"end_line": 3327
|
||||
},
|
||||
{
|
||||
"start_line": 3352,
|
||||
"end_line": 3354
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "remote-access",
|
||||
"from": "node0-cpu",
|
||||
"to": "node1-ram",
|
||||
"label": "Remote Access",
|
||||
"kind": "read",
|
||||
"order": 3,
|
||||
"style": "dashed",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3329,
|
||||
"end_line": 3339
|
||||
},
|
||||
{
|
||||
"start_line": 3356,
|
||||
"end_line": 3357
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "memory-binding",
|
||||
"from": "vm-memory-backing",
|
||||
"to": "node0-ram",
|
||||
"label": "Memory Binding",
|
||||
"kind": "control",
|
||||
"order": 4,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3361,
|
||||
"end_line": 3366
|
||||
},
|
||||
{
|
||||
"start_line": 3381,
|
||||
"end_line": 3382
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "local-access",
|
||||
"from": "node0-cpu",
|
||||
"to": "node0-ram",
|
||||
"label": "Local Access",
|
||||
"kind": "read",
|
||||
"order": 5,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3288,
|
||||
"end_line": 3296
|
||||
},
|
||||
{
|
||||
"start_line": 3373,
|
||||
"end_line": 3382
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "sequence 로 고른 이유는 §76 이 배치 하나를 서술한 절이 아니라 「pinning 만 하면 이렇게 되고 memory 까지 맞추면 저렇게 된다」는 차례로 쓰인 절이기 때문이다. 4·5 번은 3 번 뒤에 일어나는 사건이 아니라 고친 구성이고 그 사실은 long_description 과 옆 문단이 말한다. remote access 에 emphasis: warning 을 줬더니 렌더러가 화살표 끝에 빨간 X 를 그려 실패로 읽혔다. remote 는 실패가 아니라 비용이 더 드는 접근이라 강조를 빼고 점선만 남겼다. interconnect 는 그림 안 이름으로 넣지 않고 <desc> 와 노드 설명에 두었다 — 이름이 아니라 두 node 사이의 구간이라 상자도 화살표도 아니다. 접근 비용의 크기와 이 Host 의 node 수는 이 SSOT 가 재지 않았으므로 그리지 않았다."
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,544 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "packet-control-and-data-paths",
|
||||
"title": "control/setup path 는 QEMU 를 지나고 data path 는 backend 에 따라 QEMU 를 지나기도 안 지나기도 한다",
|
||||
"question": "설정하는 경로와 packet 이 흐르는 경로는 어디에서 갈라지고, 그 두 갈래 가운데 어느 쪽이 QEMU 사용자 공간을 지나는가",
|
||||
"type": "network",
|
||||
"direction": "TB",
|
||||
"audience": [
|
||||
"가상 머신 안의 요청이 느릴 때 계층을 가르려는 사람",
|
||||
"vhost-net 이 무엇을 우회하는지 확인하려는 사람"
|
||||
],
|
||||
"summary": "virsh 에서 시작한 control/setup path 는 libvirt 와 QEMU 를 지나 virtio-net Device Model 에서 backend 를 설정하고, packet 이 흐르는 data path 는 TAP 에서 그 backend 둘 가운데 하나로 갈라져 vhost-net 쪽은 QEMU 사용자 공간을 지나지 않고 virtqueue 로 모인다.",
|
||||
"alt": "위쪽 줄은 virsh, libvirt, QEMU, virtio-net Device Model 로 이어지는 control/setup path 이고 아래쪽 줄은 Physical NIC 에서 TAP 까지 이어지는 data path 다. 두 줄이 vhost-net 과 QEMU virtio backend 에서 만나 virtqueue 를 지나 Guest 로 모이는 구성도.",
|
||||
"long_description": "왼쪽에서 오른쪽으로 읽는다. 위쪽 줄이 control/setup path 다. virsh 에서 libvirt 와 QEMU 를 지나 virtio-net Device Model 에 닿고, 그 device model 이 virtual NIC 생성과 feature negotiation, virtqueue 설정, vhost-net backend 설정을 맡는다. 아래쪽 줄이 data path 다. Physical NIC 에서 Physical NIC Driver 와 Linux Bridge, TAP 까지 온다. 두 줄은 네 번째 열 다음에서 만난다. device model 은 점선으로 backend 둘을 설정하고, TAP 은 실선으로 그 둘 가운데 실제로 쓰는 쪽에 packet 을 넘긴다. vhost-net 을 쓰면 packet 은 Host Kernel 안에서 처리돼 QEMU 사용자 공간을 지나지 않고, QEMU virtio backend 를 쓰면 같은 packet 이 QEMU 사용자 공간을 지난다. 어느 쪽이든 virtqueue 에서 만나 Guest 로 올라간다. 이 Host 가 둘 중 어느 backend 를 쓰고 있는지는 그림이 말하지 않는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "125. 최종 기준 구조",
|
||||
"line": 5682
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 세운 것은 방향이 있는 경로 셋이고 그 셋이 같은 device model 과 같은 virtqueue 를 공유한다. SSOT 는 셋을 각각 따로 선 직선으로 적어 두어서 QEMU 가 control 에는 남고 data 에서는 빠질 수 있다는 사실이 한 장에 보이지 않는다. comparison 은 관계선을 지워도 뜻이 남는 자리에 쓰는 문법이라 여기서는 표가 되고, two-zone-pipeline 은 구역을 둘로 못 나눈다 — 여기 갈래는 경계가 아니라 backend 선택이 만든다. component-flow 는 갈라졌다 모이는 경로를 그대로 옮긴다.",
|
||||
"focus_node": "virtio-net-device-model"
|
||||
},
|
||||
"groups": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "virsh",
|
||||
"label": "virsh",
|
||||
"kind": "cli",
|
||||
"role": "actor",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"User"
|
||||
],
|
||||
"description": "VM/network 관리 CLI. packet datapath 에는 직접 참여하지 않는다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5662,
|
||||
"end_line": 5662
|
||||
},
|
||||
{
|
||||
"start_line": 5687,
|
||||
"end_line": 5689
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "libvirt",
|
||||
"label": "libvirt",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "VM lifecycle 과 NIC/network configuration 을 관리한다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5663,
|
||||
"end_line": 5663
|
||||
},
|
||||
{
|
||||
"start_line": 5691,
|
||||
"end_line": 5691
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu",
|
||||
"label": "QEMU",
|
||||
"kind": "process",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"Host Userspace"
|
||||
],
|
||||
"description": "실제로 실행되는 프로세스. 이 안에 virtio-net device model 이 있다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5666,
|
||||
"end_line": 5666
|
||||
},
|
||||
{
|
||||
"start_line": 5693,
|
||||
"end_line": 5693
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtio-net-device-model",
|
||||
"label": "virtio-net Device Model",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"virtual NIC 생성",
|
||||
"feature negotiation",
|
||||
"virtqueue 설정",
|
||||
"vhost-net backend 설정"
|
||||
],
|
||||
"description": "control/setup path 가 닿는 자리. 장치를 만들고 backend 를 연결한다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5666,
|
||||
"end_line": 5666
|
||||
},
|
||||
{
|
||||
"start_line": 5695,
|
||||
"end_line": 5700
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "physical-nic",
|
||||
"label": "Physical NIC",
|
||||
"kind": "hardware",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"description": "밖에서 온 packet 이 처음 닿는 하드웨어.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5706,
|
||||
"end_line": 5708
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "physical-nic-driver",
|
||||
"label": "Physical NIC Driver",
|
||||
"kind": "driver",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "그 하드웨어를 제어하는 Host kernel driver.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5674,
|
||||
"end_line": 5674
|
||||
},
|
||||
{
|
||||
"start_line": 5710,
|
||||
"end_line": 5710
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "linux-bridge",
|
||||
"label": "Linux Bridge /\nRouting / NAT",
|
||||
"kind": "network",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "L2 software switch 와 L3 routing, 주소 변환이 놓이는 자리.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5672,
|
||||
"end_line": 5673
|
||||
},
|
||||
{
|
||||
"start_line": 5712,
|
||||
"end_line": 5712
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "tap",
|
||||
"label": "TAP",
|
||||
"kind": "network",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"Host-side interface"
|
||||
],
|
||||
"description": "VM 의 Ethernet frame 과 Host Linux networking 을 잇는 접점.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5671,
|
||||
"end_line": 5671
|
||||
},
|
||||
{
|
||||
"start_line": 5714,
|
||||
"end_line": 5714
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vhost-net",
|
||||
"label": "vhost-net",
|
||||
"kind": "kernel",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"Host Kernel"
|
||||
],
|
||||
"description": "반복되는 packet datapath 를 Host kernel 에서 처리하는 backend.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5669,
|
||||
"end_line": 5669
|
||||
},
|
||||
{
|
||||
"start_line": 5716,
|
||||
"end_line": 5716
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu-virtio-backend",
|
||||
"label": "QEMU virtio backend",
|
||||
"kind": "process",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"QEMU Userspace"
|
||||
],
|
||||
"description": "vhost-net 을 쓰지 않을 때 datapath backend 를 맡는 쪽.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5668,
|
||||
"end_line": 5668
|
||||
},
|
||||
{
|
||||
"start_line": 5742,
|
||||
"end_line": 5742
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtqueue",
|
||||
"label": "virtqueue",
|
||||
"kind": "queue",
|
||||
"role": "queue",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"shared queue"
|
||||
],
|
||||
"description": "Guest 와 Host backend 가 I/O buffer 를 주고받는 descriptor 기반 공유 큐.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5667,
|
||||
"end_line": 5667
|
||||
},
|
||||
{
|
||||
"start_line": 5718,
|
||||
"end_line": 5718
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "guest",
|
||||
"label": "Guest",
|
||||
"kind": "service",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"virtio-net Frontend Driver",
|
||||
"Guest TCP/IP Stack",
|
||||
"Socket",
|
||||
"Keycloak"
|
||||
],
|
||||
"description": "packet 이 올라가 애플리케이션 socket 에 닿는 쪽.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5664,
|
||||
"end_line": 5665
|
||||
},
|
||||
{
|
||||
"start_line": 5720,
|
||||
"end_line": 5726
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "virsh-to-libvirt",
|
||||
"from": "virsh",
|
||||
"to": "libvirt",
|
||||
"label": "Control / Setup",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5662,
|
||||
"end_line": 5663
|
||||
},
|
||||
{
|
||||
"start_line": 5684,
|
||||
"end_line": 5691
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "libvirt-to-qemu",
|
||||
"from": "libvirt",
|
||||
"to": "qemu",
|
||||
"label": "NIC/network configuration",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5663,
|
||||
"end_line": 5663
|
||||
},
|
||||
{
|
||||
"start_line": 5691,
|
||||
"end_line": 5693
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu-to-device-model",
|
||||
"from": "qemu",
|
||||
"to": "virtio-net-device-model",
|
||||
"label": "QEMU process",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5666,
|
||||
"end_line": 5666
|
||||
},
|
||||
{
|
||||
"start_line": 5693,
|
||||
"end_line": 5695
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "nic-to-driver",
|
||||
"from": "physical-nic",
|
||||
"to": "physical-nic-driver",
|
||||
"label": "Ethernet Frame",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5671,
|
||||
"end_line": 5671
|
||||
},
|
||||
{
|
||||
"start_line": 5708,
|
||||
"end_line": 5710
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "driver-to-bridge",
|
||||
"from": "physical-nic-driver",
|
||||
"to": "linux-bridge",
|
||||
"label": "L2 forwarding",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5672,
|
||||
"end_line": 5672
|
||||
},
|
||||
{
|
||||
"start_line": 5710,
|
||||
"end_line": 5712
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "bridge-to-tap",
|
||||
"from": "linux-bridge",
|
||||
"to": "tap",
|
||||
"label": "Ethernet Frame",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5671,
|
||||
"end_line": 5671
|
||||
},
|
||||
{
|
||||
"start_line": 5712,
|
||||
"end_line": 5714
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "setup-vhost-backend",
|
||||
"from": "virtio-net-device-model",
|
||||
"to": "vhost-net",
|
||||
"label": "vhost-net backend 설정",
|
||||
"kind": "control",
|
||||
"style": "dashed",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5700,
|
||||
"end_line": 5700
|
||||
},
|
||||
{
|
||||
"start_line": 5716,
|
||||
"end_line": 5716
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "setup-qemu-backend",
|
||||
"from": "virtio-net-device-model",
|
||||
"to": "qemu-virtio-backend",
|
||||
"label": "",
|
||||
"kind": "control",
|
||||
"style": "dashed",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5668,
|
||||
"end_line": 5668
|
||||
},
|
||||
{
|
||||
"start_line": 5700,
|
||||
"end_line": 5700
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "tap-to-vhost",
|
||||
"from": "tap",
|
||||
"to": "vhost-net",
|
||||
"label": "vhost-net 사용",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5703,
|
||||
"end_line": 5703
|
||||
},
|
||||
{
|
||||
"start_line": 5714,
|
||||
"end_line": 5716
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "tap-to-qemu-backend",
|
||||
"from": "tap",
|
||||
"to": "qemu-virtio-backend",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5729,
|
||||
"end_line": 5729
|
||||
},
|
||||
{
|
||||
"start_line": 5740,
|
||||
"end_line": 5742
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vhost-to-virtqueue",
|
||||
"from": "vhost-net",
|
||||
"to": "virtqueue",
|
||||
"label": "Data Path",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5703,
|
||||
"end_line": 5703
|
||||
},
|
||||
{
|
||||
"start_line": 5716,
|
||||
"end_line": 5718
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu-backend-to-virtqueue",
|
||||
"from": "qemu-virtio-backend",
|
||||
"to": "virtqueue",
|
||||
"label": "Data Path",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5729,
|
||||
"end_line": 5729
|
||||
},
|
||||
{
|
||||
"start_line": 5742,
|
||||
"end_line": 5744
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtqueue-to-guest",
|
||||
"from": "virtqueue",
|
||||
"to": "guest",
|
||||
"label": "virtio-net Frontend Driver",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 5664,
|
||||
"end_line": 5664
|
||||
},
|
||||
{
|
||||
"start_line": 5718,
|
||||
"end_line": 5726
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "network 으로 고른 이유는 이 절이 답하는 물음이 「어느 경로가 어느 계층을 지나는가」이기 때문이다. 점선은 설정이고 실선은 packet 이다 — 색이 아니라 선 모양으로 갈랐다. Internet/Client 와 Socket, Keycloak 은 노드로 세우지 않고 양 끝 노드의 details 로 내렸다. §125 가 적은 세 직선을 그대로 세 장으로 옮기면 SSOT 와 같은 그림이 되고, 이 글이 필요로 하는 것은 그 셋이 어디를 공유하는가다. LR 로 그렸더니 캔버스가 2249px 가 되어 TB 로 바꿨다. 갈래 구간의 엇갈리는 두 이음에 라벨을 다 붙였더니 라벨끼리 겹치고(497px²) 하나가 device model 상자에 먹혀(316px²) check-figure-overlap 이 걸렸다. 그 구간에서는 vhost-net 쪽 두 이음에만 이름을 남기고 QEMU backend 쪽 두 이음은 선 모양(점선=설정·실선=packet)과 도착 상자 이름으로 읽게 두었다."
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,230 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "page-fault-layers-in-a-vm",
|
||||
"title": "세 fault 는 변환 사슬의 서로 다른 단계에서 갈라져 나가 서로 다른 계층이 받는다",
|
||||
"question": "Guest Page Fault · EPT Violation · Host Page Fault 는 각각 어느 단계에서 갈라지고 누가 받는가",
|
||||
"type": "flow",
|
||||
"direction": "TB",
|
||||
"audience": [
|
||||
"VM 안의 메모리 지연을 계층별로 가르려는 사람",
|
||||
"fault 지표를 어느 쪽부터 볼지 정하려는 사람"
|
||||
],
|
||||
"summary": "Guest Page Table 에서 갈라진 Guest Page Fault 는 Guest Kernel 이 받고, 그다음 단계인 EPT 에서 갈라진 EPT Violation 은 VM Exit 뒤 KVM 이 받으며, QEMU 의 Guest RAM Backing 에서 갈라진 Host Page Fault 는 Host Kernel 이 받는다.",
|
||||
"alt": "Guest Page Table 과 EPT, QEMU 의 Guest RAM Backing 에서 각각 fault 가 갈라져 Guest Kernel · KVM · Host Kernel 로 가는 갈래도.",
|
||||
"long_description": "위에서 아래로 읽는다. 맨 위 왼쪽에 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 로 간다. 받는 자리의 이름이 그대로 계층 이름이다 — Guest Kernel · KVM · Host Kernel. QEMU 갈래가 EPT 갈래와 선으로 이어져 있지 않은 것은 근거 문서가 둘을 잇는 문장을 두지 않았기 때문이다. 다섯 원인이 무엇인지와 두 사건을 항목별로 견주는 것은 본문의 표가 맡는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "48. Guest Page Fault와 EPT Violation 비교",
|
||||
"line": 2480
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 세우는 것은 같은 변환 사슬 위에서 사건이 어느 단계에 갈라져 어느 계층으로 빠지는가다. 갈래와 그 갈래가 닿는 자리가 논지라 방향 있는 경로 문법이 맞는다. sequence 는 참여자끼리 주고받는 메시지에 시각 순서를 매기는 문법인데 여기서 갈리는 셋은 서로 다른 접근에서 따로 일어나는 사건이라 하나의 시각 순서로 세울 수 없다. two-zone-pipeline 은 구역 안쪽 엣지 라벨을 캔버스 위쪽에 고정하는데 이 그림은 Guest 구역 안과 Host 구역 안에 각각 갈래가 있어 그 라벨이 구역 점선에 얹힌다.",
|
||||
"focus_node": "ept"
|
||||
},
|
||||
"groups": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "guest-page-table",
|
||||
"label": "Guest Page Table",
|
||||
"kind": "component",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"GVA → GPA"
|
||||
],
|
||||
"description": "첫 번째 translation 단계. 여기서 접근을 완료할 수 없으면 Guest Page Fault 가 난다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2443,
|
||||
"end_line": 2445
|
||||
},
|
||||
{
|
||||
"start_line": 2484,
|
||||
"end_line": 2485
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "guest-kernel",
|
||||
"label": "Guest Kernel",
|
||||
"kind": "service",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"description": "Guest Page Fault 를 받는 계층.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2487,
|
||||
"end_line": 2487
|
||||
},
|
||||
{
|
||||
"start_line": 2493,
|
||||
"end_line": 2494
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "ept",
|
||||
"label": "EPT",
|
||||
"kind": "component",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"GPA → HPA"
|
||||
],
|
||||
"description": "두 번째 translation 단계. Guest translation 이 성공한 뒤 여기서 막히면 EPT Violation 이 난다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2465,
|
||||
"end_line": 2469
|
||||
},
|
||||
{
|
||||
"start_line": 2484,
|
||||
"end_line": 2485
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "kvm",
|
||||
"label": "KVM",
|
||||
"kind": "service",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"description": "EPT Violation 을 VM Exit 뒤에 받는 계층.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2471,
|
||||
"end_line": 2473
|
||||
},
|
||||
{
|
||||
"start_line": 2487,
|
||||
"end_line": 2487
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu-backing",
|
||||
"label": "QEMU · Guest RAM Backing",
|
||||
"kind": "store",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"Host Virtual Memory"
|
||||
],
|
||||
"description": "Host 의 일반 userspace 메모리로 관리되는 자리. demand allocation 이나 reclaim, swap 때문에 여기서 fault 가 난다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2504,
|
||||
"end_line": 2504
|
||||
},
|
||||
{
|
||||
"start_line": 2514,
|
||||
"end_line": 2519
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-kernel",
|
||||
"label": "Host Kernel",
|
||||
"kind": "service",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"description": "Host Page Fault 를 받아 필요한 Host page 를 처리하는 계층.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2521,
|
||||
"end_line": 2525
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "guest-fault-branch",
|
||||
"from": "guest-page-table",
|
||||
"to": "guest-kernel",
|
||||
"label": "Guest Page Fault",
|
||||
"kind": "event",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2484,
|
||||
"end_line": 2487
|
||||
},
|
||||
{
|
||||
"start_line": 2493,
|
||||
"end_line": 2494
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "translation-continues",
|
||||
"from": "guest-page-table",
|
||||
"to": "ept",
|
||||
"label": "GPA",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2461,
|
||||
"end_line": 2467
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "ept-violation-branch",
|
||||
"from": "ept",
|
||||
"to": "kvm",
|
||||
"label": "EPT Violation · VM Exit",
|
||||
"kind": "event",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2467,
|
||||
"end_line": 2473
|
||||
},
|
||||
{
|
||||
"start_line": 2487,
|
||||
"end_line": 2487
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-fault-branch",
|
||||
"from": "qemu-backing",
|
||||
"to": "host-kernel",
|
||||
"label": "Host Page Fault",
|
||||
"kind": "event",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 2517,
|
||||
"end_line": 2525
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "flow 로 고른 이유는 이 절이 답하는 물음이 「어느 단계에서 갈라져 누구에게 가는가」이기 때문이다. 세 fault 를 나란히 늘어놓으면 그것은 본문에 이미 있는 비교표와 같은 것이 되므로, 갈래가 사슬의 어느 자리에서 나오는지를 자리로 보이게 세웠다. EPT 에서 나온 갈래와 QEMU backing 에서 나온 갈래를 선으로 잇지 않은 것은 근거 문서가 EPT 가 얻은 HPA 와 QEMU backing 의 Host physical address 를 같은 것이라고 적지 않았기 때문이다. 없는 문장을 선으로 만들지 않았다. 처음에는 Guest 와 Host 를 group 상자로 두었는데, 레이아웃이 노드를 group 별로 모으지 않아 Host 상자가 Guest 노드까지 감싸 버렸다. 받는 자리의 이름(Guest Kernel · KVM · Host Kernel)이 이미 계층을 말하므로 group 을 빼고 이름에 맡겼다."
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "virtio-balloon-inflate-deflate",
|
||||
"title": "balloon target 은 VM Boundary 를 건너 driver 에 닿고 확보한 page 는 같은 경계를 되건너 온다",
|
||||
"question": "Host 가 balloon target 을 바꾸면 그 요청은 어떤 순서로 어느 경계를 건너 Guest 안의 driver 까지 가고, 확보된 page 는 어떻게 Host 로 돌아오는가",
|
||||
"type": "sequence",
|
||||
"direction": "LR",
|
||||
"audience": [
|
||||
"Host RAM 압박에 ballooning 을 쓸지 판단하려는 사람",
|
||||
"Guest 안의 메모리 압박을 Host 조치와 이어 보려는 사람"
|
||||
],
|
||||
"summary": "balloon target 조정은 Host Memory Management 에서 QEMU 쪽 device 와 virtqueue 를 지나 VM Boundary 를 넘어 Guest kernel 의 driver 까지 가고, driver 가 확보한 Guest page 는 같은 경계를 반대 방향으로 되건너 Host 의 회수 가능 backing 이 된다.",
|
||||
"alt": "virtio-balloon Driver, virtqueue, QEMU virtio-balloon Device, Host Memory Management 네 참여자 사이의 순서도. 앞 세 메시지는 오른쪽에서 왼쪽으로 Guest 안으로 들어가고 뒤 세 메시지는 왼쪽에서 오른쪽으로 Host 로 돌아온다.",
|
||||
"long_description": "참여자는 왼쪽부터 Guest kernel 의 virtio-balloon Driver, virtqueue, QEMU virtio-balloon Device, Host Memory Management 네 개다. 메시지는 위에서 아래로 읽는다. 1번은 Host Memory Management 가 QEMU 쪽 device 에 balloon target 조정을 보내는 것이고, 2번에서 그 요청이 virtqueue 로 내려가면서 VM Boundary 를 넘고, 3번에서 Guest kernel 의 driver 에 balloon target 이 닿는다. 여기서 방향이 바뀐다. 4번에서 driver 가 확보한 Guest page 를 virtqueue 에 올리고, 5번에서 그것이 다시 VM Boundary 를 넘어 QEMU 쪽 device 로 가고, 6번에서 device 가 Host Memory Management 에 회수 가능 backing 으로 넘긴다. target 을 올릴 때와 내릴 때 Guest 가 쓸 수 있는 memory 가 어느 쪽으로 움직이는지는 본문의 표가 맡는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "65. virtio-balloon 구조",
|
||||
"line": 3008
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "sequence",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-approval-sequence"
|
||||
],
|
||||
"rationale": "이 절과 다음 절이 세운 것은 조각들의 나열이 아니라 balloon 요청이 지나는 차례와 그 차례가 VM Boundary 를 두 번 건넌다는 사실이다. two-zone-pipeline 으로 구역 둘을 그려 보니 같은 구역 안의 이음이 캔버스 맨 위로 튀어 올라가고 경계 라벨이 건너편 상자에 먹혔다. orchestrator-workers 는 QEMU 아래에 교체 가능한 워커들을 두는 그림이라 driver 가 Guest 안에 있다는 사실이 지워진다. sequence 는 참여자를 한 줄로 세우고 메시지에 번호를 붙이므로 경계를 두 번 건너는 왕복이 그대로 드러난다.",
|
||||
"focus_node": "balloon-driver"
|
||||
},
|
||||
"groups": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "balloon-driver",
|
||||
"label": "virtio-balloon Driver",
|
||||
"kind": "driver",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"Guest Kernel"
|
||||
],
|
||||
"description": "Guest kernel 쪽 balloon driver. target 을 받아 Guest page 를 확보한다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3013,
|
||||
"end_line": 3015
|
||||
},
|
||||
{
|
||||
"start_line": 3046,
|
||||
"end_line": 3046
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtqueue",
|
||||
"label": "virtqueue",
|
||||
"kind": "queue",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"description": "Guest 와 QEMU 가 balloon 요청을 주고받는 공유 큐. VM Boundary 바로 안쪽이다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3017,
|
||||
"end_line": 3017
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu-balloon-device",
|
||||
"label": "QEMU virtio-balloon\nDevice",
|
||||
"kind": "service",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"description": "Host 쪽 balloon device. 경계를 넘어온 내용을 Host memory management 로 넘긴다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3022,
|
||||
"end_line": 3022
|
||||
},
|
||||
{
|
||||
"start_line": 3037,
|
||||
"end_line": 3041
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-memory-management",
|
||||
"label": "Host Memory\nManagement",
|
||||
"kind": "service",
|
||||
"role": "participant",
|
||||
"shape": "box",
|
||||
"description": "balloon target 을 정하는 쪽이자 회수 기회가 생기는 자리.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3025,
|
||||
"end_line": 3025
|
||||
},
|
||||
{
|
||||
"start_line": 3034,
|
||||
"end_line": 3037
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "target-adjust",
|
||||
"from": "host-memory-management",
|
||||
"to": "qemu-balloon-device",
|
||||
"label": "Balloon target 조정",
|
||||
"kind": "control",
|
||||
"order": 1,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3034,
|
||||
"end_line": 3041
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "target-crosses-in",
|
||||
"from": "qemu-balloon-device",
|
||||
"to": "virtqueue",
|
||||
"label": "VM Boundary",
|
||||
"kind": "control",
|
||||
"order": 2,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3017,
|
||||
"end_line": 3022
|
||||
},
|
||||
{
|
||||
"start_line": 3041,
|
||||
"end_line": 3046
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "target-to-driver",
|
||||
"from": "virtqueue",
|
||||
"to": "balloon-driver",
|
||||
"label": "Balloon target",
|
||||
"kind": "control",
|
||||
"order": 3,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3013,
|
||||
"end_line": 3017
|
||||
},
|
||||
{
|
||||
"start_line": 3043,
|
||||
"end_line": 3046
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "pages-secured",
|
||||
"from": "balloon-driver",
|
||||
"to": "virtqueue",
|
||||
"label": "Guest pages 확보",
|
||||
"kind": "data",
|
||||
"order": 4,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3046,
|
||||
"end_line": 3050
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "pages-cross-out",
|
||||
"from": "virtqueue",
|
||||
"to": "qemu-balloon-device",
|
||||
"label": "VM Boundary",
|
||||
"kind": "data",
|
||||
"order": 5,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3017,
|
||||
"end_line": 3025
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "reclaimable-backing",
|
||||
"from": "qemu-balloon-device",
|
||||
"to": "host-memory-management",
|
||||
"label": "회수 가능 backing",
|
||||
"kind": "data",
|
||||
"order": 6,
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 3022,
|
||||
"end_line": 3028
|
||||
},
|
||||
{
|
||||
"start_line": 3077,
|
||||
"end_line": 3079
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "sequence 로 고른 이유는 §65 의 구조가 §66 의 차례와 붙어야 뜻이 나기 때문이다. 구조만 그리면 화살표가 한 방향뿐이고, 차례만 적으면 경계가 어디인지 안 보인다. deflate 쪽 절(§68)은 prepare 가 잡아 온 문맥(L2986-L3083) 밖이라 그 방향은 그리지 않았고, target 을 올릴 때와 내릴 때 Guest usable memory 가 어느 쪽으로 가는지는 기록 본문의 표가 이미 맡고 있어 그림에 넣지 않았다. 노드 라벨의 줄바꿈은 렌더러가 22자까지만 접기 때문에 넣었다 — 접지 않으면 글자가 상자 밖으로 나간다."
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "vm-exit-handling-cycle",
|
||||
"title": "VM Exit 은 KVM 에서 끝나기도 하고 QEMU userspace 까지 가기도 한다",
|
||||
"question": "VM Exit 이 난 뒤 제어권은 어디까지 올라갔다가 어떻게 Guest 로 돌아오는가",
|
||||
"type": "flow",
|
||||
"direction": "LR",
|
||||
"audience": [
|
||||
"VM 안의 지연을 계층별로 가르려는 사람",
|
||||
"KVM 실행 경로를 처음 읽는 사람"
|
||||
],
|
||||
"summary": "VM Exit 은 KVM 에서 Exit Reason 을 보고 두 갈래로 나뉘며, 커널 안에서 끝난 갈래와 QEMU userspace 를 거친 갈래가 어느 쪽이든 VM Entry 를 지나 Guest 실행으로 돌아온다.",
|
||||
"alt": "Guest 에서 VM Exit 으로 KVM 에 닿은 뒤 커널 처리와 QEMU 처리 두 갈래로 갈라졌다가 VM Entry 로 다시 Guest 실행 재개에 모이는 흐름도.",
|
||||
"long_description": "왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝에 Guest 가 있고 VM Exit 이 KVM 으로 제어권을 넘긴다. KVM 은 Exit Reason 을 확인해 두 갈래 중 하나를 고른다. 커널 안에서 처리할 수 있는 Exit 은 Kernel 쪽 KVM 커널 처리로 가고, userspace 처리가 필요한 Exit 만 KVM_RUN 이 반환되면서 Userspace 쪽 QEMU 로 간다. QEMU 는 필요한 장치 동작을 처리한 뒤 KVM_RUN 을 다시 호출한다. 두 갈래는 오른쪽 끝에서 다시 만나며 어느 쪽이든 VM Entry 를 지나 Guest 실행 재개로 돌아온다. 무엇이 Exit 을 부르는지와 Exit 이 잦을 때 무엇을 함께 보는지는 본문의 표와 문단이 맡는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "9. VM Exit 이후 처리",
|
||||
"line": 493
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 주장하는 것은 제어권이 한 방향으로 지나가는 경로와 그 경로가 Exit Reason 에서 갈렸다가 다시 만난다는 것이다. sequence 는 참여자마다 생명선을 세우고 메시지를 시각 순서로 세우는데 이 절에는 갈래가 있어 한 줄로 세울 수 없고, orchestrator-workers 는 KVM 아래에 교체 가능한 워커들을 두는 그림이라 커널 갈래와 userspace 갈래가 서로 다른 경계에 있다는 사실이 지워진다.",
|
||||
"focus_node": "kvm"
|
||||
},
|
||||
"groups": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "guest",
|
||||
"label": "Guest",
|
||||
"kind": "service",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"description": "VM Exit 이 나기 직전까지 실행 중이던 Guest.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 498,
|
||||
"end_line": 501
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "kvm",
|
||||
"label": "KVM",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"Exit Reason"
|
||||
],
|
||||
"description": "VM Exit 을 받아 Exit Reason 을 확인하고 갈래를 고르는 자리.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 495,
|
||||
"end_line": 495
|
||||
},
|
||||
{
|
||||
"start_line": 502,
|
||||
"end_line": 504
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "kvm-kernel-handling",
|
||||
"label": "KVM 커널 처리",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "커널 안에서 끝낼 수 있는 Exit 을 처리하는 갈래. QEMU 까지 올라가지 않는다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 508,
|
||||
"end_line": 510
|
||||
},
|
||||
{
|
||||
"start_line": 531,
|
||||
"end_line": 531
|
||||
}
|
||||
],
|
||||
"assumption": false,
|
||||
"details": [
|
||||
"Kernel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "qemu",
|
||||
"label": "QEMU",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"Userspace",
|
||||
"device emulation",
|
||||
"KVM_RUN 재호출"
|
||||
],
|
||||
"description": "userspace 처리가 필요한 Exit 에서만 개입하는 갈래.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 510,
|
||||
"end_line": 516
|
||||
},
|
||||
{
|
||||
"start_line": 533,
|
||||
"end_line": 533
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "guest-resume",
|
||||
"label": "Guest 실행 재개",
|
||||
"kind": "service",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"description": "두 갈래가 다시 만나는 자리. VM Exit 은 VM 종료가 아니라 여기로 돌아온다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 521,
|
||||
"end_line": 524
|
||||
},
|
||||
{
|
||||
"start_line": 529,
|
||||
"end_line": 529
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "exit-to-kvm",
|
||||
"from": "guest",
|
||||
"to": "kvm",
|
||||
"label": "VM Exit",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 495,
|
||||
"end_line": 495
|
||||
},
|
||||
{
|
||||
"start_line": 498,
|
||||
"end_line": 502
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "kernel-branch",
|
||||
"from": "kvm",
|
||||
"to": "kvm-kernel-handling",
|
||||
"label": "커널 처리 가능",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 504,
|
||||
"end_line": 510
|
||||
},
|
||||
{
|
||||
"start_line": 531,
|
||||
"end_line": 531
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "userspace-branch",
|
||||
"from": "kvm",
|
||||
"to": "qemu",
|
||||
"label": "KVM_RUN return",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 508,
|
||||
"end_line": 512
|
||||
},
|
||||
{
|
||||
"start_line": 533,
|
||||
"end_line": 533
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "kernel-reentry",
|
||||
"from": "kvm-kernel-handling",
|
||||
"to": "guest-resume",
|
||||
"label": "VM Entry",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 518,
|
||||
"end_line": 524
|
||||
},
|
||||
{
|
||||
"start_line": 531,
|
||||
"end_line": 531
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "userspace-reentry",
|
||||
"from": "qemu",
|
||||
"to": "guest-resume",
|
||||
"label": "VM Entry",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 514,
|
||||
"end_line": 524
|
||||
},
|
||||
{
|
||||
"start_line": 533,
|
||||
"end_line": 533
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "flow 로 고른 이유는 이 절이 답하는 물음이 「제어권이 어디까지 갔다가 어떻게 돌아오는가」이기 때문이다. Kernel·Userspace 를 경계 상자로 그리면 두 상자가 같은 열에서 겹쳐(check-figure-overlap 15750px²) 어느 노드가 어느 경계인지 오히려 흐려져 노드의 details 로 내렸다. 무엇이 Exit 을 부르는지(§8)와 Exit 이 잦을 때 무엇을 함께 보는지는 본문 표가 이미 맡고 있어 그림에 넣지 않았다."
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,258 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "vms-sharing-one-nvme",
|
||||
"title": "가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다",
|
||||
"question": "서로 다른 곳에서 시작한 I/O 는 어디에서 하나로 모여 device 로 나가는가",
|
||||
"type": "flow",
|
||||
"direction": "TB",
|
||||
"audience": [
|
||||
"가상 머신 위에서 본 저장소 지연의 원인을 가르려는 사람",
|
||||
"Storage Contention 과 CPU Contention 을 나눠 재려는 사람"
|
||||
],
|
||||
"summary": "VM1 QEMU 와 VM2 QEMU, Nginx, 호스트의 다른 프로세스에서 시작한 I/O 가 모두 하나의 Host Block Layer queue 로 들어가 NVMe 로 dispatch 된다.",
|
||||
"alt": "VM1 QEMU, VM2 QEMU, Nginx, Host 기타 네 곳에서 나온 화살표가 하나의 Host Block Layer 로 모이고 거기서 NVMe 로 나가는 수렴 흐름도.",
|
||||
"long_description": "위쪽에 네 출발점이 나란히 있다. VM1 QEMU 는 WRITE X 와 READ Y, WRITE Z 를 내고 VM2 QEMU 는 READ A 와 WRITE B 를 내며, 그 옆에 호스트의 Nginx 와 다른 프로세스가 있다. 네 갈래가 모두 가운데의 Host Block Layer 하나로 모인다. Host Block Layer 는 그 I/O 가 가상 머신 안에서 시작했는지 호스트 프로세스에서 시작했는지를 본질적으로 구분해 처리하는 계층이 아니라 들어온 것을 모두 Host block request 로 다루고, 그 요청들을 queue 에서 관리해 맨 아래 NVMe 로 dispatch 한다. 여기서 생기는 경쟁이 Storage Contention 이고 CPU 실행 시간을 두고 벌어지는 CPU Contention 과는 다투는 자원이 다르다는 것은 본문 문단이 맡는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "159. 여러 VM이 하나의 NVMe를 공유하면",
|
||||
"line": 7026
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 주장하는 것은 서로 다른 네 곳에서 시작한 요청이 한 자리로 모인다는 것이다. query-fanout 은 하나가 여러 저장소로 갈라지는 문법이라 방향이 반대이고, orchestrator-workers 는 가운데가 아래의 워커들에게 일을 나눠 주는 그림이라 「모인다」가 「나눈다」로 뒤집힌다. comparison 은 네 출발점을 나란히 늘어놓기만 해서 하나의 queue 로 모인다는 사실이 사라진다.",
|
||||
"focus_node": "host-block-layer"
|
||||
},
|
||||
"groups": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "vm1-qemu",
|
||||
"label": "VM1 QEMU",
|
||||
"kind": "service",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"WRITE X",
|
||||
"READ Y",
|
||||
"WRITE Z"
|
||||
],
|
||||
"description": "가상 머신 하나의 QEMU 프로세스. 게스트의 I/O 가 여기서 호스트 파일 I/O 가 된다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7029,
|
||||
"end_line": 7029
|
||||
},
|
||||
{
|
||||
"start_line": 7041,
|
||||
"end_line": 7044
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vm2-qemu",
|
||||
"label": "VM2 QEMU",
|
||||
"kind": "service",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"READ A",
|
||||
"WRITE B"
|
||||
],
|
||||
"description": "다른 가상 머신의 QEMU 프로세스.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7031,
|
||||
"end_line": 7031
|
||||
},
|
||||
{
|
||||
"start_line": 7046,
|
||||
"end_line": 7048
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "nginx",
|
||||
"label": "Nginx",
|
||||
"kind": "service",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"description": "호스트에서 직접 도는 프로세스.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7033,
|
||||
"end_line": 7033
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-other",
|
||||
"label": "Host 기타",
|
||||
"kind": "service",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"description": "그 밖의 호스트 프로세스.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7035,
|
||||
"end_line": 7038
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-block-layer",
|
||||
"label": "Host Block Layer",
|
||||
"kind": "service",
|
||||
"role": "queue",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"details": [
|
||||
"Host block request",
|
||||
"blk-mq"
|
||||
],
|
||||
"description": "어디서 왔는지 구분하지 않고 모두 Host block request 로 다루는 계층. queue 에서 관리해 device 로 내보낸다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7031,
|
||||
"end_line": 7031
|
||||
},
|
||||
{
|
||||
"start_line": 7017,
|
||||
"end_line": 7022
|
||||
},
|
||||
{
|
||||
"start_line": 7054,
|
||||
"end_line": 7054
|
||||
},
|
||||
{
|
||||
"start_line": 7058,
|
||||
"end_line": 7060
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "nvme",
|
||||
"label": "NVMe",
|
||||
"kind": "device",
|
||||
"role": "sink",
|
||||
"shape": "cylinder",
|
||||
"description": "네 갈래가 결국 함께 쓰는 하나의 물리 device.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7031,
|
||||
"end_line": 7031
|
||||
},
|
||||
{
|
||||
"start_line": 7019,
|
||||
"end_line": 7019
|
||||
},
|
||||
{
|
||||
"start_line": 7066,
|
||||
"end_line": 7069
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "vm1-to-block-layer",
|
||||
"from": "vm1-qemu",
|
||||
"to": "host-block-layer",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 1,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7029,
|
||||
"end_line": 7031
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "vm2-to-block-layer",
|
||||
"from": "vm2-qemu",
|
||||
"to": "host-block-layer",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 2,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7031,
|
||||
"end_line": 7031
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "nginx-to-block-layer",
|
||||
"from": "nginx",
|
||||
"to": "host-block-layer",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 3,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7031,
|
||||
"end_line": 7033
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-other-to-block-layer",
|
||||
"from": "host-other",
|
||||
"to": "host-block-layer",
|
||||
"label": "",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 4,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7031,
|
||||
"end_line": 7035
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "block-layer-to-nvme",
|
||||
"from": "host-block-layer",
|
||||
"to": "nvme",
|
||||
"label": "dispatch",
|
||||
"kind": "data",
|
||||
"style": "solid",
|
||||
"order": 5,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 7031,
|
||||
"end_line": 7031
|
||||
},
|
||||
{
|
||||
"start_line": 7054,
|
||||
"end_line": 7054
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "component-flow 를 고른 이유는 이 절이 답하는 물음이 「여러 곳에서 온 요청이 어디에서 하나가 되는가」이기 때문이고, 그 수렴은 자리로만 보인다. 네 출발점의 화살표에는 라벨을 달지 않았다 — 넷이 같은 「Host block request」라는 것이 이 그림의 논지인데 같은 라벨을 네 번 겹쳐 놓으면 라벨끼리 먹힌다. 그래서 그 이름은 모이는 노드의 details 로 올렸다. blk-mq 도 노드가 아니라 details 다 — §160 이 그리는 것은 CPU 넷이 자기 queue 를 갖는 다른 수렴이라 이 그림의 네 출발점과 같은 축에 놓을 수 없다. I/O Scheduler 와 NVMe Driver 는 §161 과 §164 가 대는 사실인데 이 앵커의 준비된 문맥(§158~§160) 밖이라 넣지 않았다. §159 의 「Host Process READ C」도 붙이지 않았다 — 그 프로세스가 Nginx 인지 Host 기타인지 SSOT 가 말하지 않는다."
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,264 @@
|
||||
{
|
||||
"version": "1.1",
|
||||
"id": "write-completion-boundaries",
|
||||
"title": "fsync() 의 요구가 물리 storage 까지 내려가는 사슬",
|
||||
"question": "게스트가 fsync() 를 부르면 그 요구는 어느 계층까지, 몇 번 손이 바뀌어 내려가야 하는가",
|
||||
"type": "flow",
|
||||
"direction": "TB",
|
||||
"audience": [
|
||||
"가상 머신 위에서 데이터베이스를 돌리며 durability 를 따지는 사람",
|
||||
"완료 응답이 무엇을 보장하는지 가르려는 사람"
|
||||
],
|
||||
"summary": "게스트 PostgreSQL 의 fsync() 요구는 게스트 파일시스템과 게스트 Block Layer 에서 FLUSH 등으로 바뀌고 virtio-blk 와 QEMU / Backend, Host Storage Stack 을 지나 Physical Storage 까지 전달되어야 한다.",
|
||||
"alt": "PostgreSQL 의 fsync() 가 Guest Filesystem, Guest Block Layer, virtio-blk, QEMU / Backend, Host Storage Stack 을 차례로 지나 Physical Storage 에 닿는 위에서 아래로 읽는 흐름도.",
|
||||
"long_description": "위에서 아래로 읽는다. 맨 위 PostgreSQL 이 fsync() 를 부르고 그 요구가 Guest Filesystem 으로 내려간다. Guest Block Layer 에서 FLUSH 등의 요청으로 바뀌고, virtio-blk 를 지나 VM 경계를 넘어 QEMU / Backend 로 간다. 거기서 Host Storage Stack 을 지나 맨 아래 Physical Storage 까지 그 의미가 전달되어야 완료가 성립한다. 여섯 번 손이 바뀌는 동안 한 곳이라도 그 의미를 지키지 않으면 게스트가 받은 완료 응답이 거짓이 된다. write() 완료와 writeback 완료, fsync/flush 완료, 전원 장애에도 안전한 durability 가 서로 다르다는 구분은 본문의 코드블록이 맡는다.",
|
||||
"source_context": {
|
||||
"document": "docs/virtualization/final/document.md",
|
||||
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "150. `fsync()`가 필요한 이유",
|
||||
"line": 6723
|
||||
}
|
||||
},
|
||||
"composition": {
|
||||
"profile": "component-flow",
|
||||
"diagram_only": true,
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"rationale": "이 절이 주장하는 것은 요구 하나가 한 방향으로 여러 계층을 지나야 완료가 된다는 것이다. sequence 는 참여자마다 생명선을 세우고 요청과 응답을 짝지어 세우는데 이 절에는 돌아오는 응답이 적혀 있지 않아 생명선이 전부 빈 세로줄이 된다. comparison 은 write 와 fsync 를 나란히 놓는 그림이라 「어디까지 내려가야 하는가」라는 자리의 문제가 지워진다.",
|
||||
"focus_node": "physical-storage"
|
||||
},
|
||||
"groups": [],
|
||||
"nodes": [
|
||||
{
|
||||
"id": "postgresql",
|
||||
"label": "PostgreSQL",
|
||||
"kind": "service",
|
||||
"role": "source",
|
||||
"shape": "box",
|
||||
"details": [
|
||||
"write()",
|
||||
"fsync()"
|
||||
],
|
||||
"description": "필요한 시점에 fsync() 를 불러 영속성 경계까지 반영을 요청하는 게스트 프로세스.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6742,
|
||||
"end_line": 6742
|
||||
},
|
||||
{
|
||||
"start_line": 6726,
|
||||
"end_line": 6734
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "guest-filesystem",
|
||||
"label": "Guest Filesystem",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "fsync() 요구를 처음 받는 게스트 파일시스템.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6746,
|
||||
"end_line": 6746
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "guest-block-layer",
|
||||
"label": "Guest Block Layer",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "요구를 FLUSH 등의 block 요청으로 바꾸는 계층.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6749,
|
||||
"end_line": 6751
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtio-blk",
|
||||
"label": "virtio-blk",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "그 요청을 VM 경계 너머로 옮기는 게스트 driver.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6753,
|
||||
"end_line": 6753
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu-backend",
|
||||
"label": "QEMU / Backend",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "요청을 받아 호스트 쪽 파일 I/O 나 block I/O 로 잇는 자리.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6756,
|
||||
"end_line": 6756
|
||||
},
|
||||
{
|
||||
"start_line": 6686,
|
||||
"end_line": 6686
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-storage-stack",
|
||||
"label": "Host Storage Stack",
|
||||
"kind": "service",
|
||||
"role": "service",
|
||||
"shape": "box",
|
||||
"description": "호스트 Page Cache 와 파일시스템, Block Layer 가 놓인 구간.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6759,
|
||||
"end_line": 6759
|
||||
},
|
||||
{
|
||||
"start_line": 6688,
|
||||
"end_line": 6692
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "physical-storage",
|
||||
"label": "Physical Storage",
|
||||
"kind": "store",
|
||||
"role": "sink",
|
||||
"shape": "box",
|
||||
"emphasis": "primary",
|
||||
"description": "요구가 여기까지 전달되어야 정전 이후 생존을 말할 수 있다.",
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6762,
|
||||
"end_line": 6765
|
||||
},
|
||||
{
|
||||
"start_line": 6729,
|
||||
"end_line": 6729
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "app-to-guest-fs",
|
||||
"from": "postgresql",
|
||||
"to": "guest-filesystem",
|
||||
"label": "fsync()",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"order": 1,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6744,
|
||||
"end_line": 6746
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "guest-fs-to-block-layer",
|
||||
"from": "guest-filesystem",
|
||||
"to": "guest-block-layer",
|
||||
"label": "",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"order": 2,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6746,
|
||||
"end_line": 6749
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "block-layer-to-virtio",
|
||||
"from": "guest-block-layer",
|
||||
"to": "virtio-blk",
|
||||
"label": "FLUSH 등",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"order": 3,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6751,
|
||||
"end_line": 6753
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "virtio-to-qemu",
|
||||
"from": "virtio-blk",
|
||||
"to": "qemu-backend",
|
||||
"label": "",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"order": 4,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6753,
|
||||
"end_line": 6756
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "qemu-to-host-stack",
|
||||
"from": "qemu-backend",
|
||||
"to": "host-storage-stack",
|
||||
"label": "",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"order": 5,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6756,
|
||||
"end_line": 6759
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
},
|
||||
{
|
||||
"id": "host-stack-to-physical",
|
||||
"from": "host-storage-stack",
|
||||
"to": "physical-storage",
|
||||
"label": "",
|
||||
"kind": "control",
|
||||
"style": "solid",
|
||||
"order": 6,
|
||||
"evidence": [
|
||||
{
|
||||
"start_line": 6759,
|
||||
"end_line": 6765
|
||||
}
|
||||
],
|
||||
"assumption": false
|
||||
}
|
||||
],
|
||||
"legend": [],
|
||||
"metadata": {
|
||||
"rationale": "component-flow 를 고른 이유는 이 절이 답하는 물음이 「fsync() 의 요구가 어디까지 내려가야 하는가」이기 때문이다. 앞뒤 절이 대는 사실 가운데 그리지 않은 것이 있다. 「write · writeback · fsync/flush · 전원 장애 생존」 네 완료 경계를 이 사슬 위에 표시하려 했으나 SSOT 는 §148 에서 네 이름을 나열할 뿐 writeback 완료와 fsync/flush 완료가 이 사슬의 어느 칸에서 성립하는지는 정하지 않았고, §148 자체가 이 앵커의 준비된 문맥(§149~§151) 밖이라 인용할 줄도 없다. 게다가 네 이름의 나열은 기록 본문에 이미 코드블록으로 있어 그림으로 다시 그리면 중복이다. 그래서 자리로만 보이는 것 — 요구가 여섯 번 손이 바뀌어 내려간다는 것 — 만 남겼다."
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user