디스크는 그저 거대한 블록(Block)들의 배열일 뿐이다. 파일 시스템의 목표는 이 수많은 블록들을 관리하여 사용자가 편리하게 파일과 디렉터리를 쓸 수 있게 만드는 것이다. 어떻게 맨땅(Raw Disk)에 파일 시스템을 구축하는지, 그 구조부터 살펴보자.
1. 디스크 구조 잡기 (On-Disk Structures)
먼저 디스크를 블록 단위로 나눈다. OSTEP에서는 블록 크기를 4KB로 가정한다. 총 64개의 블록이 있다고 칠 때, VSFS는 이를 용도별로 구획한다.

- 데이터 영역 (Data Region): 파일의 실제 내용이 저장되는 곳이다. 디스크의 대부분을 차지한다.
- 아이노드 테이블 (Inode Table): 파일들의 메타데이터(크기, 권한, 소유자 등)를 저장하는 아이노드(inode)들이 모여 있는 곳이다.
- 비트맵 (Bitmaps): 빈 공간 관리를 위한 지도다.
- Data Bitmap: 어떤 데이터 블록이 사용 중인지(1) 또는 비어있는지(0) 표시.
- Inode Bitmap: 어떤 아이노드가 사용 중인지 표시.
- 슈퍼블록 (Superblock): 파일 시스템 전체의 정보(총 블록 수, 아이노드 테이블 시작 위치, 매직 넘버 등)를 담고 있다. 마운트 할 때 가장 먼저 읽는 곳이다.
2. 핵심 자료구조: 아이노드 (The Inode)
아이노드(Index Node)는 파일 시스템의 심장이다. 파일 하나당 하나의 아이노드가 할당된다. (이름은 없고 번호로 식별된다.)
아이노드에는 무엇이 들어있나?
- 메타데이터: 파일 종류(일반/디렉터리), 크기, 권한(rwx), 소유자, 시간 정보(mtime 등), 링크 카운트 등.
- 데이터 위치 포인터: 파일의 실제 내용이 담긴 데이터 블록의 위치를 가리키는 주소들.
큰 파일은 어떻게 처리하나? (The Multi-Level Index)
아이노드의 크기는 보통 128바이트나 256바이트로 작다. 여기에 수 기가바이트짜리 파일의 모든 블록 주소를 다 넣을 수는 없다. 그래서 다단계 인덱스(Multi-Level Index) 방식을 쓴다.

- 직접 포인터 (Direct Pointers): 처음 12개 정도의 포인터는 실제 데이터 블록을 직접 가리킨다. 작은 파일은 이것만으로 충분하며 매우 빠르다.
- 간접 포인터 (Indirect Pointer): 파일이 커지면 쓰인다. 이 포인터가 가리키는 블록에는 데이터가 아니라 "데이터 블록들의 주소 목록"이 들어있다.
- 이중 간접 포인터 (Double Indirect Pointer): 더 큰 파일을 위해, "포인터를 가리키는 포인터 블록"을 가리킨다.
- 삼중 간접 포인터 (Triple Indirect Pointer): 정말 거대한 파일(TB 단위 등)을 지원한다.
이 구조 덕분에 작은 파일은 빠르게, 큰 파일은 (약간의 오버헤드를 감수하고) 유연하게 지원할 수 있다.
3. 디렉터리 구조 (Directory Organization)
앞서 말했듯 디렉터리도 파일이다. 디렉터리의 데이터 블록에는 무엇이 들어있을까? 단순하게 (파일 이름, 아이노드 번호, 레코드 길이)의 리스트가 저장된다.

