F2FS 파일시스템 심화

Flash-Friendly File System(F2FS)의 로그 구조 설계, NAT/SIT/SSA 온디스크 레이아웃, 멀티 헤드 로깅, GC, 체크포인트, 압축, fscrypt, Android 최적화, 성능 튜닝 종합 가이드.

관련 페이지: F2FS는 Flash 스토리지에 최적화된 로그 구조 파일시스템입니다. 블록 I/O 서브시스템은 Block I/O, VFS 계층은 VFS, 전통적 저널링 파일시스템은 ext4, Android 커널 통합은 Android 커널 페이지를 참고하세요.

개요 & 역사

F2FS(Flash-Friendly File System)는 Samsung Electronics가 개발한 Linux 파일시스템으로, NAND Flash 스토리지(eMMC, SD 카드, SSD)의 특성에 최적화된 로그 구조(Log-Structured) 설계를 채택합니다. Linux 3.8(2013년 2월)에 메인라인 머지되었습니다.

Flash 스토리지 특성

F2FS 설계를 이해하려면 NAND Flash의 물리적 제약을 먼저 알아야 합니다:

특성HDDNAND Flash
읽기 단위섹터 (512B)페이지 (4KB~16KB)
쓰기 단위섹터 (제자리)페이지 (빈 페이지만)
삭제 단위불필요블록 (128~512 페이지)
제자리 덮어쓰기가능불가능 (erase-before-write)
수명기계적 마모P/E cycle 제한 (TLC: ~3000, QLC: ~1000)

Flash 스토리지에서는 erase-before-write 제약으로 인해 제자리 갱신(in-place update)이 비효율적입니다. FTL(Flash Translation Layer)이 이를 추상화하지만, 파일시스템 레벨에서 Flash 특성을 고려하면 성능과 수명을 크게 개선할 수 있습니다.

설계 목표

F2FS 역사

시기이정표
2012.10Samsung, LSFMM에서 F2FS 설계 발표
2013.02Linux 3.8 메인라인 머지 (Jaegeuk Kim)
2014Motorola Moto G, F2FS 최초 상용 적용
2016Huawei P9, F2FS 기본 파일시스템 채택
2018Google Pixel 3, userdata 파티션 F2FS 채택
2019압축(LZO/LZ4) 지원, zoned storage 지원
2020ZSTD 압축 지원, 다중 디바이스 지원
2022Android 13+ 기본 파일시스템으로 광범위 채택

아키텍처 & 온디스크 레이아웃

F2FS는 전체 볼륨을 고정 크기 세그먼트(segment, 2MB)로 분할하고, 세그먼트를 묶어 섹션(section), 섹션을 묶어 존(zone)을 구성합니다. 온디스크 레이아웃은 6개 영역으로 나뉩니다:

Superblock (SB) Checkpoint (CP) ×2 SIT Segment Info NAT Node Address SSA Summary Area Main Area Node + Data Segments Node Seg Data Seg 세그먼트 구조: Block 0 | Block 1 | Block 2 | ... | Block 511 (512 blocks × 4KB = 2MB per segment) 계층 구조: Block(4KB) → Segment(2MB = 512 blocks) → Section(N segments) → Zone(N sections)

Superblock (SB)

볼륨의 기본 정보를 저장하며, 2개의 복사본이 존재합니다 (0번, 1번 블록):

/* include/linux/f2fs_fs.h */
struct f2fs_super_block {
    __le32 magic;                  /* 0xF2F52010 */
    __le16 major_ver;               /* 주 버전 */
    __le16 minor_ver;               /* 부 버전 */
    __le32 log_sectorsize;          /* 섹터 크기 (log2) */
    __le32 log_sectors_per_block;   /* 블록 당 섹터 수 (log2) */
    __le32 log_blocksize;           /* 블록 크기 (log2), 보통 12 (4KB) */
    __le32 log_blocks_per_seg;      /* 세그먼트 당 블록 (log2), 보통 9 (512) */
    __le32 segs_per_sec;            /* 섹션 당 세그먼트 수 */
    __le32 secs_per_zone;           /* 존 당 섹션 수 */
    __le32 checksum_offset;         /* SB 내 체크섬 위치 */
    __le64 block_count;             /* 전체 블록 수 */
    __le32 section_count;           /* 섹션 수 */
    __le32 segment_count;           /* 세그먼트 수 */
    __le32 segment_count_ckpt;      /* CP 영역 세그먼트 수 */
    __le32 segment_count_sit;       /* SIT 영역 세그먼트 수 */
    __le32 segment_count_nat;       /* NAT 영역 세그먼트 수 */
    __le32 segment_count_ssa;       /* SSA 영역 세그먼트 수 */
    __le32 segment_count_main;      /* Main Area 세그먼트 수 */
    __le32 segment0_blkaddr;        /* 세그먼트 0 시작 블록 주소 */
    __le32 cp_blkaddr;              /* CP 시작 블록 주소 */
    __le32 sit_blkaddr;             /* SIT 시작 블록 주소 */
    __le32 nat_blkaddr;             /* NAT 시작 블록 주소 */
    __le32 ssa_blkaddr;             /* SSA 시작 블록 주소 */
    __le32 main_blkaddr;            /* Main Area 시작 블록 주소 */
    /* ... feature flags, encryption, etc. */
};

