파일 시스템
파일 시스템은 데이터를 파일 단위로 저장하고 디렉토리로 계층 구조를 만들어 관리하는 OS의 핵심 기능입니다. inode 기반(Linux ext4)과 FAT 기반(Windows FAT32/exFAT)이 대표적인 두 가지 방식입니다.
핵심 개념
inode (Index Node)
파일의 메타데이터(크기, 권한, 타임스탬프, 소유자)와 데이터 블록 포인터를 저장하는 자료구조입니다. 파일명은 inode에 없고 디렉토리 엔트리에 있습니다. ls -i 명령으로 inode 번호를 확인할 수 있습니다.
핵심 개념
디렉토리 구조
inode 기반 파일 시스템(ext4 등)에서 디렉토리는 파일명과 inode 번호의 쌍을 저장하는 특수 파일입니다. 경로 탐색 시 OS는 루트(/)부터 각 디렉토리의 inode를 거쳐 목표 파일의 inode를 찾습니다.
핵심 개념
FAT (File Allocation Table)
디스크 블록(클러스터)의 연결 관계를 테이블로 관리합니다. 각 항목이 다음 블록 번호를 가리키는 연결 리스트 방식입니다. 단순하지만 랜덤 접근이 느리고 FAT가 손상되면 파일의 블록 연결 정보를 잃을 수 있습니다.
핵심 개념
하드 링크 vs 심볼릭 링크
하드 링크는 같은 inode를 가리키는 또 다른 디렉토리 엔트리입니다. 한 이름을 지워도 다른 이름(링크)이 남아 있으면 데이터가 유지됩니다. 심볼릭 링크(소프트 링크)는 경로를 저장하는 별도 파일로 원본 삭제 시 깨집니다.
핵심 개념
저널링 (Journaling)
파일 시스템 변경 전 로그(저널)에 먼저 기록합니다. 시스템 충돌 시 저널을 재실행해 일관성을 복구합니다. ext4와 NTFS는 저널링을 쓰고, APFS는 저널 대신 메타데이터를 쓰기 시점 복사(CoW) 방식으로 갱신해 일관성을 유지합니다.
핵심 개념
VFS (Virtual File System)
다양한 파일 시스템(ext4, FAT, NTFS, tmpfs)을 동일한 인터페이스로 추상화하는 Linux 커널 계층입니다. open(), read(), write() 같은 시스템 콜 인터페이스로 여러 파일 시스템에 접근할 수 있는 이유입니다(지원하는 기능은 파일 시스템마다 다릅니다).
파일 시스템이 하는 일
디스크는 번호가 매겨진 블록의 모음일 뿐이라서, 이름이 있는 파일과 디렉토리 계층으로 보이게 하려면 어떤 블록이 어떤 파일의 것인지 기록하는 구조가 필요합니다. 파일 시스템은 이 기록과 빈 공간 관리, 접근 권한, 충돌 뒤 복구를 맡습니다. 리눅스는 여러 종류의 파일 시스템을 VFS라는 공통 계층 아래에서 같은 인터페이스로 다룹니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
inode란 무엇인가요?
파일의 메타데이터와 데이터 블록 위치를 담은 구조체입니다. 파일 이름은 담지 않고 디렉토리 항목이 이름과 inode 번호를 연결합니다.
면접 질문
/home/a.txt를 열 때 어떤 일이 일어나나요?
루트 디렉토리의 inode에서 시작해 home 디렉토리의 항목을 찾고, 그 디렉토리에서 a.txt의 inode 번호를 찾습니다. 그 inode의 권한을 확인한 뒤 데이터 블록을 읽습니다.
면접 질문
하드 링크와 심볼릭 링크는 어떻게 다른가요?
하드 링크는 같은 inode에 이름을 하나 더 붙이는 것이라 같은 파일 시스템 안에서만 가능하고 어느 이름을 지워도 데이터는 남습니다. 심볼릭 링크는 경로를 담은 별도 파일이라 다른 파일 시스템도 가리킬 수 있고 대상이 지워지면 끊어집니다.
면접 질문
저널링은 왜 필요한가요?
파일 하나를 바꾸는 데 여러 블록을 고쳐야 하는데 중간에 전원이 꺼지면 구조가 어긋납니다. 변경 내용을 먼저 로그에 기록해 두면 재부팅 때 로그를 보고 완료하거나 취소해 일관성을 복구할 수 있습니다.
실제 시스템에서는
ls -i로 inode 번호를, stat으로 inode의 메타데이터를, df -i로 inode 사용량을 볼 수 있습니다. ext2·ext3·ext4처럼 inode 수가 만들 때 정해지는 파일 시스템에서는 파일이 적어도 inode가 바닥나면 새 파일을 만들 수 없습니다.
Files.createLink와 Files.createSymbolicLink로 하드 링크와 심볼릭 링크를 만들 수 있습니다(파일 시스템과 OS가 지원하고 권한이 있을 때).
함께 보면 좋은 토픽
참고 자료
C — open() / read() / write() 시스템 콜
#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> // strlen #include <sys/stat.h> // struct stat, fstat int main() { /* open(): 경로 탐색 → inode 확인 → 파일 디스크립터 반환 */ int fd = open("/home/user/hello.txt", O_RDWR | O_CREAT, 0644); if (fd < 0) { perror("open"); return 1; } // fd: 0=stdin, 1=stdout, 2=stderr 이후 가장 작은 정수 (보통 3) /* write(): fd → 파일 테이블 → inode → 데이터 블록 쓰기 */ const char *msg = "Hello, File System!\n"; ssize_t n = write(fd, msg, strlen(msg)); if (n < 0) { perror("write"); } /* lseek(): 파일 오프셋 이동 (inode의 파일 위치 포인터 변경) */ lseek(fd, 0, SEEK_SET); // 처음으로 돌아가기 /* read(): 커널이 inode → 블록 포인터 → 디스크 읽기 → 버퍼 복사 */ char buf[64]; n = read(fd, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; printf("%s", buf); } /* stat(): inode 메타데이터 조회 (크기, 권한, 타임스탬프) */ struct stat st; fstat(fd, &st); printf("inode: %lu, size: %ld, blocks: %ld\n", st.st_ino, st.st_size, st.st_blocks); close(fd); // fd 해제 (inode 링크 카운트는 유지) return 0; }
open()은 경로를 탐색해 inode를 찾고, 프로세스의 파일 디스크립터 테이블에 항목을 추가한 뒤 정수 fd를 반환합니다.
write()는 즉시 디스크에 쓰지 않고 커널 페이지 캐시(Page Cache)에 씁니다. fsync()를 호출해야 디스크에 확실히 반영됩니다.
fstat()로 st_ino(inode 번호), st_size(바이트 단위 크기), st_blocks(512B 단위 블록 수)를 조회할 수 있습니다. st_nlink가 하드 링크 개수입니다.
Java — NIO.2 / Files API로 inode 정보 확인
import java.nio.file.*; import java.nio.file.attribute.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FileSystemDemo { public static void main(String[] args) throws Exception { Path path = Path.of("/home/user/hello.txt"); // ── inode 메타데이터 조회 ────────────────────────────────── BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); System.out.println("크기: " + attr.size()); System.out.println("생성: " + attr.creationTime()); System.out.println("수정: " + attr.lastModifiedTime()); System.out.println("디렉토리: " + attr.isDirectory()); // Linux inode 번호 (UnixPath에서만 가용) if (attr instanceof PosixFileAttributes) { // unix:ino — 플랫폼 의존 속성, Unix/Linux에서만 가용 Object inode = Files.getAttribute(path, "unix:ino"); System.out.println("inode: " + inode); } // ── FileChannel: 블록 단위 직접 I/O ───────────────────────── try (var fc = FileChannel.open(path, StandardOpenOption.READ)) { ByteBuffer buf = ByteBuffer.allocate(1024); int n = fc.read(buf); // 커널 → JVM 버퍼로 복사 buf.flip(); byte[] bytes = new byte[n]; buf.get(bytes); System.out.println(new String(bytes)); } // ── 하드 링크 생성 (같은 inode를 가리키는 새 경로) ──────── Path link = Path.of("/home/user/hello_hard.txt"); Files.createLink(link, path); // 링크 카운트 +1 // ── 심볼릭 링크 (경로를 가리키는 별도 inode) ────────────── Path sym = Path.of("/home/user/hello_sym.txt"); Files.createSymbolicLink(sym, path); System.out.println("하드링크 대상: " + Files.isSameFile(path, link)); System.out.println("심볼릭 대상: " + Files.readSymbolicLink(sym)); } }
Java NIO.2의 Files.readAttributes()는 내부적으로 fstat() 시스템 콜을 호출합니다. BasicFileAttributes는 크기·타임스탬프, PosixFileAttributes는 권한·소유자까지 제공합니다.
FileChannel은 전통적인 InputStream보다 블록 단위로 효율적으로 I/O하며, 메모리 맵 파일(MappedByteBuffer)로 사용하면 디스크 블록을 메모리처럼 직접 접근할 수 있습니다.
Files.createLink()는 하드 링크(같은 inode)를 만들어 링크 카운트를 1 증가시킵니다. 원본 파일을 삭제해도 카운트가 0이 되지 않으면 데이터는 유지됩니다.
Q1
inode 방식에서 파일의 실제 데이터 위치를 찾는 방법은?
Q2
FAT(File Allocation Table) 방식에서 파일의 다음 블록을 찾는 방법은?
Q3
파일 시스템에서 디렉터리의 역할은?