Ryotta's Basic

NAND
💾 NAND 검증완료

VFS FS PageCache BlockIO

리눅스 스토리지 스택 상세 분석

VFS · File System · Page Cache · Block I/O Layer

애플리케이션이 파일을 읽고 쓸 때, 그 요청은 여러 커널 계층을 거쳐 저장장치에 도달합니다. 본 문서는 그 핵심 네 계층 — VFS(가상 파일시스템), 파일시스템(FS), 페이지 캐시(Page Cache), 블록 I/O 레이어(Block I/O Layer) — 의 역할과 동작 원리를 분석합니다.

VFS는 경로 탐색과 파일 객체 생성을 맡고, 파일시스템은 이름을 논리 블록 주소로 바꿉니다. 페이지 캐시는 read/write를 DRAM에서 흡수하고, 블록 I/O 레이어는 bio와 request를 병합해 장치 큐로 내려보냅니다. buffered I/O는 이 경로를 모두 지나가지만 direct I/O는 페이지 캐시를 우회하므로 성능과 일관성의 절충점이 달라집니다.

전체 구조 개요

네 계층은 위에서 아래로 VFS → 파일시스템 → (페이지 캐시) → 블록 I/O 레이어 순으로 쌓여 있습니다. 위 계층은 파일·바이트 단위로 추상적으로 동작하고, 아래로 내려갈수록 블록(LBA) 단위의 구체적인 I/O로 변환됩니다. 페이지 캐시는 파일시스템과 블록 계층 사이에서 완충 역할을 하여 불필요한 디스크 접근을 차단합니다.

nand_0015_vfs_fs_pagecache_blockio

그림 1. OS 스토리지 스택의 네 계층 구조와 데이터 흐름

1. VFS (Virtual File System)

VFS는 서로 다른 파일시스템들을 하나의 공통 인터페이스로 추상화하는 커널 계층입니다. 덕분에 애플리케이션은 ext4든 XFS든 NFS든 파일시스템 종류를 알 필요 없이 동일한 시스템 콜(open/read/write/close)로 파일을 다룰 수 있습니다.

VFS FS PageCache BlockIO

그림 2. VFS의 네 가지 핵심 객체와 관계

VFS 핵심 객체

프로세스가 파일을 열면 file 객체가 생성되고, dentry로 경로를 해석해 inode를 찾아 실제 파일을 식별합니다. inode는 데이터 블록의 물리 위치를 가리키며, superblock이 그 파일시스템 전체를 관장합니다.

dcache에 남는 dentry는 RAM에만 존재하는 경로 탐색 캐시이고, inode는 파일의 크기·권한·시간 정보와 블록 위치를 담는 실체입니다.

VFS의 이점

  • 투명성 — 애플리케이션은 파일시스템 종류를 몰라도 동일한 시스템 콜을 사용합니다.

  • 확장성 — 새 파일시스템도 VFS 인터페이스만 구현하면 커널에 통합됩니다.

  • 통합 — 디스크 FS, 네트워크 FS(NFS), 가상 FS(/proc, tmpfs)를 모두 같은 방식으로 취급합니다.

2. 파일시스템 (File System)

파일시스템은 VFS가 정의한 인터페이스의 실제 구현체로, 파일이라는 추상적 개념을 저장장치의 논리 블록(LBA)에 매핑합니다. 디렉터리 구조, inode 테이블, 데이터 블록 배치, 그리고 일관성을 위한 저널링을 담당합니다.

파일시스템의 핵심 역할

  • 파일↔블록 매핑 — 파일의 바이트 오프셋을 저장장치의 논리 블록 번호로 변환합니다.

  • 메타데이터 관리 — inode, 디렉터리 엔트리, 비트맵 등으로 파일 구조를 유지합니다.

  • 저널링(Journaling) — 쓰기 작업을 먼저 저널에 기록한 뒤 본 영역에 반영하여, 갑작스러운 전원 장애에도 일관성을 보장합니다.

  • 공간 할당 — 빈 블록을 추적하고 파일 확장 시 효율적으로 할당합니다(extent, 비트맵 등).

주요 파일시스템 비교

특히 F2FS는 NAND 플래시의 ‘덮어쓰기 불가·순차 쓰기 선호’ 특성에 맞춰 설계된 로그 구조 파일시스템으로, SSD의 가비지 컬렉션 부담을 줄이는 방향으로 동작합니다.

3. 페이지 캐시 (Page Cache)

페이지 캐시는 최근 접근한 파일 데이터를 DRAM에 보관하는 커널의 캐시입니다. 읽기는 캐시에서 즉시 반환하여 디스크 접근을 건너뛰고, 쓰기는 일단 캐시에 기록한 뒤 나중에 모아서 디스크에 반영(write-back)합니다.

VFS FS PageCache BlockIO

그림 3. 페이지 캐시의 읽기(히트/미스)와 쓰기(write-back) 동작

