Linux 파일 시스템 아키텍처: VFS, inode 및 스토리지 심층 분석

발행: (2026년 1월 10일 오후 12:21 GMT+9)
14 min read
원문: Dev.to

Source: Dev.to

번역을 진행하려면 실제 번역할 텍스트(본문 내용)를 제공해 주시겠어요?
본문을 알려주시면 원본 포맷과 코드 블록, URL 등을 그대로 유지하면서 한국어로 번역해 드리겠습니다.

가상 파일 시스템 (VFS)

“모든 것이 파일이다”라는 개념은 VFS (Virtual File System) 라는 기술 덕분에 가능해집니다.

VFS는 추상화 계층으로 작동합니다. 데이터와 장치를 포함한 모든 컴퓨터 자원에 대해 통합된 입출력 인터페이스를 제공합니다. VFS 덕분에 사용자(및 애플리케이션)는 물리적 매체에 대해 신경 쓰지 않고 모든 것을 “파일”처럼 다룰 수 있습니다.

VFS가 없었다면 파일을 저장할 때마다 디스크 포맷 유형을 의식해야 했을 것입니다.

vfs

Source:

파일 시스템의 다양성

VFS가 통합 인터페이스를 제공하지만 실제 데이터는 다양한 종류의 매체에 존재합니다. 이제는 단순히 하드 드라이브만을 의미하지 않습니다.

Docker로 파일 시스템 탐색하기

현대 환경에 실제로 존재하는 파일 시스템이 무엇인지 살펴보겠습니다. 다양한 파일 시스템 유형을 가장 쉽게 확인할 수 있는 방법이 Docker 컨테이너(Rocky Linux 9)를 사용하는 것입니다.

# 시연을 위해 마운트 작업을 허용하는 --privileged 옵션을 사용합니다
docker run -it --privileged --name fs-lab rockylinux:9 /bin/bash

디스크 사용량 및 유형 확인

# -T: 파일 시스템 유형 출력 (ext4, xfs, tmpfs 등)
# -h: 사람이 읽기 쉬운 크기 단위
[root@container /]# df -Th
Filesystem     Type     Size  Used Avail Use% Mounted on
overlay        overlay   59G   13G   44G  23% /
tmpfs          tmpfs     64M     0   64M   0% /dev
shm            tmpfs     64M     0   64M   0% /dev/shm
/dev/vda1      ext4      59G   13G   44G  23% /etc/hosts

우리는 이를 네 가지 주요 유형으로 구분할 수 있습니다:

유형 1 – 디스크 기반 파일 시스템 (영구)

  • 예시: ext4, xfs, btrfs
  • 역할: OS 핵심 파일, 사용자 데이터(/home) 및 로그(/var) 저장.
  • 비고: ext4는 확장 파일 시스템의 네 번째 세대일 뿐입니다.

유형 2 – 메모리 기반 파일 시스템 (휘발성)

  • 예시: tmpfs, ramfs
  • 역할: 임시 파일(/tmp, /run) 저장.
  • 왜? /run은 현재 세션에만 유효한 PID 파일과 소켓을 저장합니다. 이를 물리 디스크에 쓰는 것은 불필요한 오버헤드가 됩니다.

유형 3 – 의사 파일 시스템 (가상)

이 파일들은 영구 저장소에 존재하지 않습니다. 커널이 접근할 때 실시간으로 생성합니다. 파일처럼 보이지만 실제로는 커널에 대한 창입니다.

  • 예시: /proc, /sys, /sys/fs/cgroup, /dev/pts
예시역할
/proc클래식 인터페이스. /proc/cpuinfo를 읽으면 커널이 CPU에 대한 텍스트 정보를 동적으로 생성합니다.
/sys (sysfs)연결된 장치와 드라이버를 구조화된 형태로 보여줍니다. 때때로 이 파일에 쓰기를 통해 장치 설정(예: 화면 밝기)을 변경할 수 있습니다.
/sys/fs/cgroup컨테이너 기술의 기반. 프로세스에 대한 리소스 제한(CPU/메모리)을 관리합니다.
/dev/pts의사 터미널(PTY)을 관리합니다. 터미널이나 SSH 세션을 열 때마다 가상 파일(예: /dev/pts/0)이 생성되어 I/O를 처리합니다.

