F2FS 파일시스템 심화
Flash-Friendly File System(F2FS)의 로그 구조 설계, NAT/SIT/SSA 온디스크 레이아웃, 멀티 헤드 로깅, GC, 체크포인트, 압축, fscrypt, 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의 물리적 제약을 먼저 알아야 합니다:
| 특성 | HDD | NAND 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 특성을 고려하면 성능과 수명을 크게 개선할 수 있습니다.
설계 목표
- Flash 친화적 쓰기: 순차 쓰기(append-only) 패턴으로 FTL 부하 최소화
- Wandering tree 문제 해결: NAT을 통한 간접 주소 변환으로 연쇄 갱신 방지
- 다중 로그 헤드: Hot/Warm/Cold 데이터 분리로 GC 효율 극대화
- 빠른 복구: 체크포인트 + roll-forward 복구로 빠른 마운트
- 모바일 환경 최적화: 빈번한 fsync, 소형 파일, 랜덤 쓰기 패턴 대응
F2FS 역사
| 시기 | 이정표 |
|---|---|
| 2012.10 | Samsung, LSFMM에서 F2FS 설계 발표 |
| 2013.02 | Linux 3.8 메인라인 머지 (Jaegeuk Kim) |
| 2014 | Motorola Moto G, F2FS 최초 상용 적용 |
| 2016 | Huawei P9, F2FS 기본 파일시스템 채택 |
| 2018 | Google Pixel 3, userdata 파티션 F2FS 채택 |
| 2019 | 압축(LZO/LZ4) 지원, zoned storage 지원 |
| 2020 | ZSTD 압축 지원, 다중 디바이스 지원 |
| 2022 | Android 13+ 기본 파일시스템으로 광범위 채택 |
아키텍처 & 온디스크 레이아웃
F2FS는 전체 볼륨을 고정 크기 세그먼트(segment, 2MB)로 분할하고, 세그먼트를 묶어 섹션(section), 섹션을 묶어 존(zone)을 구성합니다. 온디스크 레이아웃은 6개 영역으로 나뉩니다:
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_NODE | Hot | 직접 노드 블록 (inode) |
| CURSEG_WARM_NODE | Warm | 간접 노드 블록 |
| CURSEG_COLD_NODE | Cold | 간접-간접 노드 블록 |
| CURSEG_HOT_DATA | Hot | 디렉토리 엔트리 |
| CURSEG_WARM_DATA | Warm | 일반 파일 데이터 |
| CURSEG_COLD_DATA | Cold | 멀티미디어, 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 (Log-Structured, Normal): 깨끗한(clean) 세그먼트를 순차적으로 사용. 최적의 순차 쓰기 패턴
- SSR (Slack Space Recycling): 이미 부분적으로 사용된 세그먼트의 빈 블록을 채움. 여유 공간이 부족할 때 GC 없이 공간 확보
NAT (Node Address Table)
NAT은 F2FS의 핵심 혁신으로, 전통적 LFS의 wandering tree 문제를 해결합니다.
Wandering Tree 문제
전통적 LFS에서 데이터 블록이 새 위치에 기록되면, 이를 가리키는 간접 노드 → inode → inode map 전체가 연쇄적으로 갱신되어야 합니다. F2FS는 NAT 테이블을 도입하여 노드 ID와 물리 주소 사이에 간접 계층을 둡니다:
/* 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 활용
- GC 대상 선택: 유효 블록이 적은 세그먼트를 GC 대상으로 선택 (비용-이득 분석)
- 여유 공간 계산: 전체 유효 블록 수에서 여유 세그먼트 수 산출
- SSR 모드: 유효 블록 비트맵을 참조하여 빈 블록 위치 결정
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 엔트리를 통해 해당 블록의 부모 노드를 찾고, 노드의 포인터를 갱신합니다:
- GC 대상 세그먼트 선택 (SIT 기반)
- SIT 비트맵에서 유효 블록 식별
- SSA에서 각 유효 블록의 부모 노드(nid) 조회
- NAT에서 부모 노드의 물리 주소 조회
- 유효 블록을 새 세그먼트로 복사
- 부모 노드의 포인터 갱신 (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 저널 */
};
체크포인트 절차
- 모든 dirty 노드/데이터 페이지 flush
- dirty NAT/SIT 엔트리를 NAT/SIT 영역에 기록 (또는 CP 내 저널에 저장)
- CP 팩 기록: header → orphan inode → 요약(summary) → footer
- CP 팩의 footer에 체크섬 기록 → 원자적 커밋 포인트
checkpoint_ver이 더 큰 쪽이 최신 유효 CP입니다. CP 기록 중 비정상 종료가 발생해도 이전 CP가 유효하므로 데이터 손실을 방지합니다.
Roll-Forward 복구
F2FS는 마지막 체크포인트 이후에 fsync()된 데이터를 roll-forward 기법으로 복구합니다:
- 마운트 시 최신 CP 로드
- CP 이후에 기록된 노드 블록을 Main Area에서 스캔
- fsync 마크(
FADVISE_FSYNC_BIT)가 있는 노드의 데이터를 복구 - 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_time | 30000 (30s) | BG GC 최소 대기 시간 (ms) |
gc_max_sleep_time | 60000 (60s) | BG GC 최대 대기 시간 (ms) |
gc_no_gc_sleep_time | 300000 (5m) | GC 불필요 시 대기 시간 |
gc_idle | 0 | 0=비활성, 1=BG GC, 2=FG GC 강제 |
gc_urgent | 0 | 0=일반, 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까지 인라인 저장 가능 */
FI_INLINE_DATA플래그가 설정된 inode에 적용- 파일이 커지면 자동으로 일반 데이터 블록으로 변환 (
f2fs_convert_inline_inode()) - 추가 블록 할당이 불필요하여 공간/성능 모두 이점
인라인 디렉토리
소규모 디렉토리(엔트리 수가 적은 경우)도 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개 엔트리까지 인라인 저장 가능 */
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 리스트 */
};
- Read extent cache: 논리→물리 매핑 캐싱, 순차 읽기에서 높은 hit rate
- Age extent cache: 블록의 나이 정보 캐싱, GC 비용 계산 가속
- LRU 정책으로 메모리 사용량 제어 (
extent_cache_count)
압축 (Compression)
F2FS는 Linux 5.4부터 투명 파일 압축을 지원합니다. 클러스터(cluster) 단위로 연속 블록을 압축하여 저장 공간을 절약합니다.
지원 알고리즘
| 알고리즘 | 커널 설정 | 특징 |
|---|---|---|
| LZO | CONFIG_F2FS_FS_LZO | 빠른 압축/해제, 낮은 압축률 |
| LZ4 | CONFIG_F2FS_FS_LZ4 | 매우 빠른 해제, 모바일 환경 최적 |
| ZSTD | CONFIG_F2FS_FS_ZSTD | 높은 압축률, 상대적으로 느림 |
| LZORLE | CONFIG_F2FS_FS_LZORLE | LZO + 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)의 기반 기술입니다:
- 파일 내용: AES-256-XTS
- 파일 이름: AES-256-CTS 또는 AES-256-HCTR2
- 디렉토리별 독립 암호화 정책 설정 가능
- inline encryption 하드웨어 가속 지원 (UFS/eMMC)
# 디렉토리에 암호화 정책 설정
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 기반의 무결성 검증을 제공합니다:
- 파일 내용에 대한 Merkle tree 해시 생성
- 읽기 시 각 블록의 해시를 실시간 검증
- Android APK 서명 검증(APK Signature Scheme v4)에 활용
대소문자 무시 (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를 직접 지원합니다:
- F2FS 섹션(section)을 존(zone)에 직접 매핑
- 순차 쓰기만 허용하는 존 제약에 LFS 쓰기 패턴이 자연스럽게 부합
- GC 시 존 리셋(zone reset)으로 전체 존을 한번에 해제
- Conventional zone을 메타데이터/랜덤 쓰기에, Sequential zone을 데이터에 활용
# 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 write | SQLite WAL 파일의 원자적 갱신. F2FS_IOC_START_ATOMIC_WRITE / F2FS_IOC_COMMIT_ATOMIC_WRITE |
| GC urgent mode | IDLE 상태에서 공격적 GC 실행. Android JobScheduler와 연계 |
| Pin file | GC에 의한 이동 방지. 대용량 파일(OBB, 미디어)의 fragmentation 방지 |
| FBE (File-Based Encryption) | fscrypt 기반 파일별 암호화. CE/DE 키 분리 |
| fsverity | APK Signature Scheme v4 인증 |
| Checkpoint disable | OTA 업데이트 중 CP 비활성화로 A/B 롤백 지원 |
| Compression | LZ4 압축으로 /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; /* 초기화 여부 */
};
성능 튜닝
주요 마운트 옵션
| 옵션 | 설명 |
|---|---|
noatime | atime 갱신 비활성화 (쓰기 감소) |
discard / nodiscard | 실시간 TRIM 활성/비활성 |
fsync_mode=posix|strict|nobarrier | fsync 동작 모드. nobarrier는 배터리 백업 환경용 |
checkpoint_merge | 다중 체크포인트 요청을 병합하여 I/O 감소 |
gc_merge | GC I/O를 체크포인트와 병합 |
iostat | /sys/kernel/debug/f2fs에 I/O 통계 활성화 |
mode=adaptive|lfs | LFS 전용 모드 (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 스케줄러 조합
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.f2fs | F2FS 볼륨 크기 조절 (오프라인) |
defrag.f2fs | 파일 단편화 해소 |
f2fs_io | F2FS ioctl 래퍼 (압축, pin_file 등) |
f2fstat | F2FS 통계 모니터링 (debugfs 기반) |
ext4 / Btrfs / XFS 비교
| 항목 | F2FS | ext4 | Btrfs | XFS |
|---|---|---|---|---|
| 설계 철학 | 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-tree | B+tree (AG별) |
| GC 필요성 | 필수 | 불필요 | 불필요 (balance 별도) | 불필요 |
| Flash 최적화 | 네이티브 | 제한적 | 일부 (COW) | 없음 |
| 압축 | LZO/LZ4/ZSTD | 없음 | ZLIB/LZO/ZSTD | 없음 |
| 암호화 | fscrypt (파일 레벨) | fscrypt | 없음 (계획 중) | 없음 |
| 스냅샷 | 없음 | 없음 | 네이티브 | 없음 |
| 최대 볼륨 | 16TB | 1EB | 16EB | 8EB |
| 최대 파일 | 3.94TB | 16TB | 16EB | 8EB |
| 랜덤 쓰기 | 매우 우수 | 보통 | 우수 (COW) | 보통 |
| 순차 읽기 | 우수 | 우수 | 우수 | 매우 우수 |
| 모바일 적합성 | 최적 | 양호 | 부적합 | 부적합 |
| Zoned Storage | 네이티브 | 없음 | 지원 | 없음 |
| 주 사용처 | 모바일, SSD, IoT | 범용 서버/데스크톱 | NAS, 데스크톱 | 엔터프라이즈 서버 |