읽기 동작

  • 캐시 히트 — 요청한 데이터가 캐시에 있으면 디스크 접근 없이 즉시 반환합니다(수십 ns).

  • 캐시 미스 — 캐시에 없으면 디스크에서 읽어 캐시에 적재한 뒤 반환하며, 이후 접근은 히트가 됩니다. read-ahead로 이어질 데이터를 미리 선반입하기도 합니다.

쓰기 동작 (Write-back)

  • write() 호출 시 데이터를 페이지 캐시에 기록하고 즉시 반환합니다(디스크 기록을 기다리지 않음).

  • 해당 페이지를 dirty로 표시하고, 커널의 writeback 스레드가 주기적으로 디스크에 기록합니다.

  • fsync()/sync 호출 시 dirty page를 강제로 디스크에 flush하여 영속성을 보장합니다.

효과와 위험

  • 효과 — 반복 읽기는 캐시 히트로 즉시 반환되고, 작은 쓰기는 write-back으로 모였다가 내려가므로 디스크 I/O 횟수와 지연이 줄어듭니다.

  • 위험 — dirty page는 fsync()/fdatasync() 전까지 영속적이지 않으며, 전원 장애가 나면 아직 기록되지 않은 데이터가 사라질 수 있습니다.

  • 회수 — 메모리 압박이 생기면 클린 페이지부터 회수되고, dirty 페이지는 먼저 writeback된 뒤 reclaim됩니다.

  • 예외 경로 — 순수한 낮은 지연이나 예측 가능한 flush가 중요하면 direct I/O나 O_DIRECT를 선택할 수 있습니다.

4. 블록 I/O 레이어 (Block I/O Layer)

블록 I/O 레이어는 상위 계층의 I/O 요청을 bio 구조체로 표현하여 블록 장치에 전달합니다. 이 과정에서 인접한 요청을 병합·정렬하고, 멀티큐(blk-mq) 구조로 다중 코어의 요청을 병렬 처리합니다.

VFS FS PageCache BlockIO

그림 4. 블록 I/O 레이어의 요청 병합·정렬과 멀티큐(blk-mq) 구조

핵심 역할

  • 추상화 — 모든 I/O 요청을 bio 구조체로 통일해 표현하므로, 어떤 블록 장치든 동일한 인터페이스로 다룹니다.

  • 병합(Merge) — 연속된 LBA에 대한 요청을 하나로 합쳐 I/O 횟수를 줄입니다.

  • 정렬(Sort) — HDD에서는 헤드 이동을 줄이기 위해 LBA 순으로 정렬하지만, SSD는 탐색 시간이 없어 정렬을 최소화하거나 생략합니다.

  • 멀티큐(blk-mq) — 코어별 소프트웨어 큐와 장치의 하드웨어 큐(NVMe SQ)를 2단계로 두어, 단일 큐의 락 경쟁 병목을 제거하고 고IOPS로 확장합니다.

I/O 스케줄러

블록 레이어는 요청 처리 순서를 결정하는 I/O 스케줄러와 연동합니다. 장치 특성에 맞는 스케줄러를 선택할 수 있습니다.

SSD와 NVMe에서는 병합 비용이 낮아 none이나 mq-deadline 같은 가벼운 정책이 자주 쓰이고, HDD에서는 순서 재배치의 이득이 더 큽니다.

bio와 request

bio는 연속된 I/O 구간을 표현하는 단위이고, 블록 계층은 여러 bio를 request로 묶어 장치 드라이버에 넘깁니다. blk-mq는 소프트웨어 staging queue와 하드웨어 dispatch queue를 분리해 병렬성을 높입니다.

5. 종합 — read()/write() 전체 경로

읽기 경로

애플리케이션 read() → VFS가 file/inode로 요청 해석 → 파일시스템이 파일 오프셋을 논리 블록으로 변환 → 페이지 캐시 확인(히트면 즉시 반환) → 미스면 블록 I/O 레이어가 bio 생성 → I/O 스케줄러·드라이버를 거쳐 저장장치에서 읽기 → 페이지 캐시에 적재 후 애플리케이션에 반환.

read(fd)

→ VFS가 fd → struct file → inode → i_mapping(address_space) 획득

→ 파일 오프셋을 페이지 인덱스로 변환

→ address_space의 페이지 트리에서 조회 ← 여기가 "페이지 캐시 주소"로 찾는 지점 (VFS)

├─ 있으면 (히트): 즉시 반환, FS·블록 레이어 안 감

└─ 없으면 (미스): 파일시스템 호출 ↓

쓰기 경로

애플리케이션 write() → VFS·파일시스템을 거쳐 페이지 캐시에 기록하고 즉시 반환(dirty 표시) → writeback 스레드가 주기적으로 또는 fsync 시 dirty page를 모아 블록 I/O 레이어로 전달 → 병합·정렬 후 드라이버를 거쳐 저장장치에 기록 → 저널링으로 일관성 보장.