유형 4 – 계층형 파일 시스템 (Union Mount)

이것이 Docker 뒤에 숨은 마법입니다. 이러한 파일 시스템은 여러 디렉터리(레이어)를 쌓아 하나의 통합 파일 시스템처럼 보여줍니다.

  • 예시: overlay, aufs
  • 역할: 읽기 전용 베이스 레이어(OS 이미지)와 쓰기 가능한 상위 레이어(컨테이너 변경 사항)를 병합합니다.

심층 분석: OverlayFS

OverlayFS는 중복 제거와 빠른 시작을 위해 특정 구조를 사용합니다.

  • Lower Dir (Base): Docker 이미지. 읽기 전용이며 모든 컨테이너가 공유합니다.
  • Upper Dir: 특정 컨테이너를 위한 쓰기 가능한 레이어. 처음에는 비어 있습니다. 변경 사항은 여기로 복사됩니다(Copy‑on‑Write).
  • Merged Dir: 여러분이 보는 뷰. 상위 레이어가 하위 레이어를 덮어씌웁니다.

overlayfs

“파일”이란 실제로 무엇일까? (Inode)

example.txt 같은 파일명은 사람을 위한 것입니다. Linux는 inode 로 파일을 식별합니다.

Inode 구조

# 더미 파일 만들기
echo "dummy" > example.txt

# 'stat' 로 inode 메타데이터 확인
stat example.txt

샘플 출력

  File: example.txt
  Size: 6           Blocks: 8          IO Block: 4096   regular file
Device: f5h/245d  Inode: 1714604     Links: 1
...

Inode 보기

ls -i example.txt
# 출력: 1714604 example.txt

참고: 파일명은 inode에 저장되지 않습니다. 디렉터리 엔트리 안에 inode 번호를 가리키는 형태로 존재합니다.

언제 Inode가 바뀌나요?

inode 변경을 이해하면 “왜 내 로그 모니터링이 멈췄지?” 같은 문제를 디버깅하는 데 도움이 됩니다.

cp vs. mv

명령어inode에 미치는 영향
cp (복사)새 파일을 생성새 inode
mv (이동/이름 변경)파일을 이름만 바꿈같은 inode
# cp는 inode를 바꾼다
ls -i example.txt      # 1714604
cp example.txt copy.txt
ls -i copy.txt         # 1714664 (NEW)

# mv는 inode를 유지한다
mv copy.txt moved.txt
ls -i moved.txt        # 1714664 (SAME)

sed -i 함정

sed -i(제자리 편집)나 Vim 같은 편집기는 종종 안전 저장 전략을 사용합니다:

  1. 임시 파일을 만든다(새 inode).
  2. 변경 내용을 임시 파일에 쓴다.
  3. 임시 파일을 원본 위에 rename한다.
# 파일 만들기
echo "hello" > config.txt
ls -i config.txt       # 1714736

# sed로 편집
sed -i 's/hello/world/' config.txt

# Inode가 바뀌었다!
ls -i config.txt       # 1714737

왜 중요한가?
inode 기반으로 로그 파일을 감시한다면(예: inotify 사용) 회전이나 제자리 편집이 발생했을 때 감시 대상이 다른 inode이 되므로 파일을 놓치게 됩니다.

추가 vs. 편집: Vim 동작

1. 데이터 추가 (>>)

추가는 기존 블록에 데이터를 덧붙이므로 inode는 그대로 유지됩니다.

# 파일 만들기
touch target.txt
ls -i target.txt       # 1714736

# 데이터 추가
echo "append" >> target.txt

# Inode는 변하지 않음
ls -i target.txt       # 1714736

2. Vim으로 편집

Vim은 기본적으로 새 임시 파일에 쓰고 나서 rename하기 때문에 inode가 바뀝니다.

# Vim으로 편집하고 저장 (:wq)
vi target.txt

# Inode가 바뀌었다!
ls -i target.txt       # 1714739

경고: tail -Finotify 같은 도구는 이 “inode 회전”을 처리할 수 있어야 합니다. 그렇지 않으면 삭제된 파일을 계속 감시하게 됩니다.

하드 링크 vs. 심볼릭 링크

링크 유형가리키는 대상inode 관계
하드 링크원본 파일과 동일한 inode동일 inode
심볼릭 링크다른 파일에 대한 경로다른 inode (링크 자체)