6개 영역 요약

영역크기역할
Superblock (SB)1개 세그먼트볼륨 메타데이터, 매직 넘버, 레이아웃 정보
Checkpoint (CP)2개 세그먼트파일시스템 일관성 상태, 유효 NAT/SIT 저널, orphan inode
SIT (Segment Info Table)N 세그먼트각 세그먼트의 유효 블록 비트맵 & 카운트
NAT (Node Address Table)N 세그먼트노드 ID → 물리 블록 주소 매핑 (wandering tree 해결)
SSA (Segment Summary Area)N 세그먼트블록별 역매핑: 어느 노드의 어느 오프셋인지
Main Area대부분실제 노드 블록 + 데이터 블록 저장

로그 구조 쓰기 (LFS)

전통적인 LFS(Log-Structured File System)는 단일 로그 헤드를 사용하지만, F2FS는 6개의 로그 헤드(current segment)를 동시에 운용합니다. 데이터 온도(temperature)에 따라 분리 기록하여 GC 효율을 극대화합니다:

로그 헤드온도용도
CURSEG_HOT_NODEHot직접 노드 블록 (inode)
CURSEG_WARM_NODEWarm간접 노드 블록
CURSEG_COLD_NODECold간접-간접 노드 블록
CURSEG_HOT_DATAHot디렉토리 엔트리
CURSEG_WARM_DATAWarm일반 파일 데이터
CURSEG_COLD_DATACold멀티미디어, GC에 의해 이동된 데이터
/* fs/f2fs/segment.h */
enum {
    CURSEG_HOT_DATA  = 0,
    CURSEG_WARM_DATA,
    CURSEG_COLD_DATA,
    CURSEG_HOT_NODE,
    CURSEG_WARM_NODE,
    CURSEG_COLD_NODE,
    NR_CURSEG_TYPE,    /* = 6 */
};

Append-Only 쓰기 패턴

F2FS는 데이터를 제자리에서 갱신(in-place update)하지 않고, 항상 현재 세그먼트의 빈 블록에 순차적으로 추가(append)합니다. 이전 위치의 블록은 무효(invalid)로 표시됩니다:

/* fs/f2fs/segment.c - 새 블록 할당 */
static void __allocate_new_segment(struct f2fs_sb_info *sbi,
                                   int type, bool new_sec)
{
    struct curseg_info *curseg = CURSEG_I(sbi, type);
    unsigned int old_segno = curseg->segno;

    /* 현재 세그먼트가 가득 찼으면 새 세그먼트 할당 */
    if (!curseg->inited || !get_valid_blocks(sbi, old_segno, false) ||
        curseg->alloc_type == SSR)
        new_curseg(sbi, type, true);
}

할당 방식: LFS vs SSR

F2FS는 두 가지 할당 방식을 사용합니다:

LFS vs SSR 전환: 여유 세그먼트가 부족하면 F2FS는 자동으로 LFS에서 SSR 모드로 전환합니다. SSR은 랜덤 쓰기가 발생하지만, GC를 즉시 수행하는 것보다 지연 시간이 짧습니다.

NAT (Node Address Table)

NAT은 F2FS의 핵심 혁신으로, 전통적 LFS의 wandering tree 문제를 해결합니다.

Wandering Tree 문제

전통적 LFS에서 데이터 블록이 새 위치에 기록되면, 이를 가리키는 간접 노드 → inode → inode map 전체가 연쇄적으로 갱신되어야 합니다. F2FS는 NAT 테이블을 도입하여 노드 ID와 물리 주소 사이에 간접 계층을 둡니다:

