162 lines
13 KiB
Markdown
162 lines
13 KiB
Markdown
---
|
|
id: 3b05c1f0-13e9-414a-97a6-2078fb4bab26
|
|
kind: CONCEPT
|
|
slug: guest-block-io-path-to-virtqueue
|
|
title: Guest 의 write() 가 virtqueue 에 실리기까지 — VFS · Filesystem · Page Cache · Block Layer · virtio-blk
|
|
topic: storage-virtualization
|
|
topicName: 스토리지 가상화
|
|
project: virtualization
|
|
status: 초안
|
|
basisVersion: QEMU/KVM 의 virtio-blk frontend 와 virtqueue · 게스트가 ext4 나 XFS 에 buffered I/O 로 쓰는 경우 · SSOT 가 커널과 QEMU 버전을 적지 않았다
|
|
studio: "https://hyeonworks.com/studio/documents/3b05c1f0-13e9-414a-97a6-2078fb4bab26/edit"
|
|
assets:
|
|
- key: guest-block-io-to-virtqueue
|
|
file: ../../../final/assets/diagrams/guest-block-io-to-virtqueue/guest-block-io-to-virtqueue.svg
|
|
sourceRevision: no-commit · 밖에서 반입한 문서 한 편. 고정할 저장소 리비전이 없다
|
|
source:
|
|
- 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 가 파일을 블록 공간에 배치하고, 페이지 캐시가 dirty page 로 받아 두었다가 나중에 writeback 한다. 이어서 블록 I/O 계층이 그 내용을 READ 와 WRITE, FLUSH 요청으로 바꾸고, 마지막에 virtio-blk 드라이버가 Virtio 블록 요청을 만들어 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 라는 같은 구조를 패킷 쪽에서 설명한 글이다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
## 애플리케이션은 저장장치를 직접 다루지 않는다
|
|
|
|
가상 머신 안의 PostgreSQL 이나 Keycloak 같은 프로세스는 SSD 나 `virtio-blk` 를 직접 다루지 않는다. 파일에 데이터를 기록할 때 개념적으로 부르는 것은 시스템 콜 하나다.
|
|
|
|
```c label="게스트 애플리케이션이 파일에 기록할 때 부르는 system call"
|
|
write(fd, buffer, size);
|
|
```
|
|
|
|
파일과 관련된 시스템 콜은 이것 말고도 여럿이다.
|
|
|
|
```text label="대표적인 파일 관련 system call"
|
|
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` 같은 디렉터리 구조를 모른다. 저장장치가 보는 것은 번호가 붙은 블록 단위 공간인데 사람과 프로그램이 보는 것은 파일과 디렉터리 계층이다. 그 둘을 잇는 계층이 `ext4` 나 `XFS` 같은 파일시스템이다. 파일시스템이 관리하는 것은 파일 이름과 디렉터리 구조, 파일 크기, 소유자와 권한, 시각 정보, `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` 의 특정 위치에 `READ` 나 `WRITE`, `FLUSH` 를 거는 요청이 된다. 대표 요청은 넷이다 — `READ`, `WRITE`, `FLUSH`, `DISCARD`.
|
|
|
|
이 계층 안에는 `bio` 와 `request`, `queue`, `blk-mq` 가 실제로 있다. SSOT 는 `blk-mq` 의 tag allocator 같은 내부 구현을 별도 문서로 미뤘고 이 글도 이름까지만 적는다.
|
|
|
|
## /dev/vda 는 게스트가 보는 이름이다
|
|
|
|
물리 머신에서는 `/dev/sda` 나 `/dev/nvme0n1` 같은 블록 장치가 보인다. `virtio-blk` 를 쓰는 가상 머신에서는 흔히 `/dev/vda` 와 `/dev/vdb` 처럼 보이고, 게스트 Linux 는 그것을 하나의 블록 장치로 인식한다. 게스트 안에서 `lsblk` 를 실행하면 그 장치와 파티션이 나온다.
|
|
|
|
```bash label="게스트가 보는 block device"
|
|
lsblk
|
|
```
|
|
|
|
```text label="SSOT 가 든 출력 예시. 이 호스트에서 실행한 결과가 아니다"
|
|
NAME SIZE TYPE MOUNTPOINT
|
|
vda 100G disk
|
|
├─vda1 1G part /boot
|
|
└─vda2 99G part /
|
|
```
|
|
|
|
게스트에 이렇게 보여도 그 장치가 호스트의 실제 SSD 인지는 게스트 안에서 알 수 없다. 이 장비의 `/dev/vda` 가 무엇에 붙어 있는지도 아직 확인하지 않았다. 게스트가 보는 것은 `/dev/vda` 에서 파티션으로, 파티션에서 파일시스템으로, 파일시스템에서 마운트 지점으로 이어지는 연결까지다. `cd /var/lib/postgresql` 로 디렉터리를 옮기는 동작은 파일시스템 세계를 지나고, `lsblk` 의 `vda` 는 블록 장치 세계를 보여 준다.
|
|
|
|
이름 둘이 가리키는 대상도 다르다. `/dev/vda` 가 게스트 Linux 에 보이는 블록 장치를 가리키고, `virtio-blk` 는 그 가상 블록 장치를 제어하는 게스트 커널 드라이버를 가리킨다. 네트워크 쪽의 `ens3` 와 `virtio-net` 이 같은 짝이다.
|
|
|
|
## virtio-blk 가 요청을 virtqueue 에 게시한다
|
|
|
|
게스트 블록 계층에서 `/dev/vda` 의 특정 위치에 데이터를 `WRITE` 하라는 요청이 내려오면, `virtio-blk` 드라이버가 그것을 Virtio 블록 요청으로 구성해 `virtqueue` 에 게시한다. 네트워크에서 TCP/IP 스택이 `virtio-net` 을 거쳐 `virtqueue` 로 내려가던 구조가 저장소 쪽에서도 반복된다.
|
|
|
|
게스트 메모리 안에 I/O 버퍼가 있고 `virtqueue` 의 디스크립터가 그 버퍼를 가리키기 때문에, `virtqueue` 를 데이터가 흘러가는 관으로 보면 부정확하다. 게시되는 요청에는 `Operation` 이 `WRITE` 인지, `Sector` 가 어디인지, `Data Buffer` 가 게스트 메모리의 어디인지가 들어간다. 뜻을 풀면 `/dev/vda` 의 이 위치에 게스트 메모리의 이 버퍼를 기록하라는 요청이다.
|
|
|
|

|
|
|
|
## 완료는 같은 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 가 적어 둔 열린 질문 가운데 첫 번째인데, 아직 확인하지 않았다. 게스트의 파일시스템이 `ext4` 인지 `XFS` 인지도 SSOT 어디에도 없어서, 이 글은 두 파일시스템이 같은 계층을 지난다는 데까지만 말한다. 요청 수나 지연을 재려면 게스트의 `fsync()` 지연과 호스트 저장소 지연을 같은 시간축에서 함께 보는 관찰이 먼저 돌아야 한다.
|
|
|
|
<!-- body:end -->
|