inode: 파일 메타데이터와 데이터 블록을 분리하는 유닉스 파일시스템의 핵심 구조
inode는 유닉스 계열 파일시스템에서 파일의 이름을 제외한 모든 메타데이터(권한, 소유자, 크기, 타임스탬프, 데이터 블록 위치)를 저장하는 자료구조다. 파일 이름은 디렉터리 엔트리에만 존재하고 실제 파일 실체는 inode 번호로 식별되기 때문에, 하드 링크나 파일 삭제 동작의 원리를 이해하는 데 핵심이 된다. 이 글에서는 inode의 구조, 디렉터리와의
By 송민성
1. 개념
inode(index node)는 파일시스템에서 파일 하나하나에 대응하는 메타데이터 구조체다. 여기서 "메타데이터"란 파일의 실제 내용(content)이 아니라 그 파일에 대한 정보를 말한다.
inode가 저장하는 정보는 대략 다음과 같다.
- 파일 타입(일반 파일, 디렉터리, 심볼릭 링크, 소켓 등)
- 권한(permission bits)
- 소유자 UID, 그룹 GID
- 파일 크기
- 타임스탬프(atime, mtime, ctime)
- 하드 링크 개수(link count)
- 데이터 블록을 가리키는 포인터들
중요한 점은 파일 이름이 inode 안에 없다는 것이다. 파일 이름은 디렉터리라는 별도의 자료구조 안에 "이름 → inode 번호" 매핑 형태로 저장된다.
2. 왜 사용하는가
파일 이름과 파일 실체를 분리해서 관리하면 다음이 가능해진다.
- 하드 링크(hard link): 하나의 inode를 여러 이름으로 참조할 수 있다. 파일을 복사하지 않고도 여러 경로에서 같은 데이터에 접근 가능하다.
- 이름 변경 비용 최소화:
mv같은 파일 이름 변경은 디렉터리 엔트리만 수정하면 되고, 데이터 블록을 옮길 필요가 없다(같은 파일시스템 내에서는 O(1)에 가깝다). - 파일 삭제의 안전한 지연: 어떤 프로세스가 파일을 열어놓은 상태에서 그 파일이 삭제되어도, link count와 open count가 0이 될 때까지 실제 데이터는 남아있는다. 이 덕분에 실행 중인 프로그램이 자신의 실행 파일을 삭제해도 크래시가 나지 않는다.
3. 동작 원리
inode와 디렉터리의 관계
디렉터리는 사실 "파일 이름과 inode 번호의 쌍(pair)"을 담은 특수한 파일이다. ls -i 명령으로 이 매핑을 직접 확인할 수 있다.
$ ls -i /etc/passwd
131099 /etc/passwd여기서 131099가 inode 번호다. 경로 탐색(path resolution)은 다음 순서로 이루어진다.
- 루트 디렉터리(
/)의 inode를 찾는다(슈퍼블록에 저장된 고정 번호). - 루트 디렉터리의 데이터 블록을 읽어 다음 경로 요소의 이름과 inode 번호 매핑을 찾는다.
- 이 과정을 경로 끝까지 반복(재귀적 탐색)한다.
데이터 블록 포인터 구조
전통적인 유닉스 파일시스템(ext2 기준)에서 inode는 데이터 블록을 가리키는 포인터를 다음과 같이 구성한다.
- 직접 블록 포인터(direct block pointer) 12개
- 단일 간접 블록 포인터(single indirect) 1개
- 이중 간접 블록 포인터(double indirect) 1개
- 삼중 간접 블록 포인터(triple indirect) 1개
블록 크기가 4KB이고 포인터가 4바이트라면, 단일 간접 블록 하나로 1024개의 블록을 추가로 가리킬 수 있다. 이 구조 덕분에 작은 파일은 direct pointer만으로 빠르게 접근하고, 큰 파일은 간접 포인터를 통해 확장 가능하다.
하드 링크와 link count
$ echo "hello" > file1.txt
$ ln file1.txt file2.txt
$ ls -i file1.txt file2.txt
2621441 file1.txt
2621441 file2.txt
$ stat file1.txt | grep Links
Links: 2두 이름이 같은 inode 번호를 가리킨다. rm file1.txt를 실행해도 file2.txt는 link count가 1이 된 채로 살아있고 데이터는 그대로 유지된다. link count가 0이 되고 열려있는 파일 디스크립터도 없을 때 비로소 데이터 블록이 실제로 해제된다.
4. 코드 예제
리눅스에서 파이썬의 os.stat으로 inode 정보를 직접 확인하는 예제다.
import os
path = "/etc/hostname"
st = os.stat(path)
print(f"inode 번호: {st.st_ino}")
print(f"파일 크기: {st.st_size} bytes")
print(f"하드 링크 개수: {st.st_nlink}")
print(f"소유자 UID: {st.st_uid}")
print(f"권한(8진수): {oct(st.st_mode)}")
print(f"최종 수정 시각: {st.st_mtime}")파일 삭제 후에도 열린 파일 디스크립터로 데이터에 접근할 수 있음을 보여주는 예제다.
import os
# 파일 생성 및 오픈
fd = os.open("temp.txt", os.O_CREAT | os.O_RDWR)
os.write(fd, b"inode still alive after unlink\n")
# 파일 이름 삭제 (디렉터리 엔트리만 제거, inode의 link count 감소)
os.unlink("temp.txt")
# fd는 여전히 유효하다. inode는 아직 살아있다.
os.lseek(fd, 0, os.SEEK_SET)
data = os.read(fd, 100)
print(data.decode()) # "inode still alive after unlink" 출력됨
os.close(fd) # 이 시점에 link count와 open count가 모두 0이 되어 실제 회수5. 시간 복잡도 또는 성능 특성
- 경로 탐색(path resolution): 경로의 깊이(depth)에 비례한다. 디렉터리 하나당 inode 하나를 읽어야 하므로 깊이가 n이면 대략 O(n)의 디스크 접근이 필요하다. 단, 리눅스 커널은 dentry cache(디렉터리 엔트리 캐시)를 사용해서 반복 조회 시 이 비용을 실질적으로 줄인다.
- 파일 이름 변경(rename, 같은 파일시스템 내): 디렉터리 엔트리 하나만 수정하면 되므로 파일 크기와 무관하게 O(1)에 가깝다.
- 디렉터리 내 파일 이름 검색: 전통적인 ext2/ext3는 디렉터리 엔트리를 선형 리스트로 저장해서 O(n)이다. ext4는 HTree(해시 기반 B-tree 유사 구조)를 사용해서 대규모 디렉터리에서도 O(log n)에 가깝게 개선했다.
- 파일 데이터 접근: direct block은 O(1), 큰 파일의 후반부는 간접 블록을 한 단계씩 더 거쳐야 하므로 상수 배 정도의 추가 비용이 든다.
6. 실무 사용 사례
inode 고갈(inode exhaustion) 문제: 디스크 용량은 남아있는데 No space left on device 에러가 발생하는 경우가 있다. 이는 inode 개수가 파일시스템 생성 시점에 고정되어 있고, 작은 파일을 대량으로 생성하는 워크로드(세션 파일, 캐시 파일, 이메일 큐 등)에서 inode를 모두 소진했을 때 발생한다.
$ df -i /var
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 1310720 1310720 0 100% /var용량(df -h)과 inode 개수(df -i)는 별개로 확인해야 한다.
로그 로테이션과 열린 파일 디스크립터: 로그 로테이션 도구(logrotate 등)가 로그 파일 이름을 바꾸거나 삭제해도, 애플리케이션이 이미 연 파일 디스크립터를 계속 쓰고 있으면 디스크 공간이 회수되지 않는다. lsof | grep deleted로 이런 "삭제되었지만 아직 열려있는" 파일을 찾을 수 있다.
컨테이너 이미지 레이어: 오버레이 파일시스템(OverlayFS)은 하드 링크와 유사한 inode 공유 개념을 활용해서 레이어 간 중복 파일을 물리적으로 복제하지 않고 참조로 공유한다.
7. 주의할 점
- 하드 링크는 같은 파일시스템(같은 디바이스) 안에서만 가능하다. 파일시스템 경계를 넘는 링크는 심볼릭 링크(symbolic link)를 써야 한다. inode 번호는 파일시스템마다 독립적으로 부여되기 때문이다.
- 디렉터리에는 하드 링크를 만들 수 없다. 디렉터리 구조가 순환(cycle)을 이루면 트리 탐색 알고리즘이 무한 루프에 빠질 위험이 있어서 대부분의 유닉스 시스템이 이를 금지한다.
- inode 개수는 파일시스템 포맷 시점에 결정된다(ext4 기준). 나중에 작은 파일이 많아질 걸 예상하지 못하면 디스크 용량이 남아도 파일을 못 만드는 상황이 생긴다.
mkfs.ext4 -N으로 inode 개수를 미리 조정할 수 있다. - SSD의 wear leveling과 별개 이슈다. inode 고갈은 순수하게 메타데이터 슬롯 부족 문제이며 디스크 수명과는 무관하다.
8. 핵심 정리
inode는 파일 이름과 파일 실체(메타데이터+데이터)를 분리하는 설계의 핵심이다. 디렉터리는 "이름 → inode 번호"를 매핑하는 특수 파일일 뿐이고, 실제 권한/크기/블록 위치 같은 정보는 모두 inode에 있다. 이 분리 덕분에 하드 링크, 빠른 이름 변경, 열린 파일의 안전한 삭제 지연 같은 기능이 가능해진다. 실무에서는 inode 개수가 유한한 자원이라는 점, 그리고 삭제된 파일도 열린 파일 디스크립터가 있으면 디스크 공간을 계속 점유한다는 점을 기억해두면 디스크 관련 장애를 진단할 때 큰 도움이 된다.