전통 LFS (연쇄 갱신) Data Direct Node Inode Inode Map ← 4곳 갱신! F2FS (NAT 간접 변환) Data Direct Node (nid 참조) NAT Inode ← NAT만 갱신!
/* include/linux/f2fs_fs.h */
struct f2fs_nat_entry {
    __u8  version;       /* 버전 (SIT와 일관성 체크) */
    __le32 ino;          /* 소속 inode 번호 */
    __le32 block_addr;   /* 노드의 실제 물리 블록 주소 */
} __packed;

NAT 영역은 SIT와 마찬가지로 이중 버퍼링됩니다. 체크포인트 시 현재 유효한 NAT 페이지 세트가 기록되며, CP의 nat_bitmap이 어느 세트가 최신인지 가리킵니다.

SIT (Segment Information Table)

SIT는 각 세그먼트의 상태를 추적합니다. 세그먼트 내 각 블록의 유효/무효 여부를 비트맵으로 관리하고, 유효 블록 수를 카운트합니다:

/* include/linux/f2fs_fs.h */
struct f2fs_sit_entry {
    __le16 vblocks;                     /* 유효 블록 수 + type(10bit+6bit) */
    __u8   valid_map[SIT_VBLOCK_MAP_SIZE]; /* 유효 블록 비트맵 (64 bytes = 512 bits) */
    __le64 mtime;                        /* 세그먼트 수정 시간 (GC 비용 계산) */
} __packed;

SIT 이중 버퍼링

SIT 영역은 두 개의 복사본(set 0, set 1)으로 구성됩니다. 체크포인트마다 번갈아 갱신하여 원자적 갱신을 보장합니다. CP의 sit_nat_version_bitmap이 각 SIT 페이지의 유효한 세트를 가리킵니다.

SIT 활용

SSA (Segment Summary Area)

SSA는 Main Area의 각 블록에 대한 역매핑(reverse mapping) 정보를 저장합니다. 블록의 물리 주소로부터 해당 블록이 어느 노드의 어느 오프셋에 속하는지 역추적할 수 있습니다:

/* include/linux/f2fs_fs.h */
struct f2fs_summary {
    __le32 nid;      /* 부모 노드 ID */
    union {
        __u8 reserved;
        struct {
            __u8 version;  /* 노드 버전 */
            __le16 ofs_in_node; /* 부모 노드 내 오프셋 */
        };
    };
} __packed;

GC에서의 SSA 역할

GC가 세그먼트를 정리할 때, 각 유효 블록을 새 위치로 이동해야 합니다. SSA 엔트리를 통해 해당 블록의 부모 노드를 찾고, 노드의 포인터를 갱신합니다:

  1. GC 대상 세그먼트 선택 (SIT 기반)
  2. SIT 비트맵에서 유효 블록 식별
  3. SSA에서 각 유효 블록의 부모 노드(nid) 조회
  4. NAT에서 부모 노드의 물리 주소 조회
  5. 유효 블록을 새 세그먼트로 복사
  6. 부모 노드의 포인터 갱신 (out-of-place)

체크포인트 & 복구

F2FS는 이중 체크포인트(dual checkpoint) 방식으로 파일시스템 일관성을 보장합니다. 체크포인트는 특정 시점의 파일시스템 상태를 안정적으로 기록합니다.

체크포인트 구조

/* include/linux/f2fs_fs.h */
struct f2fs_checkpoint {
    __le64 checkpoint_ver;          /* 체크포인트 버전 (단조 증가) */
    __le64 user_block_count;         /* 사용자 블록 총 수 */
    __le64 valid_block_count;        /* 유효 블록 수 */
    __le32 rsvd_segment_count;       /* 예약 세그먼트 (GC 용) */
    __le32 overprov_segment_count;   /* 오버프로비저닝 세그먼트 */
    __le32 free_segment_count;       /* 여유 세그먼트 수 */
    __le32 ckpt_flags;              /* CP_UMOUNT_FLAG, CP_FSCK_FLAG 등 */
    __le32 cp_pack_total_block_count; /* CP 팩 전체 블록 수 */
    __le32 cp_pack_start_sum;        /* 요약 블록 시작 오프셋 */
    __le32 valid_node_count;         /* 유효 노드 수 */
    __le32 valid_inode_count;        /* 유효 inode 수 */
    __le32 next_free_nid;            /* 다음 사용 가능 노드 ID */
    __le32 sit_ver_bitmap_bytesize;  /* SIT 버전 비트맵 크기 */
    __le32 nat_ver_bitmap_bytesize;  /* NAT 버전 비트맵 크기 */
    __le32 checksum_offset;          /* 체크섬 위치 */
    __le64 elapsed_time;             /* 마운트 누적 시간 */
    /* 이후 current segment 정보, orphan inode, SIT/NAT 저널 */
};