하드 vs. 심볼릭 링크 일러스트레이션

검증

echo "Hello" > original.txt
ln original.txt hard.txt      # Hard link
ln -s original.txt sym.txt    # Symbolic link

ls -li
# Example output:
# 1714669 -rw-r--r-- 2 user group 6 Jan 10 12:00 hard.txt
# 1714669 -rw-r--r-- 2 user group 6 Jan 10 12:00 original.txt  # Same inode!
# 1714670 lrwxrwxrwx 1 user group 12 Jan 10 12:00 sym.txt -> original.txt  # Different inode

Source:

프로세스 뷰: 파일 디스크립터

개발자는 일반적으로 파일 디스크립터(FD) 를 통해 파일과 상호작용하며, 직접 inode와 다루지는 않습니다.

프로세스가 파일을 열면 커널은 해당 프로세스의 열린 파일 테이블을 인덱싱하는 비음수 정수(FD)를 할당합니다.

Go 예제

package main

import (
	"fmt"
	"os"
)

func main() {
	// 1. 파일 열기 (커널에 시스템 콜)
	file, err := os.Open("example.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close() // 사용이 끝나면 항상 닫기

	// 2. 파일 디스크립터 가져오기
	fd := file.Fd()

	fmt.Printf("File Name: %s\n", file.Name())
	fmt.Printf("File Descriptor: %d\n", fd)
}

결과

File Name: example.txt
File Descriptor: 3

왜 “3”인가?

Linux 프로세스는 이미 세 개의 표준 파일 디스크립터가 열려 있는 상태로 시작합니다:

FD의미
0표준 입력(stdin)
1표준 출력(stdout)
2표준 오류(stderr)

커널은 가장 낮은 사용 가능한 번호를 항상 할당하므로, 사용자가 처음으로 연 파일은 디스크립터 3을 받게 됩니다.

File descriptor table illustration

Source:

파일 디스크립터 고갈

일반적인 운영 환경의 악몽은 “too many open files” 오류입니다. Unix에서는 모든 것이 파일(소켓, 파이프, 디바이스 노드 등)로 취급되기 때문에, 많은 자원이 파일 디스크립터를 사용합니다:

  • 데이터베이스 연결
  • HTTP 요청(TCP 소켓)
  • 로그 파일

트래픽이 많은 서비스가 제한(ulimit -n)보다 더 많은 디스크립터를 열면, 새로운 연결을 받아들이거나 로그를 기록하는 데 실패하기 시작합니다.

완화 팁

  1. 제한을 높이기 (ulimit -n 또는 /etc/security/limits.conf 편집).
  2. 사용하지 않는 디스크립터를 즉시 닫기.
  3. 데이터베이스 및 HTTP 클라이언트에 연결 풀링 사용.
  4. 디스크립터 사용량 모니터링 (lsof -p <pid> 또는 cat /proc/<pid>/fd).

Solutions

  • 누수 수정: 항상 Close()가 호출되도록 보장하세요 (defer를 Go에서 사용).
  • 제한 조정: 기본 제한(보통 1024)은 서버에 너무 낮습니다. /etc/security/limits.conf에서 늘리세요.

결론

Linux 파일 시스템의 층을 하나씩 살펴보면, 그것이 얼마나 아름답게 조율된 구조인지 알 수 있습니다:

  • User Space: 파일 이름과 파일 디스크립터를 처리합니다.
  • VFS Layer: 커널은 하드웨어 간 차이를 추상화합니다.
  • Physical Layer: 데이터는 디스크(ext4), RAM(tmpfs)에 존재하거나 실시간으로 계산됩니다( /proc).

이러한 계층을 이해하는 것은 성능 문제를 디버깅할 때(예: “디스크 I/O 문제인가, 의사 파일 시스템 병목 현상인가?”) 혹은 컨테이너 작업 시(예: “내 이미지가 어떻게 작게 유지되는가?”) 매우 중요합니다.

Back to Blog

관련 글

더 보기 »

리눅스

Linux란 무엇인가? 데스크톱 컴퓨터나 어떤 종류의 컴퓨팅 디바이스를 사용해 본 적이 있다면, 소프트웨어와 직접 상호작용했으며 그 소프트웨어는 반드시 통신해야 합니다.