inode 구조 심화 (inode Structure)
Linux 커널 inode 자료구조, inode_operations, inode 캐시, 파일시스템별 구현 심층 분석.
inode 개요
struct inode는 파일시스템의 파일(또는 디렉터리, 심볼릭 링크 등)을 나타내는 메타데이터 객체입니다. 파일명이 아닌 파일 자체의 속성을 저장합니다 — 파일명은 dentry가 담당합니다.
inode 추상화 원리
Unix/Linux 파일시스템의 핵심 설계 원리는 "이름(name)과 메타데이터(metadata)의 분리"입니다:
- dentry (디렉터리 엔트리): 파일의 이름과 부모-자식 관계(경로 구조)를 관리합니다. 하나의 inode에 여러 dentry가 연결될 수 있습니다(하드 링크).
- inode: 파일의 실체 — 크기, 권한, 타임스탬프, 데이터 위치 등 파일 자체의 속성을 저장합니다. 고유한 inode 번호(
i_ino)로 식별됩니다.
이 분리 덕분에 하드 링크(같은 inode에 여러 이름), 파일 이동(dentry만 변경, inode 불변), 삭제된 파일의 계속 접근(열린 파일 디스크립터가 inode 참조 유지) 등이 가능합니다.
VFS inode vs 파일시스템별 inode
커널은 2단계 inode 구조를 사용합니다:
- VFS inode (
struct inode): 모든 파일시스템에 공통인 메타데이터(크기, 권한, 타임스탬프, 참조 카운트 등). VFS 계층이 직접 접근합니다. - FS-specific inode (예:
struct ext4_inode_info): 파일시스템 고유의 데이터(extent 트리, 저널링 정보 등).container_of()매크로로 VFS inode에서 역추적합니다.
각 파일시스템은 자체 inode 구조체에 VFS inode를 임베드합니다. 이 패턴은 상속 없이 다형성을 달성하는 커널의 전형적인 객체 지향 기법입니다.
inode 캐시 동작 원리
VFS는 inode 캐시(icache)를 유지하여 디스크 I/O를 최소화합니다. 동작 원리는 다음과 같습니다:
- 해시 테이블 조회: 파일 접근 시 (superblock, inode 번호) 쌍으로 해시 테이블을 검색합니다. 히트하면 디스크 읽기 없이 즉시 반환합니다.
- LRU 관리: 참조 카운트(
i_count)가 0이 된 inode는 LRU 리스트에 들어갑니다. 즉시 삭제하지 않고 캐시에 유지하여, 재접근 시 빠르게 활용합니다. - 메모리 회수: 메모리 압력 시 커널의 shrinker가 LRU 끝에서부터 inode를 회수합니다.
vm.vfs_cache_pressuresysctl로 회수 적극성을 조절합니다 (기본값 100, 높으면 더 적극적 회수).
dentry 캐시와의 관계: dentry 캐시(dcache)와 inode 캐시는 함께 동작합니다. dentry가 해시에서 히트하면 연결된 inode도 캐시에 있습니다. 경로 조회(path_lookup)는 dcache → icache 순서로 진행되어, 자주 접근하는 파일의 경로 해석이 디스크 I/O 없이 완료됩니다.
struct inode 주요 필드
struct inode {
umode_t i_mode; /* 파일 유형 + 권한 */
unsigned short i_opflags;
kuid_t i_uid; /* 소유자 UID */
kgid_t i_gid; /* 소유자 GID */
unsigned int i_flags;
const struct inode_operations *i_op; /* inode 연산 */
struct super_block *i_sb; /* 소속 superblock */
struct address_space *i_mapping; /* 페이지 캐시 매핑 */
unsigned long i_ino; /* inode 번호 */
atomic_t i_count; /* 참조 카운트 */
unsigned int i_nlink; /* 하드 링크 수 */
loff_t i_size; /* 파일 크기 (바이트) */
struct timespec64 __i_atime; /* 최종 접근 시간 */
struct timespec64 __i_mtime; /* 최종 수정 시간 */
struct timespec64 __i_ctime; /* 최종 변경 시간 */
blkcnt_t i_blocks; /* 할당된 블록 수 */
const struct file_operations *i_fop; /* file 연산 */
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct cdev *i_cdev;
char *i_link; /* symlink target */
};
void *i_private; /* fs-specific data */
};
inode_operations
struct inode_operations {
struct dentry *(*lookup)(struct inode *, struct dentry *, unsigned int);
int (*create)(struct mnt_idmap *, struct inode *, struct dentry *, umode_t, bool);
int (*link)(struct dentry *, struct inode *, struct dentry *);
int (*unlink)(struct inode *, struct dentry *);
int (*mkdir)(struct mnt_idmap *, struct inode *, struct dentry *, umode_t);
int (*rmdir)(struct inode *, struct dentry *);
int (*rename)(struct mnt_idmap *, struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr)(struct mnt_idmap *, struct dentry *, struct iattr *);
int (*getattr)(struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
};
inode 캐시
VFS는 inode 캐시(icache)를 유지하여 디스크 접근을 최소화합니다. 사용 중이지 않은 inode는 LRU 리스트에 들어가며, 메모리 압력 시 회수됩니다.
cat /proc/sys/fs/inode-nr로 현재 할당된 inode 수와 free inode 수를 확인할 수 있습니다. slabtop에서 inode_cache 항목도 참고하세요.
inode 파일 유형
| 매크로 | 유형 | 설명 |
|---|---|---|
S_IFREG | 일반 파일 | 데이터 저장 |
S_IFDIR | 디렉터리 | 다른 파일들의 목록 |
S_IFLNK | 심볼릭 링크 | 다른 경로 참조 |
S_IFBLK | 블록 디바이스 | 디스크 등 |
S_IFCHR | 문자 디바이스 | 터미널, 시리얼 등 |
S_IFIFO | FIFO (named pipe) | 프로세스간 통신 |
S_IFSOCK | 소켓 | Unix domain socket |
ext4 inode 확장
각 파일시스템은 VFS inode를 자체 구조체에 임베드합니다. ext4의 경우:
struct ext4_inode_info {
__le32 i_data[15]; /* block pointers or extent tree */
__u32 i_flags;
ext4_fsblk_t i_file_acl;
/* ... ext4 specific fields ... */
struct inode vfs_inode; /* VFS inode 임베드 */
};
/* VFS inode에서 ext4 inode로 변환 */
static inline struct ext4_inode_info *EXT4_I(struct inode *inode)
{
return container_of(inode, struct ext4_inode_info, vfs_inode);
}
inode 생명주기
inode는 할당 → 초기화 → 사용 → 해제의 생명주기를 가집니다. 참조 카운트(i_count)와 하드 링크 수(i_nlink)가 모두 0이 되면 삭제됩니다.
/* 새 inode 할당 (파일시스템별 alloc_inode 호출) */
struct inode *inode = new_inode(sb);
/* inode 번호 할당 및 해시 테이블에 삽입 */
inode->i_ino = get_next_ino();
insert_inode_hash(inode);
/* 초기 속성 설정 */
inode->i_mode = S_IFREG | 0644;
inode_init_owner(idmap, inode, dir, mode);
inode->i_op = &myfs_inode_ops;
inode->i_fop = &myfs_file_ops;
inode->i_mapping->a_ops = &myfs_aops;
/* 참조 카운트 관리 */
ihold(inode); /* i_count++ (참조 획득) */
iput(inode); /* i_count-- (참조 해제, 0이면 evict) */
/* inode 삭제 경로 */
/* i_nlink == 0 && i_count == 0 → evict_inode() 호출 */
파일시스템별 inode 할당
각 파일시스템은 alloc_inode()와 free_inode()를 구현하여 자체 확장 inode를 관리합니다:
static struct kmem_cache *myfs_inode_cachep;
static struct inode *myfs_alloc_inode(struct super_block *sb)
{
struct myfs_inode_info *mi;
mi = alloc_inode_sb(sb, myfs_inode_cachep, GFP_KERNEL);
if (!mi)
return NULL;
/* fs-specific 필드 초기화 */
mi->i_disksize = 0;
return &mi->vfs_inode;
}
static void myfs_free_inode(struct inode *inode)
{
kmem_cache_free(myfs_inode_cachep, MYFS_I(inode));
}
static const struct super_operations myfs_sops = {
.alloc_inode = myfs_alloc_inode,
.free_inode = myfs_free_inode,
.write_inode = myfs_write_inode,
.evict_inode = myfs_evict_inode,
};
확장 속성 (xattr)
inode에 추가적인 이름-값 쌍 메타데이터를 저장합니다. 보안 레이블(SELinux), ACL, 사용자 데이터 등에 사용됩니다.
| 네임스페이스 | 접두사 | 용도 |
|---|---|---|
| user | user.* | 사용자 정의 메타데이터 |
| security | security.* | SELinux, AppArmor 레이블 |
| system | system.posix_acl_* | POSIX ACL |
| trusted | trusted.* | 관리자 전용 메타데이터 |
/* xattr 핸들러 등록 */
static const struct xattr_handler myfs_xattr_user_handler = {
.prefix = XATTR_USER_PREFIX,
.get = myfs_xattr_get,
.set = myfs_xattr_set,
};
static const struct xattr_handler *myfs_xattr_handlers[] = {
&myfs_xattr_user_handler,
&myfs_xattr_security_handler,
NULL,
};
sb->s_xattr = myfs_xattr_handlers;
inode 이벤트 감시 (inotify/fanotify)
inode 변경 사항을 유저스페이스에 알리는 커널 메커니즘입니다:
/* 커널 내부: 파일 변경 시 이벤트 발생 */
fsnotify_modify(file); /* 파일 내용 수정 */
fsnotify_access(file); /* 파일 읽기 */
fsnotify_create(dir, dentry); /* 파일 생성 */
fsnotify_delete(dir, dentry); /* 파일 삭제 */
/* VFS 계층에서 자동 호출됨 (vfs_write, vfs_read 등) */
| 인터페이스 | 대상 | 특징 |
|---|---|---|
| inotify | 파일/디렉터리 | 간편한 API, 재귀 감시 미지원 |
| fanotify | 마운트/파일시스템 | 전체 마운트 감시, 접근 제어 가능 |
Btrfs의 inode 확장
Btrfs는 전통적 inode 번호 대신 (subvolume_id, objectid) 쌍으로 파일을 식별합니다:
struct btrfs_inode {
struct inode vfs_inode;
u64 root_objectid; /* subvolume ID */
struct btrfs_key location; /* (objectid, type, offset) */
u64 disk_i_size; /* 디스크 상의 크기 */
u64 generation; /* CoW 트랜잭션 세대 */
u64 flags; /* NODATASUM, COMPRESS 등 */
struct btrfs_ordered_inode_tree ordered_tree;
struct list_head delalloc_inodes;
};
stat --format=%i로 inode 번호를, getfattr -d로 확장 속성을 확인할 수 있습니다. Btrfs에서는 btrfs inspect-internal inode-resolve로 inode 번호에서 경로를 역추적할 수 있습니다.
파일시스템별 inode 고려사항
앞서 ext4와 Btrfs의 inode 확장을 살펴보았습니다. 다음으로 파일시스템 설계 시 고려해야 할 inode 관련 공통 사항 — inode 고갈, 크기 제한, 성능 특성 등을 정리합니다.
inode 고갈 문제
# inode 사용량 확인
df -i
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 6553600 234567 6319033 4% /
# ext4: inode 수는 mkfs 시 결정 (이후 변경 불가!)
mkfs.ext4 -N 10000000 /dev/sda1 # inode 천만 개
mkfs.ext4 -i 4096 /dev/sda1 # 4KB당 1개 inode (소파일 많은 환경)
# XFS: inode는 동적 할당 (고갈 문제 적음)
# Btrfs: inode 번호 동적 (고갈 없음)
mkfs 옵션을 설정해야 합니다.
inode 크기와 인라인 데이터
| 파일시스템 | 기본 inode 크기 | 인라인 데이터 | xattr 인라인 |
|---|---|---|---|
| ext4 | 256바이트 | 소파일 데이터를 inode 내 저장 (inline_data 옵션) | 잔여 공간에 xattr 저장 (별도 블록 할당 불필요) |
| XFS | 512바이트 | 인라인 데이터 지원 | attr fork에 inline xattr |
| Btrfs | 가변 | 소파일은 메타데이터 B-tree에 인라인 | xattr는 별도 아이템 |
inode 타임스탬프와 성능
/* inode의 세 가지 타임스탬프 */
struct inode {
struct timespec64 __i_atime; /* 마지막 접근 시간 (read) */
struct timespec64 __i_mtime; /* 마지막 수정 시간 (write) */
struct timespec64 __i_ctime; /* 마지막 변경 시간 (메타데이터) */
/* ext4는 crtime (생성 시간)도 저장 — statx()로 조회 */
};
/* atime 마운트 옵션과 성능 영향 */
/* noatime — atime 갱신 완전 비활성화 (최고 성능) */
/* relatime — mtime보다 오래된 경우에만 atime 갱신 (기본값) */
/* strictatime — 매 접근마다 갱신 (성능 나쁨) */
/* lazytime — atime을 메모리에서만 갱신, 주기적으로 디스크 기록 (5.6+) */
하드 링크와 inode 공유
/* 하드 링크: 여러 dentry가 하나의 inode를 공유 */
/* ln target link → target과 link의 inode 번호가 동일 */
/* i_nlink: 하드 링크 수. 0이 되면 inode 해제 */
/* 하드 링크 제한 사항 */
/* 1. 디렉토리에는 하드 링크 불가 (순환 참조 방지) */
/* 2. 파일시스템 경계를 넘을 수 없음 (같은 디바이스만) */
/* 3. ext4 최대 하드 링크 수: 65,000 (dir_nlink로 확장 가능) */
/* reflink (CoW 복사) — Btrfs, XFS 4.16+ */
/* cp --reflink=always src dst */
/* inode는 별도이지만 데이터 extent를 공유 (공간 절약) */
/* 쓰기 시 CoW로 분리 — 스냅샷의 기반 기술 */
debugfs— ext4 내부 구조 탐색, inode 직접 조회, 삭제된 파일 복구xfs_db— XFS 내부 구조 탐색, AG/inode/extent 정보btrfs inspect-internal— Btrfs inode, extent, 서브볼륨 정보filefrag— 파일의 extent 매핑과 단편화 정도 확인statx()— 확장 stat: 생성 시간(btime), 마운트 ID, DAX 상태 조회 (5.x+)