체크포인트 절차

  1. 모든 dirty 노드/데이터 페이지 flush
  2. dirty NAT/SIT 엔트리를 NAT/SIT 영역에 기록 (또는 CP 내 저널에 저장)
  3. CP 팩 기록: header → orphan inode → 요약(summary) → footer
  4. CP 팩의 footer에 체크섬 기록 → 원자적 커밋 포인트
이중 CP의 의미: CP 영역은 pack #0, pack #1 두 곳에 번갈아 기록됩니다. 마운트 시 checkpoint_ver이 더 큰 쪽이 최신 유효 CP입니다. CP 기록 중 비정상 종료가 발생해도 이전 CP가 유효하므로 데이터 손실을 방지합니다.

Roll-Forward 복구

F2FS는 마지막 체크포인트 이후에 fsync()된 데이터를 roll-forward 기법으로 복구합니다:

  1. 마운트 시 최신 CP 로드
  2. CP 이후에 기록된 노드 블록을 Main Area에서 스캔
  3. fsync 마크(FADVISE_FSYNC_BIT)가 있는 노드의 데이터를 복구
  4. NAT/SIT 갱신하여 파일시스템 상태 일관성 확보
/* fs/f2fs/recovery.c */
static int f2fs_recover_fsync_data(struct f2fs_sb_info *sbi, bool check_only)
{
    /* Step 1: curseg 이후의 노드 스캔 */
    find_fsync_dnodes(sbi, &inode_list, check_only);
    /* Step 2: 유효한 fsync 노드의 데이터 복구 */
    do_recover_data(sbi, &inode_list);
    /* Step 3: 복구 완료 후 체크포인트 */
    f2fs_write_checkpoint(sbi, CP_RECOVERY_FLAG);
    return 0;
}

가비지 컬렉션 (GC)

로그 구조 파일시스템에서 GC는 필수적입니다. 무효화된 블록이 포함된 세그먼트를 정리하여 깨끗한 세그먼트를 확보합니다.

GC 모드

모드트리거대상 선택특징
Background GC (BG_GC)커널 스레드 주기적 실행Cost-Benefit유휴 시 점진적, I/O 영향 최소
Foreground GC (FG_GC)여유 세그먼트 부족Greedy즉시 공간 확보, 지연 발생

대상 선택 알고리즘

Greedy 정책 (Foreground GC): 유효 블록이 가장 적은 세그먼트를 선택합니다. 이동할 데이터가 적어 빠르게 공간을 확보할 수 있습니다.

Cost-Benefit 정책 (Background GC): 유효 블록 수뿐 아니라 세그먼트의 나이(age)도 고려합니다. 오래된 세그먼트의 데이터는 Cold 데이터일 확률이 높아, 이동 후 다시 무효화될 가능성이 낮습니다:

/* Cost-Benefit 공식:
 * cost = (1 - u) / (2 * u * age)
 * u = valid_blocks / total_blocks (utilization)
 * age = current_time - segment_mtime
 * 비용이 낮은 세그먼트가 GC에 유리 */

GC 실행 흐름

/* fs/f2fs/gc.c */
static int f2fs_gc(struct f2fs_sb_info *sbi, struct f2fs_gc_control *gc_control)
{
    /* 1. victim 세그먼트 선택 */
    f2fs_get_victim(sbi, &segno, gc_type, type, alloc_mode, age);

    /* 2. 유효 블록 이동 */
    do_garbage_collect(sbi, segno, &gc_list, gc_type, force);
    /*   - 데이터 블록: 새 COLD_DATA 세그먼트로 복사 */
    /*   - 노드 블록: 새 노드 세그먼트로 복사 */
    /*   - SIT/NAT 갱신 */

    /* 3. 정리된 세그먼트를 free 세그먼트로 반환 */
    return seg_freed;
}

GC 튜닝 파라미터

sysfs 파라미터기본값설명
gc_min_sleep_time30000 (30s)BG GC 최소 대기 시간 (ms)
gc_max_sleep_time60000 (60s)BG GC 최대 대기 시간 (ms)
gc_no_gc_sleep_time300000 (5m)GC 불필요 시 대기 시간
gc_idle00=비활성, 1=BG GC, 2=FG GC 강제
gc_urgent00=일반, 1=저속 GC, 2=고속 GC, 3=최고속
gc_urgent_high_limit-긴급 GC 공간 상한 (사용률 %)

