inode 구조 심화 (inode Structure)

Linux 커널 inode 자료구조, inode_operations, inode 캐시, 파일시스템별 구현 심층 분석.

관련 표준: POSIX.1-2017 (inode 시맨틱, 하드링크, 퍼미션 모델) — inode 구조체는 POSIX 파일 메타데이터 규약을 커널에서 구현합니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

inode 개요

struct inode는 파일시스템의 파일(또는 디렉터리, 심볼릭 링크 등)을 나타내는 메타데이터 객체입니다. 파일명이 아닌 파일 자체의 속성을 저장합니다 — 파일명은 dentry가 담당합니다.

inode 추상화 원리

Unix/Linux 파일시스템의 핵심 설계 원리는 "이름(name)과 메타데이터(metadata)의 분리"입니다:

이 분리 덕분에 하드 링크(같은 inode에 여러 이름), 파일 이동(dentry만 변경, inode 불변), 삭제된 파일의 계속 접근(열린 파일 디스크립터가 inode 참조 유지) 등이 가능합니다.

VFS inode vs 파일시스템별 inode

커널은 2단계 inode 구조를 사용합니다:

각 파일시스템은 자체 inode 구조체에 VFS inode를 임베드합니다. 이 패턴은 상속 없이 다형성을 달성하는 커널의 전형적인 객체 지향 기법입니다.

inode 캐시 동작 원리

VFS는 inode 캐시(icache)를 유지하여 디스크 I/O를 최소화합니다. 동작 원리는 다음과 같습니다:

  1. 해시 테이블 조회: 파일 접근 시 (superblock, inode 번호) 쌍으로 해시 테이블을 검색합니다. 히트하면 디스크 읽기 없이 즉시 반환합니다.
  2. LRU 관리: 참조 카운트(i_count)가 0이 된 inode는 LRU 리스트에 들어갑니다. 즉시 삭제하지 않고 캐시에 유지하여, 재접근 시 빠르게 활용합니다.
  3. 메모리 회수: 메모리 압력 시 커널의 shrinker가 LRU 끝에서부터 inode를 회수합니다. vm.vfs_cache_pressure sysctl로 회수 적극성을 조절합니다 (기본값 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_IFIFOFIFO (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, 사용자 데이터 등에 사용됩니다.

네임스페이스접두사용도
useruser.*사용자 정의 메타데이터
securitysecurity.*SELinux, AppArmor 레이블
systemsystem.posix_acl_*POSIX ACL
trustedtrusted.*관리자 전용 메타데이터
/* 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 번호 동적 (고갈 없음)
inode 고갈은 디스크 여유 공간이 있어도 파일 생성 불가를 초래합니다. 컨테이너 환경, 메일 서버, 캐시 디렉토리 등 소파일이 대량 생성되는 시스템에서 주의가 필요합니다. ext4에서는 생성 시 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+) */
/* 하드 링크: 여러 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+)