에듀테크 프로젝트 대시보드
파일 시스템과 inode
파일의 이름은 디렉터리에 있고, 파일의 실체(크기·권한·데이터 위치)는
inode 라는 구조체에 있다. 이름과 실체가 분리돼 있기 때문에 하드 링크,
rm 의 동작, mv 가 빠른 이유가 전부 설명된다. 직접 만져 보자.
디렉터리 자체도 하나의 파일이고, 그 내용은 (파일 이름, inode 번호) 쌍의 목록이다.
파일을 열면 커널은 ① 디렉터리에서 이름으로 inode 번호를 찾고 → ② inode 테이블에서 그 번호의
inode를 읽어 → ③ inode 안의 블록 포인터를 따라 실제 데이터 블록을 읽는다.
inode에는 파일 이름이 없다. 그래서 한 inode에 여러 이름을 붙일 수 있고(하드 링크), 이름을 바꿔도 데이터는 1바이트도 안 움직인다(디렉터리 표의 문자열만 교체).
inode 8칸, 데이터 블록 24칸짜리 디스크다. 이름과 크기를 정해 파일 생성을 누르고, 디렉터리 행을 클릭해 선택한 뒤 하드 링크/심볼릭 링크/삭제를 눌러 본다. 블록이 4개를 넘으면 간접 블록(주황 테두리) 하나가 추가로 소비된다.
| 이름 | inode | 종류 |
|---|
하드 링크 (ln a b) | 심볼릭 링크 (ln -s a b) | |
|---|---|---|
| 가리키는 것 | 같은 inode 번호 | 대상의 경로 문자열 |
| 자기 inode | 없음 (이름만 추가) | 따로 있음 (내용 = 경로) |
| 원본 삭제 시 | 링크 수만 감소, 데이터 유지 | 깨진 링크(dangling) |
| 디렉터리 대상 | 불가 (루프 방지) | 가능 |
| 다른 파티션 | 불가 (inode 번호는 파티션 내부용) | 가능 |
rm 은 파일을 지우는 게 아니라 디렉터리 엔트리 하나를 지우고 링크 수(link count) 를
1 줄인다. 링크 수가 0 이 되고, 그 파일을 열어 둔 프로세스도 없을 때 비로소 inode와
데이터 블록이 해제된다. 그래서 실행 중인 로그 파일을 rm 해도 프로세스가 계속 쓸 수 있다.
inode 안의 포인터 배열은 크기가 고정이다(예: 직접 12개). 파일이 그보다 커지면 간접 블록을 한 칸 할당해 그 안에 "블록 번호 목록"을 채운다. 더 커지면 2중·3중 간접까지 간다. 덕분에 작은 파일은 추가 블록 없이 빠르고, 큰 파일도 inode 크기를 늘리지 않고 담는다.
| 단계 | 담을 수 있는 양 (블록 4KB 기준, 대략) |
|---|---|
| 직접 포인터 12개 | 48 KB |
| 1중 간접 (포인터 1024개) | + 4 MB |
| 2중 간접 | + 4 GB |
| 3중 간접 | + 4 TB |
// 파일 이름은 여기 없다. 이름은 디렉터리 엔트리에 있다. struct inode { uint16_t mode; // 파일/디렉터리/심링크 + 권한(rwx) uint16_t uid, gid; uint32_t size; // 바이트 단위 파일 크기 uint32_t atime, mtime, ctime; uint16_t links_count; // 이 inode를 가리키는 이름의 개수 uint32_t block[15]; // 0~11: 직접, 12: 1중, 13: 2중, 14: 3중 간접 }; struct dirent { uint32_t inode_no; char name[]; }; // open("/home/a.txt") 의 이름 해석(path walk) int namei(const char *path) { int ino = ROOT_INO; // '/' 는 항상 2번 for (char *part = strtok(path,"/"); part; part = strtok(NULL,"/")) { ino = dir_lookup(ino, part); // 디렉터리에서 이름으로 검색 if (ino < 0) return -ENOENT; } return ino; }
ls -i 로 inode 번호를, stat 으로 링크 수(Links:)와
블록 수를 볼 수 있다. df -i 는 디스크 용량이 아니라 inode 잔여 개수를 보여 준다 —
작은 파일을 수백만 개 만들면 용량이 남아도 inode가 먼저 바닥나 "No space left" 가 뜬다.