인라인 데이터 & 인라인 디렉토리

소형 파일과 디렉토리에 대해 별도의 데이터 블록을 할당하지 않고, inode 블록 내부의 여유 공간에 직접 저장합니다.

인라인 데이터

F2FS inode는 4KB 블록을 차지하며, 메타데이터 이후의 여유 공간(약 3.4KB)에 소형 파일의 데이터를 직접 저장할 수 있습니다:

/* fs/f2fs/f2fs.h */
#define MAX_INLINE_DATA(inode) \
    (sizeof(__le32) * (DEF_ADDRS_PER_INODE - \
     get_inline_xattr_addrs(inode) - \
     F2FS_INLINE_XATTR_ADDRS - 1))
/* 보통 ~3,488 bytes까지 인라인 저장 가능 */

인라인 디렉토리

소규모 디렉토리(엔트리 수가 적은 경우)도 inode 블록 내에 저장됩니다:

/* fs/f2fs/f2fs.h */
#define NR_INLINE_DENTRY(inode) \
    (MAX_INLINE_DATA(inode) * BITS_PER_BYTE / \
     ((SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN) * BITS_PER_BYTE + 1))
/* 약 20~30개 엔트리까지 인라인 저장 가능 */
Android 효과: 모바일 환경에서는 소형 파일(설정, 캐시)이 매우 많습니다. 인라인 데이터/디렉토리로 이러한 파일의 I/O를 크게 줄여 앱 실행 속도가 개선됩니다.

Extent Cache

F2FS는 논리 블록 주소 → 물리 블록 주소 매핑을 인메모리 extent 캐시로 가속합니다. Red-black tree로 관리되며, 빈번한 파일 읽기에서 NAT/노드 탐색을 우회합니다:

/* fs/f2fs/extent_cache.c */
struct extent_info {
    unsigned int fofs;    /* 파일 내 시작 오프셋 (블록 단위) */
    unsigned int len;     /* 연속 블록 수 */
    block_t      blk;     /* 물리 시작 블록 주소 */
};

struct extent_node {
    struct rb_node rb_node;    /* rb-tree 노드 */
    struct extent_info ei;     /* extent 정보 */
    struct list_head list;    /* LRU 리스트 */
};

압축 (Compression)

F2FS는 Linux 5.4부터 투명 파일 압축을 지원합니다. 클러스터(cluster) 단위로 연속 블록을 압축하여 저장 공간을 절약합니다.

지원 알고리즘

알고리즘커널 설정특징
LZOCONFIG_F2FS_FS_LZO빠른 압축/해제, 낮은 압축률
LZ4CONFIG_F2FS_FS_LZ4매우 빠른 해제, 모바일 환경 최적
ZSTDCONFIG_F2FS_FS_ZSTD높은 압축률, 상대적으로 느림
LZORLECONFIG_F2FS_FS_LZORLELZO + RLE, LZO 개선판

클러스터 기반 압축

파일을 고정 크기 클러스터(기본 16 블록 = 64KB)로 분할한 후, 각 클러스터를 독립적으로 압축합니다:

/* 클러스터 압축 구조 (16블록 클러스터 예시)
 *
 * 원본: [blk0][blk1]...[blk15]  (16 * 4KB = 64KB)
 *     ↓ LZ4 압축
 * 압축: [cblk0][cblk1]...[cblk9][NULL]...[NULL]
 *       10블록(40KB) 사용 + 6블록 절약
 *
 * NULL 블록은 공간 반환 (sparse block)
 */
/* fs/f2fs/compress.c */
static int f2fs_compress_pages(struct compress_ctx *cc)
{
    const struct f2fs_compress_ops *cops =
        f2fs_cops[F2FS_I(cc->inode)->i_compress_algorithm];

    /* 클러스터 페이지를 연속 버퍼에 복사 */
    f2fs_compress_gather_pages(cc);

    /* 알고리즘별 압축 수행 */
    ret = cops->compress_pages(cc);

    /* 압축 결과가 원본보다 크면 비압축 저장 */
    if (cc->nr_cpages >= cc->nr_rpages)
        return -EAGAIN;
    return 0;
}

압축 사용법

# 파일시스템 레벨 압축 활성화 (마운트 옵션)
mount -t f2fs -o compress_algorithm=lz4,compress_log_size=4 /dev/sda1 /mnt

# 특정 파일/디렉토리에 압축 속성 설정
f2fs_io setflags compression /mnt/data
chattr +c /mnt/data      # FS_COMPR_FL

