Files
document-haness/docs/virtualization/tech-log-studio/storage-virtualization/concept/concept-guest-block-io-path-to-virtqueue.md
T
DongHyeonkaandClaude Opus 5 2109f726fe feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만
있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문
종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다.

SSOT 결함 둘을 고쳤다.

- kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이
  「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽
  `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다.
- virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데
  원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다.

기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은
둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더
든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다).

계약을 셋 고쳤다.

- kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/**
  28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는
  것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온
  것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수
  대조했을 때 가장 가까운 커밋도 28개가 어긋났다).
- virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고
  재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다.
- kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고
  넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다.

style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져
(실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고,
engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고
있었다(Question 기록에서 11.94 → 3.86).

verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:01:55 +09:00

14 KiB

id, kind, slug, title, topic, topicName, project, status, basisVersion, studio, assets, sourceRevision, source
id kind slug title topic topicName project status basisVersion studio assets sourceRevision source
3b05c1f0-13e9-414a-97a6-2078fb4bab26 CONCEPT guest-block-io-path-to-virtqueue Guest 의 write() 가 virtqueue 에 실리기까지 — VFS · Filesystem · Page Cache · Block Layer · virtio-blk storage-virtualization 스토리지 가상화 virtualization 초안 QEMU/KVM 의 virtio-blk frontend 와 virtqueue · 게스트가 ext4 나 XFS 에 buffered I/O 로 쓰는 경우 · 제4부가 커널과 QEMU 판을 고정하지 않았다 https://hyeonworks.com/studio/documents/3b05c1f0-13e9-414a-97a6-2078fb4bab26/edit
key file
guest-block-io-to-virtqueue ../../../final/assets/diagrams/guest-block-io-to-virtqueue/guest-block-io-to-virtqueue.svg
no-commit · 밖에서 반입한 문서 한 편. 고정할 저장소 리비전이 없다
final/document.md#128-전체-구조
final/document.md#129-guest-application-read-write-에서-시작
final/document.md#130-vfs-공통-파일-인터페이스-계층
final/document.md#131-filesystem-ext4-xfs-파일-세계를-block-공간에-배치
final/document.md#132-inode
final/document.md#133-page-cache-write-가-바로-ssd-write는-아니다
final/document.md#134-guest-block-i-o-layer
final/document.md#135-dev-vda-guest가-보는-가상-block-device
final/document.md#136-dev-vda-와-filesystem-관계
final/document.md#137-virtio-blk-guest의-가상-block-device-driver
final/document.md#138-virtio-blk와-virtqueue
final/document.md#139-virtqueue의-실제-의미
final/document.md#166-storage-i-o-completion
final/document.md#127-문서-목적
final/document.md#172-storage-virtualization-canonical-flow
final/document.md#173-network-virtualization과-비교
final/document.md#177-최종-요약

Guest 의 write() 가 virtqueue 에 실리기까지 — VFS · Filesystem · Page Cache · Block Layer · virtio-blk

가상 머신 안의 PostgreSQL 이 파일에 데이터를 기록하면, 그 요청은 게스트 Linux 안에서만 VFS 와 ext4·XFS, 페이지 캐시, 블록 I/O 계층, virtio-blk 를 지난 뒤에야 virtqueue 에 실려 가상 머신 경계에 닿는다. 계층마다 무엇을 하고 무엇이 바뀌는지 차례로 살펴본다. 계층 이름과 게스트·호스트 구분을 아는 사람을 독자로 둔다. Keycloak 멀티 노드 실험을 가상 머신 두 대 위에서 돌리고 있다 보니, 이 경로를 알아 두면 게스트에서 본 저장소 지연을 애플리케이션 문제와 가상 머신 경계 아래의 문제로 갈라 볼 수 있다.

관계

  • /dev/vda 뒤에 있는 것 — qcow2 · RAW · Host block device 이 글은 virtqueue 까지만 따라간다. 그 요청을 받은 QEMU 가 무엇에 연결돼 있는지는 그 글이 이어받는다.
  • 완료라는 말의 네 가지 뜻 — write · writeback · flush · 전원 장애 생존 여기서는 write() 성공이 SSD 영속화가 아니라는 데까지만 적었다. 그 응답이 어디까지 보장하는지는 그 글이 나눈다.
  • VM 아래에 한 번 더 있는 Host block stack — blk-mq · I/O Scheduler · NVMe 게스트 블록 계층과 같은 이름의 계층이 호스트에도 한 번 더 있고, 그쪽에서는 다른 가상 머신의 요청과 섞인다.
  • Guest 안에서 본 disk 로 backend 를 단정하지 않는다 게스트에 /dev/vda 가 보인다는 관측이 그 규칙의 근거가 된다.
  • 이 호스트의 /dev/vda 는 어떤 backend 에 붙어 있는가 이 글은 게스트가 보는 이름까지만 말한다. 이 장비에서 그 이름에 무엇이 붙는지는 그 물음이 출력으로 확정한다.
  • Guest 의 fsync() 지연과 Host storage 지연은 같이 오르는가 경로는 적었지만 각 구간의 지연을 이 호스트에서 재지 않았다.
  • Guest 의 packet 이 Host Physical NIC 에 닿기까지 — virtio-net · virtqueue · vhost-net · TAP · Bridge virtqueue 라는 같은 구조를 패킷 쪽에서 설명한 글이다.

본문

애플리케이션은 저장장치를 직접 다루지 않는다

가상 머신 안의 PostgreSQL 이나 Keycloak 같은 프로세스는 SSD 나 virtio-blk 를 직접 다루지 않는다. 파일에 데이터를 기록할 때 개념적으로 부르는 것은 시스템 콜 하나다.

write(fd, buffer, size);

파일과 관련된 시스템 콜은 이것 말고도 여럿이다.

open()
read()
write()
close()
fsync()

이 시점에는 QEMU 도 qcow2 도 호스트의 NVMe 도 등장하지 않는다. 애플리케이션은 저장장치를 조작하는 것이 아니라 게스트 Linux 커널에 파일 연산을 요청하고, 그 뒤의 계층들이 그 요청을 하나씩 옮긴다.

VFS 가 어느 파일시스템으로 보낼지 정한다

VFS(Virtual File System, 가상 파일 시스템)는 Linux 커널 안에서 여러 파일시스템을 같은 방식으로 쓸 수 있게 묶어 주는 공통 계층이다. 게스트가 ext4 를 쓰든 XFS 를 쓰든 애플리케이션이 부르는 write() 는 같고, 그 뒤를 VFS 가 갈라 준다. VFS 는 이 fd 가 어떤 파일인지 확인하고, 그 파일이 어떤 파일시스템에 속하는지 확인한 뒤 해당 파일시스템 구현으로 연산을 넘긴다.

ext4 와 XFS 가 파일을 블록 공간에 배치한다

SSD 는 /var/lib/postgresql/data 같은 디렉터리 구조를 모른다. 저장장치가 보는 것은 번호가 붙은 블록 단위 공간인데 사람과 프로그램이 보는 것은 파일과 디렉터리 계층이다. 그 둘을 잇는 계층이 ext4XFS 같은 파일시스템이다. 파일시스템이 관리하는 것은 파일 이름과 디렉터리 구조, 파일 크기, 소유자와 권한, 시각 정보, inode 와 메타데이터, 파일 데이터가 저장될 블록, 남은 공간, 파일시스템 일관성이다.

inode 는 파일 메타데이터와 저장 위치 정보를 관리하는 자료구조다. 디렉터리 항목이 파일 이름을 inode 번호로 옮기고, 그 inode 가 소유자와 권한, 크기, 시각 정보, 파일 데이터가 저장된 블록 정보를 들고 있다. 그래서 파일 이름과 inode 는 같은 것이 아니다. 어디까지 들어갈지는 SSOT 가 직접 그어 두었다.

Storage 가상화를 이해하기 위해 inode 내부 구현까지 파고들 필요는 없지만, filesystem이 파일과 block을 연결한다는 점은 알아야 한다.

이 글도 거기서 멈춘다.

write() 가 끝나도 SSD 에는 아직 없다

일반적인 buffered I/O 에서는 write() 가 호출될 때마다 물리 SSD 까지 즉시 내려갈 필요가 없다. 애플리케이션이 넘긴 데이터는 메모리의 페이지 캐시에 먼저 닿는다. 저장장치에 ABC 가 있는데 애플리케이션이 DEF 를 추가하면 페이지 캐시에는 ABCDEF 가 들어가고 SSD 에는 아직 ABC 만 있다. 이렇게 저장장치보다 최신인 페이지를 dirty page 라고 한다. 이후 커널 writeback 이 그 내용을 파일시스템과 블록 계층을 거쳐 저장장치 쪽으로 내려보낸다.

데이터가 페이지 캐시에 머무는 동안에도 호출이 성공으로 돌아오기 때문에, write() 성공과 물리 SSD 영속화 완료는 같은 사건이 아니다. 게스트 애플리케이션이 성공을 돌려받은 시점에 데이터가 어디에 있는지는 게스트 메모리까지만 확정된다.

블록 I/O 계층이 파일 세계를 블록 장치 세계로 옮긴다

파일시스템이 파일과 블록 할당을 관리하면, Linux 의 블록 I/O 서브시스템이 그 요청을 아래의 블록 장치 드라이버가 처리할 수 있는 I/O 요청으로 바꿔 전달한다. 파일시스템 세계에서 내려오는 것은 /users/data.db 의 오프셋 8192 에 4KB 를 쓰라는 요청이다. 블록 장치 세계에서는 그것이 /dev/vda 의 특정 위치에 READWRITE, FLUSH 를 거는 요청이 된다. 대표 요청은 넷이다 — READ, WRITE, FLUSH, DISCARD.

이 계층 안에는 biorequest, queue, blk-mq 가 실제로 있다. SSOT 는 blk-mq 의 tag allocator 같은 내부 구현을 별도 문서로 미뤘고 이 글도 이름까지만 적는다.

/dev/vda 는 게스트가 보는 이름이다

물리 머신에서는 /dev/sda/dev/nvme0n1 같은 블록 장치가 보인다. virtio-blk 를 쓰는 가상 머신에서는 흔히 /dev/vda/dev/vdb 처럼 보이고, 게스트 Linux 는 그것을 하나의 블록 장치로 인식한다. 게스트 안에서 lsblk 를 실행하면 그 장치와 파티션이 나온다.

lsblk
NAME   SIZE TYPE MOUNTPOINT
vda    100G disk
├─vda1   1G part /boot
└─vda2  99G part /

게스트에 이렇게 보여도 그 장치가 호스트의 실제 SSD 인지는 게스트 안에서 알 수 없는데, 이 장비의 /dev/vda 에 무엇이 붙어 있는지를 virsh domblklist 로 읽은 출력도 아직 없다. 게스트가 보는 것은 /dev/vda 에서 파티션으로, 파티션에서 파일시스템으로, 파일시스템에서 마운트 지점으로 이어지는 연결까지다. cd /var/lib/postgresql 로 디렉터리를 옮기는 동작은 파일시스템 세계를 지나고, lsblkvda 는 블록 장치 세계를 보여 준다.

SSOT 의 실험대 배치 절이 2026-09-03 에 그려 둔 이 실험대의 게스트는 위 예시와 다르게 생겼다. vda 는 20G 이고 루트를 ext4 로 담아 거기서 부팅하는데, 그 옆의 vdb 는 370K 짜리 iso9660 이고 레이블이 CIDATA 라 마운트되지 않는다. /dev/vdb 가 붙어 있다고 게스트가 그것을 파일시스템으로 쓰는 것은 아니다.

이름 둘이 가리키는 대상도 다르다. /dev/vda 가 게스트 Linux 에 보이는 블록 장치를 가리키고, virtio-blk 는 그 가상 블록 장치를 제어하는 게스트 커널 드라이버를 가리킨다. 네트워크 쪽의 ens3virtio-net 이 같은 짝이다.

virtio-blk 가 요청을 virtqueue 에 게시한다

게스트 블록 계층에서 /dev/vda 의 특정 위치에 데이터를 WRITE 하라는 요청이 내려오면, virtio-blk 드라이버가 그것을 Virtio 블록 요청으로 구성해 virtqueue 에 게시한다. 네트워크에서 TCP/IP 스택이 virtio-net 을 거쳐 virtqueue 로 내려가던 구조가 저장소 쪽에서도 반복된다.

게스트 메모리 안에 I/O 버퍼가 있고 virtqueue 의 디스크립터가 그 버퍼를 가리키기 때문에, virtqueue 를 데이터가 흘러가는 관으로 보면 부정확하다. 게시되는 요청에는 OperationWRITE 인지, Sector 가 어디인지, Data Buffer 가 게스트 메모리의 어디인지가 들어간다. 뜻을 풀면 /dev/vda 의 이 위치에 게스트 메모리의 이 버퍼를 기록하라는 요청이다.

게스트 사용자 공간의 PostgreSQL / Keycloak 에서 시작한 write() 가 게스트 커널의 VFS, ext4 / XFS, Guest Page Cache, Guest Block Layer, /dev/vda, virtio-blk Frontend 를 차례로 지나 virtqueue 에 실리는 흐름도. Guest Page Cache 에서 Guest Block Layer 로 내려가는 화살표에 writeback 이, Guest Block Layer 에서 /dev/vda 로 가는 화살표에 WRITE / FLUSH 가 붙어 있고, 맨 아래 virtqueue 바로 밑이 VM Boundary 라 이 그림은 거기서 끝난다.

완료는 같은 virtqueue 로 돌아온다

WRITE 요청은 아래로 내려가고 완료는 반대 방향으로 올라온다. NVMe 가 완료를 올리면 NVMe 드라이버와 호스트 블록 계층, QEMU 와 백엔드를 지나 virtqueue 완료가 된다. virtio-blk 가 그것을 받아 게스트 블록 계층으로 올린다. 그래서 virtqueue 는 요청을 내려보내는 통로이면서 완료를 올려보내는 통로이기도 하다.

네트워크 가상화와 짝이 되는 이름들

제3부의 네트워크 경로를 먼저 읽었다면 이름이 하나씩 대응된다.

Network Storage
virtio-net virtio-blk
packet block I/O request
TX/RX virtqueue I/O virtqueue
TAP / network backend QEMU block backend
Linux Bridge/Route Host filesystem/block stack
Physical NIC Physical SSD/NVMe
Guest TCP/IP Stack Guest VFS/Filesystem/Block Layer
send/recv read/write/fsync

SSOT 는 이 표를 학습용 대응으로 두었고, 두 열의 각 요소가 1:1 로 같은 종류라는 데까지는 말하지 않는다.

확인하지 못한 것과 범위 밖으로 둔 것

SSOT 는 이 부에서 다룰 것과 미룰 것을 먼저 갈라 두었다. qcow2 내부의 L1/L2 테이블과 blk-mq 의 tag allocator, NVMe 의 submission/completion 큐는 필요할 때 별도 문서에서 다루기로 했다.

이 경로를 이 호스트에서 재 본 값은 없다. 게스트의 /dev/vda 가 호스트에서 무엇에 붙어 있는지는 SSOT 가 적어 둔 열린 질문 가운데 첫 번째인데, virsh domblklist 로 그 대응을 읽은 출력이 아직 없다. 게스트 쪽 파일시스템으로 남아 있는 것은 SSOT 의 실험대 배치 절이 vda 의 루트를 ext4 로 적어 둔 2026-09-03 스냅샷 하나뿐이고, 이 글은 ext4XFS 가 같은 계층을 지난다는 데까지만 말한다. 요청 수나 지연을 재려면 게스트의 fsync() 지연과 호스트 저장소 지연을 같은 시간축에서 함께 보는 관찰이 먼저 돌아야 한다.