struct dir_entry {
char name[256]; // 파일 이름
int inum; // 아이노드 번호
int reclen; // 레코드 길이 (삭제된 공간 관리용)
};
- 파일 찾기: ls를 치면 이 리스트를 순차적으로 읽어서 보여준다. (선형 탐색)
- 파일 삭제: 파일을 지우면 리스트 중간에 빈 공간이 생긴다. 이를 효율적으로 재사용하기 위해 reclen 같은 필드를 둔다.
- 최적화: 파일이 아주 많은 디렉터리의 경우 선형 탐색이 느리므로, B-Tree나 해시 테이블을 사용하기도 한다(XFS, ext4 등).
4. 빈 공간 관리 (Free Space Management)
새 파일을 만들거나 내용을 추가하려면 빈 아이노드와 빈 데이터 블록을 찾아야 한다. VSFS는 비트맵(Bitmap)을 사용한다.
- 0: 비어있음 (Free)
- 1: 사용 중 (Allocated)
비트맵은 공간을 적게 차지하고(4KB 블록 하나로 32,000개의 블록 상태 표현 가능), 연속된 빈 공간을 찾기 쉽다는 장점이 있다.
5. 접근 경로 추적 (Access Paths: Reading and Writing)
파일 시스템 구현을 이해했는지 확인하는 가장 좋은 방법은, 파일 I/O 시 디스크에서 어떤 일이 일어나는지 추적해 보는 것이다. (이 과정의 비효율성을 느끼는 것이 포인트다.)
시나리오 1: 파일 읽기 (/foo/bar 읽기)
파일을 하나 읽으려는데 디스크 헤드는 엄청나게 바쁘게 움직인다.
- 루트 디렉터리 읽기: 루트 inode(보통 2번) 읽기 -> 루트 데이터 읽기 (foo 찾음 -> inode 번호 획득)
- foo 디렉터리 읽기: foo inode 읽기 -> foo 데이터 읽기 (bar 찾음 -> inode 번호 획득)
- bar 파일 열기: bar inode 읽기 -> open() 완료 (fd 반환)
- bar 파일 읽기: bar inode 읽기(블록 위치 확인) -> bar 데이터 읽기 -> bar inode 쓰기(atime 업데이트)
문제점: 파일 하나 읽는데 I/O가 너무 많이 발생한다! 특히 디렉터리 계층을 타고 내려가는 비용이 크다.
시나리오 2: 파일 생성 및 쓰기 (/foo/bar 생성)
쓰기는 더 복잡하다. 메타데이터 업데이트(비트맵, 아이노드)가 추가되기 때문이다.
- foo 디렉터리 읽기: (이름 중복 확인)
- 아이노드 할당: Inode Bitmap 읽기 -> Inode Bitmap 쓰기(1로 변경) -> 새 Inode 쓰기(초기화)
- 디렉터리 갱신: foo 데이터 읽기 -> foo 데이터 쓰기(새 파일 이름 추가) -> foo Inode 쓰기(크기/시간 갱신)
- 실제 데이터 쓰기: Data Bitmap 읽기 -> Data Bitmap 쓰기 -> 데이터 블록 쓰기 -> Inode 쓰기(블록 포인터 추가)
문제점: 쓰기 작업 한번 할 때마다 비트맵, 아이노드, 데이터 블록을 오가며 디스크 I/O가 폭발한다. 이를 "Write Traffic"이라고 한다.
6. 캐싱과 버퍼링 (Caching and Buffering)
위의 끔찍한 I/O 비용을 해결하기 위해 현대 OS는 적극적인 캐싱을 도입한다.
읽기 성능 향상: 통합 페이지 캐시 (Unified Page Cache)
- 초기 시스템은 파일 시스템용 고정 메모리 영역을 뒀지만 낭비가 심했다.
- 현대 시스템은 남는 메모리(RAM)를 전부 페이지 캐시로 활용한다.
- 자주 읽는 디렉터리나 파일은 메모리에 올라와 있으므로, 위의 시나리오에서 "루트 읽기", "foo 읽기" 등은 대부분 디스크 접근 없이 메모리에서 즉시 처리된다. (Cache Hit)
쓰기 성능 향상: 쓰기 버퍼링 (Write Buffering)
write() 호출 시 디스크에 바로 쓰지 않고 메모리에 모아뒀다가 나중에 쓴다(Delayed Write). 보통 5~30초 뒤에 쓴다.
- 장점 1 (Batching): 작은 I/O들을 모아서 한 번의 큰 I/O로 처리한다.
- 장점 2 (Scheduling): 디스크 헤드 움직임을 최소화하는 순서로 정렬해서 쓴다.
- 장점 3 (Avoidance): 파일을 만들고 바로 지우면, 아예 디스크에 쓸 필요가 없어진다.
- 단점: 전원이 나가면 버퍼에 있던 데이터는 사라진다. (그래서 fsync가 필요하다.)
요약 (Summary)
- 아이노드(Inode)는 파일의 모든 정보(메타데이터, 데이터 위치)를 담고 있는 핵심 자료구조다.
- 다단계 인덱스(Multi-Level Index) 구조를 통해 작은 파일의 효율성과 큰 파일의 확장성을 모두 잡았다.
- 디렉터리는 그저 (이름, 아이노드 번호) 목록을 담은 파일일 뿐이다.
- 파일 시스템 작업(읽기/쓰기)은 수많은 메타데이터 I/O를 유발한다.
- 이 성능 저하를 막기 위해 OS는 시스템 메모리(RAM)를 적극적으로 캐시로 사용하여 디스크 접근을 최소화한다.
이제 우리는 디스크 위에 파일 시스템이 어떻게 "구현"되어 있는지 알게 되었다. 하지만 여기서 끝이 아니다. "쓰기 도중에 전원이 나가면 파일 시스템이 깨지는 문제(Crash Consistency)"는 어떻게 해결할까? 이는 다음 장에서 다루게 된다.
'Deep Dive > OS' 카테고리의 다른 글
| [OSTEP] 스터디 17주차 Part.1 Crash Consistency: FSCK and Journaling (0) | 2025.12.23 |
|---|---|
| [OSTEP] 스터디 16주차 Fast File System (FFS) 와 로그 구조화 파일 시스템 (LFS) (0) | 2025.12.23 |
| [OSTEP] 스터디 15주차 Part.1 파일과 디렉터리 (1) | 2025.12.09 |
| [OSTEP] 스터디 15주차 - 영속성 Redundant Array of Inexpensive Disk (RAID) (0) | 2025.12.09 |
| [OSTEP] 스터디 14주차 - 영속성 1 Part.2 (0) | 2025.12.02 |