# 마운트 옵션
compress_algorithm=lz4     # 압축 알고리즘
compress_log_size=4        # 클러스터 크기 (2^N 블록, 4=16블록)
compress_extension=*.log   # 자동 압축 대상 확장자
compress_chksum            # 압축 데이터 체크섬 검증

암호화 & 무결성

fscrypt (파일 레벨 암호화)

F2FS는 Linux 커널의 fscrypt 프레임워크를 사용하여 파일 단위 암호화를 지원합니다. Android에서 FBE(File-Based Encryption)의 기반 기술입니다:

# 디렉토리에 암호화 정책 설정
fscryptctl set_policy --contents=AES-256-XTS --filenames=AES-256-CTS /mnt/private/

# F2FS 마운트 옵션
mount -t f2fs -o inlinecrypt /dev/sda1 /mnt  # 하드웨어 inline encryption

fsverity (무결성 검증)

fs-verity는 파일의 읽기 전용 인증 메커니즘으로, Merkle tree 기반의 무결성 검증을 제공합니다:

대소문자 무시 (Casefold)

F2FS는 디렉토리별 대소문자 무시(case-insensitive) 기능을 지원합니다. Android에서 Windows/macOS 호환 파일 접근에 사용됩니다:

# casefold 활성화 (mkfs 시)
mkfs.f2fs -O casefold /dev/sda1

# 디렉토리에 casefold 속성 설정
chattr +F /mnt/shared  # 이후 생성되는 파일에 대소문자 무시 적용

다중 디바이스 & Zoned Storage

다중 디바이스 지원

F2FS는 최대 8개의 블록 디바이스로 구성된 볼륨을 지원합니다. Device Mapper 없이 파일시스템 레벨에서 직접 다중 디바이스를 관리합니다:

# 다중 디바이스로 F2FS 생성
mkfs.f2fs -f /dev/sda /dev/sdb /dev/sdc

# 메타데이터는 첫 번째 디바이스, 데이터는 분산 저장

Zoned Storage (ZNS/SMR)

F2FS의 로그 구조 설계는 Zoned Storage와 자연스럽게 호환됩니다. ZNS(Zoned Namespaces) SSD와 SMR(Shingled Magnetic Recording) HDD를 직접 지원합니다:

# ZNS SSD에 F2FS 생성
mkfs.f2fs -f -m /dev/nvme0n1   # -m: zoned device mode

# Conventional + Sequential 혼합 구성
mkfs.f2fs -f -c /dev/nvme0n2 /dev/nvme0n1  # conventional + zoned

Android 통합

F2FS는 Android 기기의 /data (userdata) 파티션에 광범위하게 사용됩니다. Android 환경에 특화된 여러 기능이 있습니다:

Android 최적화 기능

기능설명
Atomic writeSQLite WAL 파일의 원자적 갱신. F2FS_IOC_START_ATOMIC_WRITE / F2FS_IOC_COMMIT_ATOMIC_WRITE
GC urgent modeIDLE 상태에서 공격적 GC 실행. Android JobScheduler와 연계
Pin fileGC에 의한 이동 방지. 대용량 파일(OBB, 미디어)의 fragmentation 방지
FBE (File-Based Encryption)fscrypt 기반 파일별 암호화. CE/DE 키 분리
fsverityAPK Signature Scheme v4 인증
Checkpoint disableOTA 업데이트 중 CP 비활성화로 A/B 롤백 지원
CompressionLZ4 압축으로 /data 용량 절약 (Android 12+)

Atomic Write

Android의 SQLite 데이터베이스는 WAL(Write-Ahead Log) 모드를 사용합니다. F2FS의 atomic write는 SQLite의 fsync 빈도를 줄이면서도 데이터 무결성을 보장합니다:

/* Atomic write ioctl 흐름 */
ioctl(fd, F2FS_IOC_START_ATOMIC_WRITE);   /* COW 시작 */
write(fd, data, len);                       /* 별도 공간에 기록 */
ioctl(fd, F2FS_IOC_COMMIT_ATOMIC_WRITE);  /* 원자적 커밋 (또는 ABORT) */

Android 마운트 옵션 예시

# 일반적인 Android /data 마운트 옵션
/dev/block/by-name/userdata /data f2fs \
    noatime,nosuid,nodev,discard,fsync_mode=nobarrier,\
    reserve_root=32768,resgid=1065,\
    inlinecrypt,checkpoint_merge,\
    compress_algorithm=lz4,compress_log_size=3,\
    compress_extension=*.apk,compress_extension=*.dex