buffered I/O, direct I/O, mmap 비교

방식 주 경로 특징
buffered I/O VFS → FS → Page Cache → Block I/O 캐시 히트가 빠르고 작은 쓰기를 묶기 쉽습니다.
direct I/O VFS → FS → Block I/O page cache를 우회해 캐시 오염을 줄이지만, 앱이 지연과 정렬을 직접 감당해야 합니다.
mmap VFS → Page Cache 파일 페이지를 주소 공간에 직접 매핑해 읽기 접근을 단순화합니다.

계층별 통신 단위 요약

핵심 정리 — 위 계층(VFS·FS)은 파일·바이트 단위로 추상적으로 동작하고, 아래 계층(블록 I/O)은 블록(LBA) 단위로 구체화됩니다. 페이지 캐시가 그 사이에서 완충 역할을 하여 실제 디스크 I/O를 크게 줄이는 것이 전체 스택 성능의 핵심입니다.

객체 역할 주요 정보
superblock 마운트된 파일시스템 전체를 표현 FS 종류, 블록 크기, 마운트 옵션
inode 파일/디렉터리 하나의 메타데이터 크기, 권한, 타임스탬프, 데이터 블록 위치
dentry 디렉터리 엔트리 (이름↔inode 연결) 경로 탐색 결과 캐시 (예: /home/user/a.txt)
file 프로세스가 연 파일의 인스턴스 현재 오프셋, 접근 모드, fd와 연결
파일시스템 특징 적합 용도
ext4 성숙·안정, extent 기반, 저널링 범용 리눅스 기본
XFS 대용량·고병렬 I/O 강점, 확장성 우수 서버, 대용량 스토리지
F2FS 로그 구조, NAND 순차 쓰기 최적화 모바일/SSD 플래시
Btrfs CoW, 스냅샷, 체크섬, 풀링 스냅샷·데이터 무결성 중시
측면 내용
효과 반복 읽기 가속, 작은 쓰기 병합 → 디스크 I/O 횟수 감소, 체감 성능 대폭 향상
위험 write-back은 디스크 기록 전 전원 장애 시 데이터 유실 가능 → 저널링·fsync로 보완
메모리 압박 시 클린 페이지를 우선 회수(reclaim), dirty 페이지는 먼저 기록 후 회수
스케줄러 특징 SSD 적합성
none 스케줄링 거의 없이 즉시 전달 NVMe SSD 기본 권장
mq-deadline 지연시간 상한 보장, 가벼운 정렬 혼합 워크로드
kyber 지연시간 목표 기반 적응 제어 고성능 NVMe
BFQ 공정성 중심, 데스크탑 반응성 오버헤드 큼
계층 주 통신 단위 핵심 책임
VFS 파일 / 객체 공통 인터페이스 추상화
파일시스템 파일 ↔ 논리 블록 매핑, 메타데이터, 저널링
페이지 캐시 페이지 (보통 4KB) 읽기/쓰기 버퍼링
블록 I/O 블록 (LBA) bio 병합·정렬, 멀티큐

6. 장단점

장점

  • VFS가 파일시스템 종류를 숨겨서 애플리케이션은 open/read/write/close만으로 다양한 저장소를 다룰 수 있습니다.

  • 페이지 캐시는 DRAM에서 반복 접근을 흡수하고 write-back으로 작은 쓰기를 병합해 체감 성능을 높입니다.

  • blk-mq는 코어별 소프트웨어 큐와 장치 하드웨어 큐를 분리해 NVMe 같은 고IOPS 장치에서 병렬성을 살립니다.

  • F2FS 같은 플래시 친화적 파일시스템과 결합하면 NAND의 순차 쓰기 성향을 더 잘 활용할 수 있습니다.

한계

  • buffered I/O는 flush 전까지 데이터가 영속적이지 않아서 전원 장애에 취약합니다.

  • dirty page가 많아지면 writeback과 reclaim이 겹치며 지연이 튈 수 있습니다.

  • HDD에 유리한 정렬 정책은 SSD/NVMe에서는 오히려 오버헤드가 될 수 있습니다.

  • direct I/O는 page cache를 우회하지만, 애플리케이션이 정렬과 일관성 관리를 더 많이 떠안습니다.

7. 관련 기술

8. 핵심 정리

VFS는 파일 이름을 공통 인터페이스로 묶고, 파일시스템은 그 이름을 블록 주소로 번역합니다. 페이지 캐시는 DRAM에서 읽기와 쓰기를 받아 성능을 끌어올리지만, 실제 영속성은 fsync와 writeback 시점에 확보됩니다. 블록 I/O와 blk-mq는 bio를 병합하고 여러 큐로 분산해 장치 병렬성을 살립니다. 그래서 이 스택은 단순한 경로가 아니라, 캐시 일관성과 장치 효율 사이의 균형 장치라고 볼 수 있습니다.