핵심 커널 자료구조

f2fs_sb_info

F2FS 파일시스템 인스턴스의 중앙 자료구조입니다. 마운트 시 할당되며 모든 서브시스템이 참조합니다:

/* fs/f2fs/f2fs.h */
struct f2fs_sb_info {
    struct super_block     *sb;            /* VFS superblock */
    struct f2fs_super_block *raw_super;     /* 온디스크 superblock */
    struct f2fs_checkpoint  *ckpt;          /* 현재 체크포인트 */

    /* 세그먼트 관리 */
    struct f2fs_sm_info    *sm_info;       /* 세그먼트 매니저 */
    struct sit_info        *sit_info;      /* SIT 관리 */
    struct free_segmap_info *free_info;    /* 여유 세그먼트 비트맵 */
    struct dirty_seglist_info *dirty_info; /* dirty 세그먼트 */
    struct curseg_info     *curseg_array;  /* 6개 current segment */

    /* 노드 관리 */
    struct f2fs_nm_info    *nm_info;       /* 노드 매니저 (NAT) */

    /* GC */
    struct f2fs_gc_kthread *gc_thread;     /* BG GC 스레드 */
    unsigned int            cur_victim_sec; /* 현재 GC 대상 */

    /* 통계 & 디버깅 */
    struct f2fs_stat_info  *stat_info;     /* /sys/kernel/debug/f2fs */
    atomic_t                nr_pages[NR_COUNT_TYPE]; /* 페이지 카운터 */
};

f2fs_inode_info

/* fs/f2fs/f2fs.h */
struct f2fs_inode_info {
    struct inode       vfs_inode;      /* VFS inode (임베디드) */
    unsigned long       i_flags;        /* F2FS inode 플래그 */
    unsigned char       i_advise;       /* fadvise 힌트 */
    unsigned char       i_dir_level;    /* 디렉토리 해시 레벨 */
    unsigned int        i_current_depth; /* 디렉토리 현재 깊이 */
    unsigned int        i_pino;         /* 부모 inode 번호 */
    umode_t             i_acl_mode;     /* ACL 모드 */

    /* 인라인 */
    unsigned int        i_inline_xattr_size; /* 인라인 xattr 크기 */

    /* 압축 */
    unsigned char       i_compress_algorithm; /* 압축 알고리즘 */
    unsigned char       i_log_cluster_size;  /* 클러스터 크기 (log2) */
    unsigned int        i_cluster_size;      /* 클러스터 블록 수 */

    /* extent cache */
    struct extent_tree  *extent_tree[NR_EXTENT_CACHES];
    struct rw_semaphore  i_gc_rwsem[2]; /* GC 보호 */
};

curseg_info

/* fs/f2fs/segment.h */
struct curseg_info {
    struct mutex         curseg_mutex;     /* 세그먼트 잠금 */
    struct f2fs_summary_block *sum_blk;   /* 요약 블록 캐시 */
    struct rw_semaphore  journal_rwsem;    /* 저널 잠금 */
    unsigned int         segno;            /* 현재 세그먼트 번호 */
    unsigned short       next_blkoff;      /* 다음 블록 오프셋 */
    unsigned int         zone;             /* 현재 존 번호 */
    unsigned int         next_segno;       /* 다음 세그먼트 */
    int                  alloc_type;       /* LFS or SSR */
    bool                 inited;           /* 초기화 여부 */
};

성능 튜닝

주요 마운트 옵션

옵션설명
noatimeatime 갱신 비활성화 (쓰기 감소)
discard / nodiscard실시간 TRIM 활성/비활성
fsync_mode=posix|strict|nobarrierfsync 동작 모드. nobarrier는 배터리 백업 환경용
checkpoint_merge다중 체크포인트 요청을 병합하여 I/O 감소
gc_mergeGC I/O를 체크포인트와 병합
iostat/sys/kernel/debug/f2fs에 I/O 통계 활성화
mode=adaptive|lfsLFS 전용 모드 (zoned device) 또는 적응 모드
active_logs=2|4|6활성 로그 헤드 수 (기본 6)
alloc_mode=default|reuse세그먼트 할당 모드

sysfs 튜닝 파라미터

F2FS는 /sys/fs/f2fs/<devname>/ 경로에 다양한 런타임 튜닝 파라미터를 제공합니다:

# 주요 sysfs 파라미터 확인/설정
echo 40 > /sys/fs/f2fs/sda1/gc_urgent_sleep_time   # GC 간격 (ms)
echo 100 > /sys/fs/f2fs/sda1/min_seq_blocks        # 순차 쓰기 판단 블록 수
cat /sys/fs/f2fs/sda1/gc_urgent                     # 긴급 GC 모드
cat /sys/fs/f2fs/sda1/dirty_segments                # dirty 세그먼트 수
cat /sys/fs/f2fs/sda1/free_segments                 # 여유 세그먼트 수
cat /sys/fs/f2fs/sda1/ovp_segments                  # 오버프로비저닝 세그먼트
cat /sys/fs/f2fs/sda1/lifetime_write_kbytes         # 누적 기록량

I/O 스케줄러 조합

권장 조합: F2FS + eMMC/SSD에서는 none(noop) 또는 mq-deadline I/O 스케줄러를 권장합니다. F2FS 자체가 I/O 순서를 최적화하므로 복잡한 스케줄러는 불필요합니다.
# I/O 스케줄러 설정
echo none > /sys/block/sda/queue/scheduler

# discard granularity 확인
cat /sys/block/sda/queue/discard_granularity

관리 도구 (f2fs-tools)

f2fs-tools 패키지는 F2FS 파일시스템 생성, 검사, 디버깅을 위한 유저스페이스 도구를 제공합니다:

mkfs.f2fs

# 기본 생성
mkfs.f2fs /dev/sda1

# 주요 옵션
mkfs.f2fs -f \                     # 강제 포맷
  -l "myvolume" \                # 볼륨 라벨
  -O extra_attr,inode_checksum \   # feature 활성화
  -O encrypt,verity,compression \  # 보안/압축 feature */
  -O casefold \                    # 대소문자 무시
  -s 1 \                            # 섹션 당 세그먼트 수
  -z 1 \                            # 존 당 섹션 수
  -o 5 \                            # 오버프로비저닝 비율 (%) */
  /dev/sda1

fsck.f2fs

# 검사만 수행
fsck.f2fs /dev/sda1

# 자동 복구
fsck.f2fs -a /dev/sda1     # auto repair
fsck.f2fs -f /dev/sda1     # 강제 전체 검사
fsck.f2fs -p /dev/sda1     # preen (가벼운 검사)

dump.f2fs

# superblock 정보 출력
dump.f2fs /dev/sda1

# 특정 inode 덤프
dump.f2fs -i 3 /dev/sda1      # inode #3 (root dir)

# NAT 영역 덤프
dump.f2fs -n 0~100 /dev/sda1 # NAT 엔트리 0~100

# SIT 영역 덤프
dump.f2fs -s 0~10 /dev/sda1  # SIT 엔트리 0~10

기타 도구

도구설명
sload.f2fs오프라인 상태에서 디렉토리 트리를 F2FS 이미지에 로드
resize.f2fsF2FS 볼륨 크기 조절 (오프라인)
defrag.f2fs파일 단편화 해소
f2fs_ioF2FS ioctl 래퍼 (압축, pin_file 등)
f2fstatF2FS 통계 모니터링 (debugfs 기반)

ext4 / Btrfs / XFS 비교

항목F2FSext4BtrfsXFS
설계 철학Flash 최적화 LFS범용 저널링COW B-tree고성능 저널링
쓰기 패턴Append-only (out-of-place)In-place + 저널COW (out-of-place)In-place + WAL
메타데이터 구조NAT/SIT/SSA 테이블Extent Tree, 비트맵COW B-treeB+tree (AG별)
GC 필요성필수불필요불필요 (balance 별도)불필요
Flash 최적화네이티브제한적일부 (COW)없음
압축LZO/LZ4/ZSTD없음ZLIB/LZO/ZSTD없음
암호화fscrypt (파일 레벨)fscrypt없음 (계획 중)없음
스냅샷없음없음네이티브없음
최대 볼륨16TB1EB16EB8EB
최대 파일3.94TB16TB16EB8EB
랜덤 쓰기매우 우수보통우수 (COW)보통
순차 읽기우수우수우수매우 우수
모바일 적합성최적양호부적합부적합
Zoned Storage네이티브없음지원없음
주 사용처모바일, SSD, IoT범용 서버/데스크톱NAS, 데스크톱엔터프라이즈 서버
선택 가이드: eMMC/UFS 기반 모바일 기기 → F2FS, 범용 서버 → ext4 또는 XFS, 스냅샷/체크섬 필요 → Btrfs, 대용량 엔터프라이즈 → XFS.