NTFS 파일시스템 심화

Windows 기본 파일시스템인 NTFS를 Linux 관점에서 심층 분석합니다. MFT 레코드와 속성(attribute) 기반 데이터 모델, 디렉터리 인덱스(B-tree) 검색, `$LogFile` 트랜잭션 저널 복구 개념, ACL/보안 식별자/대체 데이터 스트림(ADS) 기능, ntfs3 드라이버의 읽기·쓰기 경로와 호환성 제약, 대용량 볼륨 운용 시 성능·무결성 튜닝 포인트를 상세히 설명합니다.

전제 조건: VFSBlock I/O 서브시스템 문서를 먼저 읽으세요. 디스크 기반 파일시스템은 저널링·할당기·복구 정책 차이가 핵심이므로, I/O 경로와 on-disk 구조를 함께 봐야 합니다.
일상 비유: 이 주제는 창고 배치와 재고 장부 운영과 비슷합니다. 공간 배치 규칙과 기록 정책이 달라지면 성능·복구·무결성 특성이 크게 달라집니다.

핵심 요약

  • 계층 이해 — VFS, 캐시, 하위 FS 경계를 구분합니다.
  • 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
  • 저장 정책 — 저널링/압축/할당 정책 차이를 비교합니다.
  • 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
  • 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.

단계별 이해

  1. 경계 계층 파악
    요청이 VFS에서 어디로 내려가는지 확인합니다.
  2. 메타/데이터 분리
    어느 경로에서 무엇이 갱신되는지 나눠 봅니다.
  3. 동기화/플러시 확인
    쓰기 반영 시점과 순서를 검증합니다.
  4. 복구 시나리오 점검
    비정상 종료 후 일관성 회복을 확인합니다.
관련 표준: NTFS(New Technology File System)는 Microsoft가 Windows NT를 위해 설계한 저널링 파일시스템입니다. Linux 커널 5.15에서 Paragon ntfs3 드라이버가 병합되어 일반적인 읽기/쓰기 워크로드를 커널 네이티브로 처리할 수 있게 되었습니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

개요 & 역사

NTFS(New Technology File System)는 1993년 Windows NT 3.1과 함께 출시된 Microsoft의 주력 파일시스템입니다. FAT/HPFS를 대체하기 위해 설계되었으며, 메타데이터 저널링, ACL 기반 보안, 압축, 암호화(EFS), Alternate Data Streams 등 엔터프라이즈 기능을 제공합니다. 현재까지 Windows 운영체제의 기본 파일시스템으로 사용되고 있습니다.

NTFS 버전 역사

버전OS주요 변경사항
1.0NT 3.1 (1993)최초 릴리스, 기본 MFT/저널링 구조
1.1NT 3.5 (1994)압축 파일 지원 (LZNT1)
1.2NT 4.0 (1996)Security Descriptor 스트림, 디스크 쿼터 기반 구축
3.0Windows 2000디스크 쿼터, EFS 암호화, Reparse Point, $UsnJrnl 변경 저널, Sparse 파일
3.1Windows XP+$MFT 미러 확장, 셀프 힐링(Self-Healing), MFT 엔트리 재사용 시퀀스 번호 강화
버전 번호: NTFS 2.x는 존재하지 않습니다. Windows 2000에서 1.2 → 3.0으로 직접 점프했으며, 현재 모든 Windows(XP~11/Server 2025)는 NTFS 3.1을 사용합니다.

NTFS 주요 스펙

항목
최대 볼륨 크기256 TiB (클러스터 64 KiB 기준), 이론적 264 클러스터
최대 파일 크기256 TiB (볼륨 크기에 의존)
최대 파일명 길이255 UTF-16 문자
최대 경로 길이32,767 UTF-16 문자 (Win32 API 제한 260자)
클러스터 크기512 B ~ 2 MiB (기본 4 KiB)
MFT 엔트리 크기1024 바이트 (고정)
저널링메타데이터 전용 (WAL 방식)
대소문자대소문자 보존, 기본적으로 대소문자 비구분
파일시스템 IDMBR: 0x07, GPT: EBD0A0A2-B9E5-4433-...

Linux에서의 NTFS 지원 개요

Linux에서 NTFS 접근은 세 가지 방식으로 발전해왔습니다:

NTFS 디스크 레이아웃

NTFS 볼륨은 크게 VBR(Volume Boot Record), MFT Zone, Data Area로 구성됩니다. 모든 것이 파일로 표현되는 것이 NTFS의 핵심 설계 원칙입니다. 디렉터리조차 특별한 속성을 가진 MFT 레코드에 불과합니다.

NTFS 볼륨 레이아웃 VBR Boot Sector + Boot Code MFT Zone $MFT (Master File Table) 볼륨의 ~12.5% 예약 Data Area 파일 데이터, 디렉터리 인덱스 Non-Resident 속성 데이터 MFT Mirror $MFTMirr 시스템 메타데이터 파일 (MFT 레코드 0~15): #0 $MFT #1 $MFTMirr #2 $LogFile #3 $Volume #4 $AttrDef #5 . (root dir) #6 $Bitmap #7 $Boot #8 $BadClus #9 $Secure #10 $UpCase #11 $Extend 레코드 #12~#15: 예약 (미사용), #16~: 일반 파일/디렉터리 $Extend 하위: $ObjId, $Quota, $Reparse, $UsnJrnl, $RmMetadata

시스템 메타데이터 파일 상세

MFT #이름역할
0$MFTMFT 자체를 기술하는 레코드 (자기 참조)
1$MFTMirr$MFT 처음 4개 레코드의 백업 (복구용)
2$LogFile트랜잭션 저널 (메타데이터 일관성 보장)
3$Volume볼륨 이름, NTFS 버전, 플래그 (dirty bit 등)
4$AttrDef속성 타입 정의 테이블
5. (root)루트 디렉터리 (\)
6$Bitmap클러스터 할당 비트맵 (1비트/클러스터)
7$BootVBR(Volume Boot Record), BPB, 부트 코드
8$BadClus불량 클러스터 목록 (Sparse 파일)
9$Secure보안 디스크립터 저장소 (ACL 중앙 관리)
10$UpCase유니코드 대문자 변환 테이블 (파일명 비교용)
11$Extend확장 메타데이터 디렉터리

VBR (Volume Boot Record) 구조

NTFS VBR은 섹터 0에 위치하며, BIOS Parameter Block(BPB)과 부트 코드를 포함합니다. 마지막 섹터에 VBR 백업이 위치합니다.

/* NTFS BPB (BIOS Parameter Block) - 오프셋 0x00~0x53 */
struct ntfs_bpb {
    uint8_t  jump[3];           /* 0x00: JMP 명령 (EB 52 90) */
    char     oem_id[8];          /* 0x03: "NTFS    " */
    uint16_t bytes_per_sector;   /* 0x0B: 보통 512 */
    uint8_t  sectors_per_cluster; /* 0x0D: 1,2,4,8...128 */
    uint16_t reserved_sectors;   /* 0x0E: 0 (사용 안 함) */
    uint8_t  unused[5];          /* 0x10~0x14: 항상 0 */
    uint8_t  media_descriptor;   /* 0x15: 0xF8 (하드디스크) */
    uint8_t  unused2[2];         /* 0x16~0x17: 항상 0 */
    uint16_t sectors_per_track;  /* 0x18: CHS 지오메트리 */
    uint16_t number_of_heads;   /* 0x1A: CHS 지오메트리 */
    uint32_t hidden_sectors;    /* 0x1C: 파티션 시작 오프셋 */
    uint32_t unused3;           /* 0x20: 항상 0 */
    uint32_t unused4;           /* 0x24: 0x00800080 */
    uint64_t total_sectors;     /* 0x28: 볼륨 전체 섹터 수 */
    uint64_t mft_lcn;           /* 0x30: $MFT 시작 LCN */
    uint64_t mftmirr_lcn;       /* 0x38: $MFTMirr 시작 LCN */
    int8_t   clusters_per_mft;  /* 0x40: MFT 레코드 크기 */
    uint8_t  padding1[3];       /* 0x41~0x43 */
    int8_t   clusters_per_idx;  /* 0x44: 인덱스 블록 크기 */
    uint8_t  padding2[3];       /* 0x45~0x47 */
    uint64_t serial_number;     /* 0x48: 볼륨 시리얼 번호 */
    uint32_t checksum;          /* 0x50: 부트 섹터 체크섬 */
};
/* 0x54~0x1FD: 부트 코드, 0x1FE~0x1FF: 0x55AA 시그니처 */
clusters_per_mft 해석: 양수이면 MFT 레코드 = 값 × 클러스터 크기. 음수이면 MFT 레코드 = 2|값| 바이트. 일반적으로 0xF6(-10)으로 설정되어 210 = 1024바이트입니다.

MFT (Master File Table) 구조

MFT는 NTFS의 핵심 자료구조로, 볼륨의 모든 파일과 디렉터리를 레코드 단위로 관리합니다. 각 MFT 엔트리(레코드)는 1024바이트 고정 크기이며, 파일의 모든 메타데이터와 작은 파일의 데이터까지 포함할 수 있습니다. MFT 자체도 하나의 파일($MFT, 레코드 #0)로 볼륨에 존재합니다.

MFT 엔트리 구조 (1024 바이트) Record Header "FILE" 시그니처 USA offset/count Fixup USA (Update Sequence Array) Attribute 1 $STANDARD_INFO (0x10, Resident) Attribute 2 $FILE_NAME (0x30, Resident) Attribute 3 $DATA (0x80, Res/Non-Res) End 0xFFFFFFFF Free 오프셋: 0x00 0x30 첫 번째 속성 오프셋 헤더 주요 필드: sequence_number: 레코드 재사용 추적 | flags: IN_USE(0x01), DIRECTORY(0x02) | base_record: 확장 레코드의 기본 레코드 참조 allocated_size: 1024 | used_size: 실제 사용 바이트 | first_attr_offset: 첫 속성 시작 위치 | lsn: $LogFile 시퀀스 번호

MFT 레코드 헤더

/* MFT Record Header (48 바이트, 오프셋 0x00~0x2F) */
struct mft_record_header {
    uint32_t magic;              /* 0x00: "FILE" (0x454C4946) */
    uint16_t usa_offset;         /* 0x04: Update Sequence Array 오프셋 */
    uint16_t usa_count;          /* 0x06: USA 엔트리 수 */
    uint64_t lsn;               /* 0x08: $LogFile 시퀀스 번호 */
    uint16_t sequence_number;    /* 0x10: 재사용 카운터 */
    uint16_t hard_link_count;    /* 0x12: 하드 링크 수 */
    uint16_t first_attr_offset;  /* 0x14: 첫 속성 시작 오프셋 */
    uint16_t flags;             /* 0x16: IN_USE(0x01), DIRECTORY(0x02) */
    uint32_t used_size;         /* 0x18: 사용 바이트 수 */
    uint32_t allocated_size;    /* 0x1C: 할당 크기 (보통 1024) */
    uint64_t base_record;       /* 0x20: 기본 레코드 참조 (확장 시) */
    uint16_t next_attr_id;      /* 0x28: 다음 속성 ID */
    uint16_t padding;           /* 0x2A: 패딩 */
    uint32_t record_number;     /* 0x2C: 이 레코드의 MFT 번호 */
};

Update Sequence Array (USA)

NTFS는 다중 섹터 전송 중 데이터 손상을 감지하기 위해 USA(Update Sequence Array)를 사용합니다. 디스크에 쓸 때, 각 512바이트 섹터의 마지막 2바이트를 USA 값으로 대체하고, 원래 값은 USA 배열에 보관합니다. 읽을 때 USA 값을 검증한 후 원래 값으로 복원합니다.

설명 요약:
  • USA 적용 과정 (1024바이트 MFT 레코드, 2개 섹터) */
  • USA 배열: [usa_value, orig_sector1_last2, orig_sector2_last2]
  • 쓰기 시:
  • sector[0].bytes[510:511] → USA[1]에 백업, usa_value로 대체
  • sector[1].bytes[510:511] → USA[2]에 백업, usa_value로 대체
  • 읽기 시:
  • sector[N].bytes[510:511] == usa_value 확인 → 불일치 시 I/O 오류
  • USA[N+1]에서 원래 값 복원

MFT 할당 전략

NTFS는 볼륨의 약 12.5%(1/8)를 MFT Zone으로 예약합니다. MFT Zone이 소진되면 Data Area에서 MFT를 확장하며, 이때 MFT 단편화가 발생할 수 있습니다. MFT는 $MFT 레코드(#0)의 Data Run을 통해 자신의 위치를 추적합니다 (자기 참조 구조).

/* Windows 레지스트리로 MFT Zone 크기 조정 */
/* HKLM\SYSTEM\CurrentControlSet\Control\FileSystem */
/* NtfsMftZoneReservation: 1(12.5%) 2(25%) 3(37.5%) 4(50%) */

/* ntfs3 드라이버에서 MFT Zone은 자동 관리 */
/* fs/ntfs3/fsntfs.c: ntfs_update_mftmirr() */

NTFS 속성 시스템

NTFS에서 파일의 모든 데이터는 속성(Attribute)으로 표현됩니다. 파일명, 타임스탬프, 보안 정보, 실제 파일 데이터까지 모두 속성입니다. 각 속성은 Resident(MFT 레코드 내부에 저장) 또는 Non-Resident(외부 클러스터에 저장)입니다.

속성 헤더 구조

/* 공통 속성 헤더 (16 바이트) */
struct attr_header {
    uint32_t type;       /* 속성 타입 코드 (0x10, 0x30, 0x80 등) */
    uint32_t length;     /* 속성 전체 길이 (헤더 포함) */
    uint8_t  non_resident; /* 0=Resident, 1=Non-Resident */
    uint8_t  name_length; /* 속성 이름 길이 (UTF-16 문자 수) */
    uint16_t name_offset; /* 속성 이름 오프셋 */
    uint16_t flags;      /* COMPRESSED(0x01), ENCRYPTED(0x4000), SPARSE(0x8000) */
    uint16_t attr_id;    /* 속성 인스턴스 ID */
};

/* Resident 속성 추가 헤더 (8 바이트) */
struct resident_attr {
    uint32_t value_length;  /* 속성 값 길이 */
    uint16_t value_offset;  /* 속성 값 오프셋 */
    uint16_t indexed_flag;  /* 인덱싱 여부 */
};

/* Non-Resident 속성 추가 헤더 (48 바이트) */
struct nonresident_attr {
    uint64_t start_vcn;       /* 시작 VCN (Virtual Cluster Number) */
    uint64_t end_vcn;         /* 마지막 VCN */
    uint16_t data_runs_offset; /* Data Run 시작 오프셋 */
    uint16_t compression_unit; /* 압축 단위 (2^N 클러스터, 보통 4) */
    uint32_t padding;
    uint64_t allocated_size;  /* 할당된 크기 (클러스터 정렬) */
    uint64_t real_size;       /* 실제 데이터 크기 */
    uint64_t initialized_size; /* 초기화된 크기 */
};

표준 속성 타입

타입 코드이름설명Resident
0x10$STANDARD_INFORMATION타임스탬프(MACB), 파일 플래그, 보안 ID, 쿼터, USN항상
0x20$ATTRIBUTE_LIST속성이 여러 MFT 레코드에 분산 시 인덱스보통
0x30$FILE_NAME파일명(UTF-16), 부모 디렉터리 참조, 타임스탬프항상
0x40$OBJECT_IDGUID 기반 파일 고유 식별자항상
0x50$SECURITY_DESCRIPTOR레거시 보안 디스크립터 (NT 4.0 이하)보통
0x60$VOLUME_NAME볼륨 이름 (NTFS 레이블)항상
0x70$VOLUME_INFORMATIONNTFS 버전, 볼륨 플래그항상
0x80$DATA파일 데이터 스트림 (기본 + ADS)가변
0x90$INDEX_ROOTB-tree 인덱스 루트 노드 (디렉터리)항상
0xA0$INDEX_ALLOCATIONB-tree 인덱스 노드 저장소Non-Res
0xB0$BITMAP인덱스/MFT 할당 비트맵가변
0xC0$REPARSE_POINTReparse Point 데이터 (심볼릭 링크, 마운트 포인트)항상
0xD0$EA_INFORMATIONExtended Attribute 정보항상
0xE0$EAExtended Attribute 데이터가변

Data Run 인코딩 (Non-Resident 속성)

Non-Resident 속성의 데이터 위치는 Data Run(Cluster Run)으로 인코딩됩니다. 각 Data Run은 가변 길이 바이트 시퀀스로, 연속 클러스터 블록을 표현합니다.

설명 요약:
  • Data Run 인코딩:
  • Header byte: [offset_size:4][length_size:4]
  • Length: length_size 바이트 (리틀엔디안, 클러스터 수)
  • Offset: offset_size 바이트 (부호 있는 리틀엔디안, LCN 오프셋)
  • 종료: 0x00 바이트
  • 예시: 0x31 0x05 0x30 0x02
  • header = 0x31: offset_size=3, length_size=1
  • length = 0x05: 5 클러스터
  • offset = 0x000230: LCN 560 (리틀엔디안 0x30 0x02 0x00)
  • → 클러스터 560~564에 5개 클러스터 할당
  • 두 번째 run의 offset은 이전 LCN으로부터의 상대 오프셋 (부호 있음)
  • Sparse run: offset_size=0 → 비할당 영역 (Sparse 파일)

Alternate Data Streams (ADS)

NTFS는 하나의 파일에 여러 $DATA 속성(스트림)을 허용합니다. 기본 스트림(이름 없음)과 하나 이상의 명명된 스트림(ADS)을 가질 수 있습니다.

# ADS 생성 및 접근 (Windows)
echo "hidden data" > file.txt:secret_stream
more < file.txt:secret_stream
dir /r file.txt    # ADS 목록 확인

# ntfs3에서 ADS 접근 (Linux)
# xattr을 통해 접근 가능
getfattr -n user.secret_stream file.txt
setfattr -n user.secret_stream -v "hidden data" file.txt
보안 주의: ADS는 보안 위협에 악용될 수 있습니다. 악성코드가 ADS에 페이로드를 숨기거나, 실행 파일을 ADS로 첨부하는 사례가 있습니다. 일부 보안 도구는 ADS를 스캔하지 않으므로 주의가 필요합니다.

NTFS 압축 (LZNT1)

NTFS는 $DATA 속성에 LZNT1 알고리즘 기반 압축을 지원합니다. 압축 단위(Compression Unit)는 기본 16개 클러스터(64 KiB)이며, 각 단위를 독립적으로 압축/해제합니다.

설명 요약:
  • LZNT1 압축 구조:
  • 16 클러스터 단위로 압축
  • 압축 후 크기가 원본보다 작으면: 압축 클러스터 + Sparse(비할당)
  • 압축 후 크기가 원본과 같거나 크면: 비압축으로 저장
  • Data Run에서의 표현:
  • 할당된 run: 실제 압축 데이터 클러스터
  • Sparse run (offset=0): 절약된 공간
  • ntfs3 마운트 옵션:
  • compress - 새 파일에 LZNT1 압축 적용
  • nocompress - 압축 비활성화 (기본)

B-tree 인덱싱

NTFS 디렉터리는 B+ tree 기반 인덱스 구조를 사용하여 파일명을 정렬하고 빠른 검색을 제공합니다. 인덱스는 $INDEX_ROOT(Resident, 루트 노드)와 $INDEX_ALLOCATION(Non-Resident, 나머지 노드)로 구성됩니다. $UpCase 테이블을 사용하여 대소문자를 구분하지 않는 유니코드 비교를 수행합니다.

NTFS B+ Tree 디렉터리 인덱스 $INDEX_ROOT (Resident) doc.txt | notes.txt | zzz.log Index Block 0 (VCN 0) a.c | backup | config Index Block 1 (VCN 1) file1 | hello | main.c Index Block 2 (VCN 2) patch | readme | test < doc.txt doc~notes notes~zzz 인덱스 엔트리 구조: MFT Reference Entry Length Content Length Flags Key ($FILE_NAME) Sub-node VCN Flags: CHILD_NODE(0x01) = 하위 노드 존재, LAST_ENTRY(0x02) = 마지막 엔트리 (키 없음, 하위 포인터만) $BITMAP 속성: Index Block 할당 상태 추적 (1비트/블록)

$INDEX_ROOT 속성

/* $INDEX_ROOT (속성 타입 0x90) */
struct index_root {
    uint32_t attr_type;          /* 인덱싱할 속성 타입 (0x30 = $FILE_NAME) */
    uint32_t collation_rule;     /* 정렬 규칙 (1 = FILENAME) */
    uint32_t index_block_size;   /* Index Block 크기 (보통 4096) */
    uint8_t  clusters_per_block; /* 블록당 클러스터 수 */
    uint8_t  padding[3];
    /* Index Node Header */
    uint32_t entries_offset;     /* 첫 엔트리 오프셋 */
    uint32_t index_size;         /* 인덱스 사용 크기 */
    uint32_t allocated_size;     /* 할당 크기 */
    uint32_t flags;              /* LARGE_INDEX(0x01) = $INDEX_ALLOCATION 사용 */
    /* ... 인덱스 엔트리 배열 ... */
};

$UpCase 정렬 규칙

NTFS 파일명 비교는 $UpCase 테이블(MFT 레코드 #10)을 사용합니다. 이 테이블은 65,536개 UTF-16 코드 포인트의 대문자 매핑을 제공하여, 대소문자를 무시한 비교를 수행합니다. Windows 버전에 따라 $UpCase 테이블이 다를 수 있으므로, ntfs3 드라이버는 볼륨의 $UpCase를 읽어 사용합니다.

저널링 ($LogFile)

NTFS 저널링은 $LogFile(MFT 레코드 #2)에 WAL(Write-Ahead Logging) 방식으로 메타데이터 변경을 기록합니다. 비정상 종료 시 $LogFile을 재생(replay)하여 파일시스템 일관성을 복구합니다. $LogFile은 파일 데이터가 아닌 메타데이터 변경만 기록합니다.

$LogFile 구조

$LogFile 레이아웃 (순환 로그 버퍼) Restart Area (페이지 0) 마지막 체크포인트 정보 Restart Area (페이지 1, 백업) 이중화 복구용 Log Record Area 순환 버퍼 — Redo/Undo 로그 볼륨 크기의 2~4%, 최대 64MiB 순환 기록 (oldest LSN 덮어씀) LSN (Log Sequence Number) 기반 WAL — 크래시 복구 시 Redo → Undo 순서로 재생

로그 레코드 타입

작업 코드설명Undo 작업
InitializeFileRecordSegmentMFT 레코드 초기화DeallocateFileRecordSegment
CreateAttribute속성 생성DeleteAttribute
DeleteAttribute속성 삭제CreateAttribute
UpdateResidentValueResident 속성 값 변경UpdateResidentValue (이전 값)
UpdateNonResidentValueNon-Resident 데이터 변경Noop 또는 역 데이터
AddIndexEntryRoot인덱스 루트에 엔트리 추가DeleteIndexEntryRoot
AddIndexEntryAllocation인덱스 할당에 엔트리 추가DeleteIndexEntryAllocation
SetBitsInNonResidentBitMap비트맵 비트 설정ClearBitsInNonResidentBitMap
UpdateMappingPairsData Run 업데이트UpdateMappingPairs (이전 값)

복구 과정 (Analysis → Redo → Undo)

설명 요약:
  • NTFS 복구 3단계:
  • Analysis Pass (분석):
  • Restart Area에서 마지막 체크포인트 LSN 읽기
  • 체크포인트 이후의 모든 로그 레코드 스캔
  • Dirty Page Table + Transaction Table 재구성
  • Redo Pass (재실행):
  • 체크포인트 이후 커밋된 트랜잭션의 Redo 레코드 순서대로 재실행
  • 디스크에 반영되지 않은 메타데이터 변경 완료
  • Undo Pass (취소):
  • 미완료 트랜잭션의 Undo 레코드를 역순으로 실행
  • 커밋되지 않은 변경 사항 롤백

$UsnJrnl (변경 저널)

$UsnJrnl($Extend\$UsnJrnl)은 $LogFile과는 별개로, 파일/디렉터리 변경 이력을 유저스페이스에 제공하는 변경 저널입니다. Windows의 검색 인덱싱, 백업, 복제 서비스가 이 저널을 사용합니다.

/* USN_RECORD_V2 구조 (축약) */
struct usn_record_v2 {
    uint32_t record_length;
    uint16_t major_version;    /* 2 */
    uint16_t minor_version;    /* 0 */
    uint64_t file_reference;   /* MFT 레코드 번호 + 시퀀스 */
    uint64_t parent_reference; /* 부모 디렉터리 MFT 참조 */
    uint64_t usn;              /* Update Sequence Number */
    uint64_t timestamp;        /* FILETIME */
    uint32_t reason;           /* USN_REASON_* 플래그 */
    uint32_t source_info;
    uint32_t security_id;
    uint32_t file_attributes;
    uint16_t file_name_length;
    uint16_t file_name_offset;
    /* wchar_t file_name[]; */
};

보안 & 권한

NTFS는 Windows NT 보안 모델에 기반한 ACL(Access Control List) 기반 권한 시스템을 구현합니다. 각 파일/디렉터리에 Security Descriptor가 연결되며, $Secure 파일에서 중앙 집중 관리합니다.

Security Descriptor

/* Security Descriptor 구조 */
struct security_descriptor {
    uint8_t  revision;         /* 항상 1 */
    uint8_t  padding;
    uint16_t control;          /* SE_DACL_PRESENT, SE_SACL_PRESENT 등 */
    uint32_t owner_offset;     /* Owner SID 오프셋 */
    uint32_t group_offset;     /* Group SID 오프셋 */
    uint32_t sacl_offset;      /* System ACL 오프셋 */
    uint32_t dacl_offset;      /* Discretionary ACL 오프셋 */
};

/* ACL → ACE(Access Control Entry) 배열 */
/* ACE 타입: ACCESS_ALLOWED, ACCESS_DENIED, SYSTEM_AUDIT 등 */
/* ACE 권한: READ_DATA, WRITE_DATA, EXECUTE, DELETE, FULL_CONTROL 등 */

$Secure 파일

NTFS 3.0+에서 Security Descriptor는 $Secure 파일(MFT #9)에서 중앙 관리됩니다. 각 고유 디스크립터에 Security ID가 할당되고, 파일의 $STANDARD_INFORMATION에 이 ID만 저장합니다.

$Secure 스트림설명
$SDS (Security Descriptor Stream)Security Descriptor 실제 데이터 (추가 전용, 순차 기록)
$SDH (Security Descriptor Hash Index)해시 → Security ID 매핑 (중복 방지)
$SII (Security ID Index)Security ID → $SDS 오프셋 매핑

Linux POSIX 권한 매핑

ntfs3 드라이버는 NTFS ACL을 Linux POSIX 권한으로 매핑합니다. 마운트 옵션을 통해 기본 uid/gid/fmask/dmask를 설정할 수 있습니다.

# ntfs3 ACL/POSIX 매핑 마운트 예시
mount -t ntfs3 -o uid=1000,gid=1000,fmask=0022,dmask=0022 /dev/sda1 /mnt

# POSIX ACL 지원 (acl 마운트 옵션 필요)
mount -t ntfs3 -o acl /dev/sda1 /mnt
getfacl /mnt/file.txt
setfacl -m u:user1:rwx /mnt/file.txt

Linux NTFS 지원

Linux에서 NTFS 접근은 세 가지 구현을 통해 이루어져 왔습니다. 레거시 ntfs.ko, FUSE 기반 ntfs-3g, 그리고 최신 커널 네이티브 ntfs3 드라이버입니다.

드라이버 비교

항목ntfs.ko (레거시)ntfs-3g (FUSE)ntfs3 (Paragon)
구현 위치커널 모듈유저스페이스 (FUSE)커널 모듈
커널 버전2.6~6.8커널 무관 (FUSE 필요)5.15+
읽기지원지원지원
쓰기제한적 (안전하지 않음)완전 지원완전 지원
NTFS 3.1부분 지원완전 지원완전 지원
압축 (LZNT1)읽기만읽기/쓰기읽기/쓰기
ACL미지원제한적POSIX ACL 매핑
ADS미지원xattr 통해 접근xattr 통해 접근
성능보통커널 전환 오버헤드네이티브 커널 성능
$LogFile 재생미지원 (dirty 시 마운트 거부)지원지원
유지보수6.9에서 삭제활발 (Tuxera)활발 (Paragon)
패키지커널 내장ntfs-3g / libntfs-3g커널 내장 (CONFIG_NTFS3_FS)
권장: Linux 5.15+ 환경에서는 ntfs3 드라이버를 사용하는 것이 성능과 기능 면에서 최적입니다. ntfs-3g는 오래된 커널이나 ntfs3에서 지원하지 않는 특수 기능이 필요할 때 대안이 됩니다.

커널 설정

# ntfs3 드라이버 커널 설정
CONFIG_NTFS3_FS=m          # ntfs3 파일시스템 드라이버
CONFIG_NTFS3_LZX_XPRESS=y  # LZX/Xpress 압축 지원 (WOF 압축 읽기)
CONFIG_NTFS3_FS_POSIX_ACL=y # POSIX ACL 지원

# FUSE (ntfs-3g 사용 시)
CONFIG_FUSE_FS=m

# 모듈 로드 확인
modprobe ntfs3
lsmod | grep ntfs3

ntfs3 드라이버 심화

ntfs3은 Paragon Software가 커널 5.15에 기여한 NTFS 읽기/쓰기 드라이버입니다. fs/ntfs3/ 디렉터리에 위치하며, 기존 ntfs.ko(fs/ntfs/)를 대체합니다.

소스 파일 구조 (fs/ntfs3/)

파일역할
super.c슈퍼블록 operations, ntfs_fill_super(), 마운트/언마운트
inode.cinode operations, ntfs_iget(), 속성 읽기/쓰기
file.cfile operations, 읽기/쓰기, fallocate, fiemap
dir.cdirectory operations, readdir, lookup
namei.cinode operations (create, link, unlink, rename)
attrib.cNTFS 속성 관리 (읽기/쓰기/삽입/삭제)
index.cB-tree 인덱스 operations (삽입/삭제/검색)
record.cMFT 레코드 I/O, USA 처리
run.cData Run 인코딩/디코딩, 클러스터 할당
bitmap.c비트맵 operations (클러스터/MFT 할당)
fsntfs.cNTFS 전용 유틸리티 (MFT Mirror, $LogFile, 볼륨 플래그)
frechet.c이름 해싱, 검색 최적화
lznt.cLZNT1 압축/해제
xattr.c확장 속성, POSIX ACL, ADS 매핑

VFS 통합 (Operations)

/* fs/ntfs3/super.c - 파일시스템 타입 등록 */
static struct file_system_type ntfs_fs_type = {
    .owner          = THIS_MODULE,
    .name           = "ntfs3",
    .init_fs_context = ntfs_init_fs_context,
    .parameters     = ntfs_fs_parameters,
    .kill_sb        = kill_block_super,
    .fs_flags       = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
};

/* fs/ntfs3/inode.c - inode operations */
const struct inode_operations ntfs_dir_inode_operations = {
    .lookup    = ntfs_lookup,
    .create    = ntfs_create,
    .link      = ntfs_link,
    .unlink    = ntfs_unlink,
    .symlink   = ntfs_symlink,
    .mkdir     = ntfs_mkdir,
    .rmdir     = ntfs_rmdir,
    .rename    = ntfs_rename,
    .setattr   = ntfs3_setattr,
    .getattr   = ntfs_getattr,
    .listxattr = ntfs_listxattr,
    .fiemap    = ntfs_fiemap,
};

/* fs/ntfs3/file.c - file operations */
const struct file_operations ntfs_file_operations = {
    .llseek        = generic_file_llseek,
    .read_iter     = ntfs_file_read_iter,
    .write_iter    = ntfs_file_write_iter,
    .mmap          = ntfs_file_mmap,
    .open          = ntfs_file_open,
    .fsync         = generic_file_fsync,
    .splice_read   = ntfs_file_splice_read,
    .splice_write  = iter_file_splice_write,
    .fallocate     = ntfs_fallocate,
    .unlocked_ioctl = ntfs_ioctl,
};

마운트 과정 (ntfs_fill_super)

설명 요약:
  • ntfs_fill_super() 주요 단계 (fs/ntfs3/super.c):
  • VBR 읽기 및 검증
  • "NTFS " OEM ID 확인
  • bytes_per_sector, sectors_per_cluster 파싱
  • MFT/MFTMirr LCN 계산
  • ntfs_sb_info 초기화
  • 슈퍼블록 정보 구조체 할당/초기화
  • 클러스터 크기, MFT 레코드 크기 설정
  • 시스템 메타데이터 파일 로드
  • $MFT(#0) → MFT 자체 Data Run 파싱
  • $MFTMirr(#1) → 미러 검증
  • $LogFile(#2) → 저널 상태 확인, 필요 시 재생
  • $Volume(#3) → dirty 플래그 확인
  • $AttrDef(#4) → 속성 정의 로드
  • $Bitmap(#6) → 클러스터 비트맵 로드
  • $Secure(#9) → 보안 디스크립터 인덱스
  • $UpCase(#10) → 대문자 변환 테이블 로드
  • 루트 디렉터리 inode 로드
  • MFT 레코드 #5 (루트 디렉터리)
  • d_make_root()로 루트 dentry 생성
  • 볼륨 dirty 플래그 설정 (읽기-쓰기 마운트 시)

xattr 매핑

ntfs3는 NTFS의 다양한 속성을 Linux xattr(확장 속성) 인터페이스로 노출합니다:

핵심 커널 자료구조 (ntfs3)

ntfs_sb_info

/* fs/ntfs3/ntfs_fs.h - 슈퍼블록 정보 (핵심 필드 발췌) */
struct ntfs_sb_info {
    struct super_block *sb;

    u32 cluster_size;          /* 클러스터 크기 (바이트) */
    u32 cluster_mask;          /* cluster_size - 1 */
    u8  cluster_bits;          /* log2(cluster_size) */
    u32 record_size;           /* MFT 레코드 크기 (보통 1024) */
    u32 index_size;            /* 인덱스 블록 크기 (보통 4096) */

    u64 maxbytes;              /* 최대 파일 크기 */
    u64 maxbytes_sparse;       /* Sparse 파일 최대 크기 */

    struct ntfs_inode *mft_ni; /* $MFT inode */
    struct ntfs_inode *security_ni; /* $Secure inode */
    struct ntfs_inode *objid_ni;   /* $ObjId inode */
    struct ntfs_inode *reparse_ni; /* $Reparse inode */
    struct ntfs_inode *usn_jrnl_ni; /* $UsnJrnl inode */

    struct wnd_bitmap used;    /* 클러스터 비트맵 ($Bitmap) */
    struct wnd_bitmap mft_bitmap; /* MFT 비트맵 */

    u16 *upcase;               /* $UpCase 테이블 (64K 엔트리) */

    struct ntfs_mount_options options; /* 마운트 옵션 */

    struct rw_semaphore vol_sem; /* 볼륨 메타데이터 락 */
    u64 maxbytes;              /* 파일시스템 최대 파일 크기 */

    u32 zone_max;              /* MFT zone 최대 클러스터 */
    bool dirty;                /* 볼륨 dirty 플래그 */
};

ntfs_inode

/* fs/ntfs3/ntfs_fs.h - NTFS inode (핵심 필드 발췌) */
struct ntfs_inode {
    struct inode vfs_inode;    /* VFS inode (반드시 첫 필드) */

    u64 mft_no;                /* MFT 레코드 번호 */
    struct MFT_REC *mi;       /* MFT 레코드 포인터 */

    struct runs_tree run;     /* Data Run 트리 (데이터 매핑) */

    union {
        struct {
            struct runs_tree dir_run; /* 디렉터리: $INDEX_ALLOCATION run */
            struct runs_tree bmp_run; /* 디렉터리: $BITMAP run */
        } dir;
        struct {
            struct rw_semaphore run_lock; /* 파일 run 보호 */
        } file;
    };

    u32 std_fa;                /* NTFS 파일 속성 ($STANDARD_INFORMATION) */
    u32 std_security_id;       /* Security ID */

    struct rw_semaphore ni_lock; /* inode 수준 락 */
};

/* ntfs_inode ↔ vfs_inode 변환 매크로 */
#define ntfs_i(inode)  container_of(inode, struct ntfs_inode, vfs_inode)

성능 튜닝 & mount 옵션

ntfs3 마운트 옵션

옵션기본값설명
uid=마운트 프로세스 uid파일 소유자 UID
gid=마운트 프로세스 gid파일 소유 그룹 GID
fmask=0022파일 권한 마스크
dmask=0022디렉터리 권한 마스크
umask=-파일/디렉터리 공통 권한 마스크
nls=utf8파일명 인코딩 (NLS 캐릭터셋)
acl비활성POSIX ACL 지원 활성화
noatime비활성접근 시간 업데이트 비활성화
prealloc비활성사전 할당 (단편화 감소)
no_acs_rules비활성NTFS ACL 검사 비활성화 (모든 접근 허용)
discard비활성TRIM/discard 명령 활성화 (SSD)
force비활성dirty 볼륨 강제 마운트
sparse비활성Sparse 파일 생성 허용
showmeta비활성시스템 메타파일을 디렉터리에 표시
compress비활성새 파일에 LZNT1 압축 적용

성능 고려사항

마운트 예시

# 기본 ntfs3 마운트 (읽기-쓰기)
mount -t ntfs3 /dev/sda1 /mnt/windows

# 성능 최적화 마운트
mount -t ntfs3 -o noatime,prealloc,discard /dev/sda1 /mnt/windows

# 데스크탑 사용 (일반 사용자 접근)
mount -t ntfs3 -o uid=1000,gid=1000,umask=0022,noatime /dev/sda1 /mnt/windows

# POSIX ACL 활성화
mount -t ntfs3 -o acl,noatime /dev/sda1 /mnt/windows

# 읽기 전용 마운트
mount -t ntfs3 -o ro /dev/sda1 /mnt/windows

# dirty 볼륨 강제 마운트 (주의: 데이터 손실 위험)
mount -t ntfs3 -o force /dev/sda1 /mnt/windows

# fstab 설정
# /dev/sda1 /mnt/windows ntfs3 defaults,noatime,prealloc,uid=1000,gid=1000 0 0

도구 & 유틸리티

Linux에서 NTFS 볼륨 관리에 사용되는 도구는 ntfsprogs(ntfs-3g 패키지) 세트입니다. ntfs3 드라이버는 마운트 후 표준 Linux 도구(cp, mv, chmod 등)를 사용할 수 있지만, 포맷/복구/레이블 변경 등은 전용 도구가 필요합니다.

ntfsprogs 도구

도구설명
mkntfs (mkfs.ntfs)NTFS 볼륨 포맷 (mkfs.ntfs는 심볼릭 링크)
ntfsfix간단한 NTFS 복구 (dirty 플래그 클리어, $LogFile 리셋)
ntfsresizeNTFS 볼륨 크기 조정 (축소/확장)
ntfscloneNTFS 볼륨 복제 (사용 중인 클러스터만 복사)
ntfslabel볼륨 레이블 읽기/변경
ntfsinfoNTFS 볼륨/파일 메타데이터 정보 출력
ntfslsNTFS 디렉터리 목록 (마운트 없이 직접 읽기)
ntfscatNTFS 파일 내용 출력 (마운트 없이 직접 읽기)
ntfscpNTFS 볼륨에 파일 복사 (마운트 없이 직접 쓰기)
ntfscmp두 NTFS 볼륨 비교
ntfswipeNTFS 볼륨 데이터 안전 삭제
ntfsdecryptEFS 암호화 파일 복호화

mkntfs 사용 예시

# 기본 NTFS 포맷
mkfs.ntfs /dev/sdb1

# 빠른 포맷 (제로 채움 건너뛰기)
mkfs.ntfs -f /dev/sdb1

# 클러스터 크기 지정
mkfs.ntfs -c 65536 /dev/sdb1    # 64 KiB 클러스터

# 볼륨 레이블 지정
mkfs.ntfs -L "DATA" /dev/sdb1

# 볼륨 정보 확인
ntfsinfo -m /dev/sdb1

# dirty 볼륨 수정
ntfsfix /dev/sdb1

# 볼륨 레이블 변경
ntfslabel /dev/sdb1 "NEWLABEL"

# 볼륨 크기 조정 (5GiB로 축소)
ntfsresize -s 5G /dev/sdb1

NTFS vs 다른 파일시스템 비교

항목NTFSext4XFSBtrfsexFAT
저널링메타데이터메타+데이터(옵션)메타데이터(WAL)COW(저널 불필요)없음
최대 볼륨256 TiB1 EiB8 EiB16 EiB128 PiB
최대 파일256 TiB16 TiB8 EiB16 EiB128 PiB
압축LZNT1없음없음zstd/lzo/zlib없음
암호화EFSfscrypt없음없음없음
스냅샷VSS (Windows)없음없음네이티브없음
Reflink없음없음지원지원없음
데이터 체크섬없음메타만메타만데이터+메타없음
Linux 지원ntfs3(5.15+)네이티브네이티브네이티브네이티브(5.4+)
Windows 지원네이티브서드파티없음WinBtrfs네이티브
주 사용처Windows 시스템Linux 범용대용량/엔터프라이즈Linux 데스크탑/NAS이동식 미디어

Linux에서의 NTFS 한계

Linux에서 NTFS 사용 시 알려진 제한사항:
  • EFS 암호화: ntfs3/ntfs-3g 모두 투명한 EFS 복호화를 지원하지 않습니다 (ntfsdecrypt로 수동 복호화 가능).
  • $UsnJrnl: ntfs3는 USN 저널을 완전히 지원하지 않습니다.
  • VSS (Volume Shadow Copy): Windows 전용 기능으로, Linux에서 접근 불가합니다.
  • 대소문자 구분: NTFS는 기본적으로 대소문자 비구분이지만, Linux는 대소문자를 구분합니다. ntfs3는 NTFS 규칙을 따라 대소문자 비구분 동작을 합니다.
  • Reparse Point: 심볼릭 링크, Junction은 매핑되지만, 커스텀 Reparse Point는 제한적입니다.
  • WOF 압축: Windows 10+의 WOF(Windows Overlay Filter) 압축(LZX/Xpress)은 CONFIG_NTFS3_LZX_XPRESS 옵션으로 읽기만 지원됩니다.
  • Deduplication: Windows Server의 데이터 중복 제거 기능은 Linux에서 지원되지 않습니다.
듀얼 부팅 팁: Windows/Linux 듀얼 부팅 환경에서는 데이터 공유 파티션에 NTFS를 사용하는 것이 일반적입니다. Windows의 "빠른 시작"(Fast Startup)을 비활성화해야 Linux에서 NTFS를 읽기-쓰기로 마운트할 수 있습니다. 빠른 시작이 활성화된 상태에서는 Windows가 $LogFile을 dirty 상태로 유지하여, ntfs3가 읽기 전용으로만 마운트합니다.

MFT 레코드 구조 심화

MFT 레코드는 NTFS에서 파일과 디렉터리를 표현하는 기본 단위입니다. 1024바이트 고정 크기의 레코드 안에 헤더, Fixup Array, 그리고 하나 이상의 속성이 순서대로 배치됩니다. 작은 파일은 데이터까지 레코드 내부에 저장(Resident)되지만, 큰 파일은 외부 클러스터를 참조(Non-Resident)합니다. 하나의 레코드로 모든 속성을 담지 못하면 확장 레코드(Extension Record)를 할당하고 $ATTRIBUTE_LIST로 연결합니다.

MFT 레코드 상세 구조 (1024 바이트) 레코드 헤더 "FILE" 시그니처 LSN, Seq#, Flags first_attr_offset 0x00~0x2F (48B) Fixup USA Value Orig[0] Orig[1] 0x30~0x35 (6B) $STD_INFO (0x10) 타임스탬프 (MACB) 파일 플래그 Security ID Resident (72B) $FILE_NAME 부모 MFT 참조 파일명 (UTF-16) 크기, 타임스탬프 Resident (가변) $DATA (0x80) 작은 파일 데이터 MFT 내부 저장 (~700B 이하) Resident End 0xFFFF FFFF Free Space Non-Resident $DATA: Data Run이 외부 클러스터를 가리킴 $DATA (Non-Resident) start_vcn=0, end_vcn=N Data Runs: [len|off]... Data Run LCN 100 5 clusters LCN 250 10 clusters LCN 500 3 clusters = 18 clusters 총 확장 레코드 (Extension Record): 속성이 1024B를 초과할 때 기본 레코드 (MFT #N) $ATTRIBUTE_LIST 포함 base_record = 0 확장 레코드 1 (MFT #M) 추가 $DATA runs base_record = #N 확장 레코드 2 (MFT #P) 추가 속성들 base_record = #N Resident vs Non-Resident 결정 기준: - 속성 데이터가 MFT 레코드 여유 공간(~700B)에 들어가면 → Resident (MFT 내부 저장, I/O 1회로 완료) - 속성 데이터가 여유 공간을 초과하면 → Non-Resident (외부 클러스터에 저장, Data Run으로 위치 추적) - $STANDARD_INFORMATION, $FILE_NAME은 항상 Resident | $DATA, $INDEX_ALLOCATION은 크기에 따라 전환 - 확장 레코드: $ATTRIBUTE_LIST가 기본 레코드에서 확장 레코드들의 속성 위치를 인덱싱

Resident 임계값과 전환

MFT 레코드의 고정 크기는 1024바이트이며, 헤더(48B)와 Fixup Array(6B)를 빼면 약 970바이트가 속성 공간입니다. $STANDARD_INFORMATION(72B)과 $FILE_NAME(최소 66B + 파일명 길이)을 제외하면 $DATA 속성에 사용할 수 있는 공간은 약 700바이트입니다. 파일 크기가 이 임계값을 초과하면 $DATA가 Non-Resident로 전환됩니다.

/* Resident → Non-Resident 전환 판단 (fs/ntfs3/attrib.c 개념) */
static int attr_make_nonresident(struct ntfs_inode *ni,
                                 struct ATTRIB *attr)
{
    u32 asize = le32_to_cpu(attr->size);
    u32 rsize = le32_to_cpu(attr->res.data_size);

    /* 1. 현재 속성 값을 임시 버퍼에 복사 */
    void *data = kmalloc(rsize, GFP_NOFS);
    memcpy(data, resident_data(attr), rsize);

    /* 2. 외부 클러스터 할당 */
    ntfs_alloc_clusters(sbi, rsize, &run);

    /* 3. 데이터를 외부 클러스터에 기록 */
    ntfs_sb_write_run(sbi, &run, 0, data, rsize);

    /* 4. 속성 헤더를 Non-Resident로 변경 */
    attr->non_res = 1;
    /* Data Run 인코딩하여 속성에 기록 */
    run_pack(&run, attr);

    return 0;
}

MFT 레코드 플래그와 시퀀스 번호

플래그설명
IN_USE0x01레코드가 활성 파일/디렉터리에 사용 중
DIRECTORY0x02레코드가 디렉터리 ($INDEX_ROOT 포함)
IN_EXTEND0x04$Extend 디렉터리의 시스템 파일
IS_VIEW_INDEX0x08뷰 인덱스 (보안/객체 ID)

시퀀스 번호(sequence_number)는 MFT 레코드가 재사용될 때마다 1씩 증가합니다. 파일 참조(File Reference)는 48비트 MFT 레코드 번호 + 16비트 시퀀스 번호로 구성되어, 삭제 후 재사용된 레코드를 이전 참조와 구별할 수 있습니다. 이를 통해 stale 참조(이미 삭제된 파일을 가리키는 오래된 포인터)를 감지합니다.

/* File Reference (MFT_REF) 구조 - 8바이트 */
/* 하위 48비트: MFT 레코드 번호 (최대 2^48 = 281조 개) */
/* 상위 16비트: 시퀀스 번호 (0~65535, 래핑) */
#define MFT_REC_NO(ref)   ((ref) & 0x0000FFFFFFFFFFFFULL)
#define MFT_REC_SEQ(ref)  ((u16)((ref) >> 48))

/* ntfs3에서의 사용 (fs/ntfs3/ntfs_fs.h) */
static inline bool mft_ref_valid(u64 ref, struct MFT_REC *rec)
{
    return MFT_REC_SEQ(ref) == le16_to_cpu(rec->seq);
}

NTFS 속성 구조 심화

NTFS의 핵심 설계 원칙은 "모든 것이 속성"이라는 개념입니다. 파일의 이름, 타임스탬프, 보안 정보, 실제 데이터 모두 동일한 속성 프레임워크 안에서 관리됩니다. 각 속성은 타입 코드로 식별되며, Resident/Non-Resident 구분에 따라 저장 방식이 달라집니다. 이 섹션에서는 주요 속성의 내부 구조를 상세히 분석합니다.

NTFS 핵심 속성 내부 구조 $STANDARD_INFORMATION (0x10) Created Time (8B) - 파일 생성 시각 (FILETIME) Modified Time (8B) - 마지막 수정 시각 MFT Modified (8B) - MFT 레코드 변경 시각 Accessed Time (8B) - 마지막 접근 시각 File Flags (4B) - READ_ONLY, HIDDEN, SYSTEM... Security ID (4B) - $Secure 인덱스 참조 USN + Quota (NTFS 3.0+): Owner ID, Quota Charged $FILE_NAME (0x30) Parent Dir Ref (8B) - 부모 디렉터리 MFT 참조 Timestamps (32B) - C/M/A/E 4개 (인덱스용 사본) Allocated Size (8B) - 할당된 크기 Real Size (8B) - 실제 데이터 크기 Flags (4B) - 파일 속성 복사본 Name Length (1B) + Namespace (1B) File Name (가변) - UTF-16LE, Win32/DOS/POSIX 네임스페이스 $DATA (0x80) Resident 모드: 속성 헤더 바로 뒤에 파일 데이터가 직접 저장 작은 파일(~700B) - I/O 1회로 데이터 접근 Non-Resident 모드: Data Run으로 외부 클러스터 매핑 (VCN → LCN) ADS: 동일 MFT에 명명된 $DATA 스트림 추가 가능 $INDEX 속성들 (디렉터리) $INDEX_ROOT (0x90) - 항상 Resident: B+ tree 루트 노드, 인덱스 엔트리 직접 포함 작은 디렉터리는 이것만으로 충분 $INDEX_ALLOCATION (0xA0) - 항상 Non-Resident: B+ tree 나머지 노드 저장 $BITMAP(0xB0)으로 블록 할당 추적 MFT 레코드 내 속성 배치 순서 (타입 코드 오름차순) 0x10 STD_INFO 0x20 ATTR_LIST 0x30 FILE_NAME 0x80 DATA 0x90 INDEX_ROOT 0xFF End $FILE_NAME 네임스페이스: 0 = POSIX (대소문자 구분, 거의 모든 문자 허용) | 1 = Win32 (대소문자 보존, 일부 문자 제한) 2 = DOS (8.3 형식, 대문자 전용) | 3 = Win32+DOS (Win32 이름이 8.3 규칙 충족 시 병합) 긴 파일명 = Win32(1) + DOS(2) 두 개의 $FILE_NAME 속성이 하나의 MFT 레코드에 공존

$STANDARD_INFORMATION 상세

/* $STANDARD_INFORMATION 속성 (0x10) - 72바이트 (NTFS 3.0+) */
struct ATTR_STD_INFO {
    uint64_t cr_time;     /* 생성 시각 (100ns 단위, 1601-01-01 기준) */
    uint64_t m_time;      /* 수정 시각 */
    uint64_t c_time;      /* MFT 변경 시각 */
    uint64_t a_time;      /* 접근 시각 */
    uint32_t fa;          /* 파일 속성 플래그 */
    uint32_t max_ver;     /* 최대 버전 번호 */
    uint32_t ver;         /* 현재 버전 번호 */
    uint32_t class_id;    /* 클래스 ID */
    /* NTFS 3.0+ 추가 필드 */
    uint32_t owner_id;    /* 쿼터 소유자 ID */
    uint32_t security_id; /* $Secure 인덱스 */
    uint64_t quota_charge; /* 쿼터 부과량 */
    uint64_t usn;         /* Update Sequence Number */
};

/* 파일 속성 플래그 (fa 필드) */
#define FILE_ATTR_READONLY            0x0001
#define FILE_ATTR_HIDDEN              0x0002
#define FILE_ATTR_SYSTEM              0x0004
#define FILE_ATTR_DIRECTORY           0x0010
#define FILE_ATTR_ARCHIVE             0x0020
#define FILE_ATTR_DEVICE              0x0040
#define FILE_ATTR_TEMPORARY           0x0100
#define FILE_ATTR_SPARSE_FILE         0x0200
#define FILE_ATTR_REPARSE_POINT       0x0400
#define FILE_ATTR_COMPRESSED          0x0800
#define FILE_ATTR_ENCRYPTED           0x4000

$FILE_NAME 네임스페이스와 8.3 이름

NTFS는 하위 호환성을 위해 하나의 파일에 최대 3개의 $FILE_NAME 속성을 가질 수 있습니다. Win32 이름이 8.3 규칙을 만족하면 Win32+DOS(namespace=3) 하나로 통합됩니다. 그렇지 않으면 Win32 이름과 DOS 이름이 별도 속성으로 존재합니다.

/* $FILE_NAME 속성 (0x30) 구조 */
struct ATTR_FILE_NAME {
    uint64_t parent_ref;     /* 부모 디렉터리 MFT 참조 */
    uint64_t cr_time;        /* 생성 시각 (인덱스 검색용 사본) */
    uint64_t m_time;         /* 수정 시각 */
    uint64_t c_time;         /* MFT 변경 시각 */
    uint64_t a_time;         /* 접근 시각 */
    uint64_t alloc_size;     /* 할당 크기 */
    uint64_t data_size;      /* 실제 크기 */
    uint32_t fa;             /* 파일 속성 (플래그 사본) */
    uint32_t ea_reparse;     /* EA 크기 또는 Reparse 태그 */
    uint8_t  name_len;       /* 파일명 길이 (UTF-16 문자 수) */
    uint8_t  name_type;      /* 0=POSIX 1=Win32 2=DOS 3=Win32+DOS */
    /* uint16_t name[]; */  /* UTF-16LE 파일명 */
};
타임스탬프 이중화: $STANDARD_INFORMATION과 $FILE_NAME 모두 타임스탬프를 갖습니다. $STANDARD_INFORMATION의 타임스탬프가 실제 값이고, $FILE_NAME의 타임스탬프는 디렉터리 인덱스 검색 시 MFT 레코드를 읽지 않고도 파일 정보를 보여주기 위한 캐시 사본입니다. Windows는 파일을 닫을 때 $FILE_NAME 타임스탬프를 갱신하며, 포렌식 분석에서 이 불일치가 타임스탬프 위변조 단서가 됩니다.

NTFS3 vs NTFS-3G 비교

Linux에서 NTFS 읽기/쓰기를 지원하는 두 가지 주요 구현의 아키텍처와 성능 차이를 비교합니다. ntfs3는 커널 공간에서 직접 실행되어 커널 I/O 스택과 긴밀히 통합되고, ntfs-3g는 FUSE 프레임워크를 통해 유저스페이스에서 실행됩니다.

NTFS3 (커널) vs NTFS-3G (FUSE) 아키텍처 ntfs3 (커널 드라이버) Application (read/write/stat) syscall VFS Layer (커널) ntfs3.ko (fs/ntfs3/) 직접 블록 디바이스 접근 Block I/O + Page Cache (커널) NTFS 볼륨 (디스크) ntfs-3g (FUSE) Application (read/write/stat) syscall VFS + FUSE 커널 모듈 유저-커널 전환! ntfs-3g 데몬 (유저스페이스) libntfs-3g 라이브러리 유저-커널 전환! Block I/O (커널, /dev/sdXN) NTFS 볼륨 (디스크) 컨텍스트 전환: 2회 (user↔kernel) Page Cache 직접 활용 컨텍스트 전환: 6+회 (FUSE 왕복) 데이터 복사 오버헤드 발생

성능 비교

벤치마크 항목ntfs3ntfs-3g차이 요인
순차 읽기~400 MB/s~250 MB/sPage Cache 직접 접근 vs FUSE 데이터 복사
순차 쓰기~350 MB/s~200 MB/s커널 writeback vs 유저스페이스 쓰기
랜덤 4K 읽기~50K IOPS~15K IOPS컨텍스트 전환 횟수 차이
메타데이터 ops~80K ops/s~20K ops/s각 메타데이터 작업마다 FUSE 왕복
CPU 사용률낮음높음유저-커널 전환 + 데이터 복사 오버헤드
메모리 사용커널 슬랩유저 힙 + 커널ntfs-3g 데몬 프로세스 메모리
성능 참고: 위 수치는 NVMe SSD 기반 대략적 기대값이며, 실제 성능은 디스크 종류, 파일 크기 분포, 단편화 수준에 따라 달라집니다. ntfs-3g의 가장 큰 병목은 FUSE 프로토콜의 메시지 전달에 따른 컨텍스트 전환과 데이터 복사입니다. FUSE passthrough 모드(커널 6.9+)가 활성화되면 읽기 성능 차이가 줄어들 수 있습니다.

기능 매트릭스

기능ntfs3ntfs-3g비고
LZNT1 압축 읽기/쓰기지원지원동등
WOF 압축 (LZX/Xpress)읽기만미지원CONFIG_NTFS3_LZX_XPRESS 필요
ADS (대체 데이터 스트림)xattrxattr접근 방식 동일
POSIX ACL지원제한적ntfs3가 더 완전한 매핑
$LogFile 재생지원지원둘 다 dirty 볼륨 복구 가능
Reparse Point심볼릭 링크심볼릭 링크커스텀 Reparse는 양쪽 제한적
EFS 암호화미지원미지원ntfsdecrypt로 수동 복호화
큰 MFT 처리커널 메모리유저 힙ntfs3가 메모리 효율적
Hot plug/unplug커널 이벤트시그널ntfs3가 더 안정적
inotify/fanotify네이티브제한적커널 드라이버 장점
# ntfs3 vs ntfs-3g 성능 비교 벤치마크
# ntfs3으로 마운트
mount -t ntfs3 -o noatime /dev/sdb1 /mnt/ntfs3
fio --name=seqread --filename=/mnt/ntfs3/testfile \
    --rw=read --bs=1M --size=1G --numjobs=1 --runtime=30

# ntfs-3g로 마운트
ntfs-3g -o noatime /dev/sdb1 /mnt/ntfs3g
fio --name=seqread --filename=/mnt/ntfs3g/testfile \
    --rw=read --bs=1M --size=1G --numjobs=1 --runtime=30

# 메타데이터 성능 비교
# 10,000개 파일 생성 시간 측정
time for i in $(seq 1 10000); do touch /mnt/test/file_$i; done

NTFS 저널 ($LogFile) 심화

$LogFile은 NTFS 메타데이터 일관성의 핵심입니다. WAL(Write-Ahead Logging) 프로토콜에 따라, 메타데이터를 디스크에 실제로 반영하기 전에 반드시 로그 레코드를 먼저 기록합니다. 각 로그 레코드에는 Redo(재실행) 데이터와 Undo(취소) 데이터가 쌍으로 포함되어, 크래시 복구 시 커밋된 트랜잭션은 Redo로 완료하고 미완료 트랜잭션은 Undo로 롤백합니다.

NTFS $LogFile 복구 알고리즘 (ARIES 변형) 로그 레코드 구조 LSN (8B) Prev LSN Undo Next Trans ID Redo Op+Data Undo Op+Data Target MFT/VCN Phase 1: Analysis 1. Restart Area에서 체크포인트 읽기 2. 체크포인트 LSN부터 순방향 스캔 3. Dirty Page Table 재구성 - 어떤 MFT/인덱스가 미반영인지 4. Transaction Table 재구성 - 커밋/미완료 트랜잭션 분류 Phase 2: Redo 1. Dirty Page Table의 oldest LSN부터 2. 순방향으로 Redo 레코드 재실행 3. 페이지 LSN 비교로 중복 방지 - page.lsn >= record.lsn → 건너뛰기 4. 커밋/미커밋 무관하게 모두 Redo → 디스크를 크래시 직전 상태로 복원 Phase 3: Undo 1. 미완료 트랜잭션만 대상 2. 역방향으로 Undo 레코드 실행 3. Undo Next LSN 체인을 따라 역추적 4. Compensation Log Record(CLR) 기록 → Undo 도중 크래시 시 재시작 안전 5. 커밋 안 된 변경 사항 완전 롤백 체크포인트(Checkpoint) 메커니즘 주기적으로 Dirty Page Table + Transaction Table을 Restart Area에 기록 체크포인트 이전 로그는 복구에 불필요 → 순환 버퍼에서 덮어쓰기 가능 → 로그 공간 재활용 Restart Area 2개(이중화): 하나가 손상되어도 다른 것으로 복구 가능 $Volume Dirty 플래그와 복구 트리거 마운트(RW) → $Volume.flags |= DIRTY → 정상 언마운트 → $Volume.flags &= ~DIRTY 크래시 후 재마운트 → DIRTY 플래그 감지 → $LogFile 재생 (Analysis → Redo → Undo) → 일관성 복구 ntfs3: dirty 볼륨 감지 시 기본적으로 읽기 전용 마운트 (force 옵션으로 강제 RW 가능, 위험)

로그 레코드 상세 구조

/* $LogFile 로그 레코드 헤더 (개념적 구조) */
struct log_record_header {
    uint64_t this_lsn;        /* 이 레코드의 LSN */
    uint64_t prev_lsn;        /* 같은 트랜잭션의 이전 LSN */
    uint64_t undo_next_lsn;   /* Undo 시 다음 처리할 LSN */
    uint32_t transaction_id;  /* 트랜잭션 식별자 */
    uint16_t redo_op;         /* Redo 작업 코드 */
    uint16_t undo_op;         /* Undo 작업 코드 */
    uint16_t redo_offset;     /* Redo 데이터 오프셋 */
    uint16_t redo_length;     /* Redo 데이터 길이 */
    uint16_t undo_offset;     /* Undo 데이터 오프셋 */
    uint16_t undo_length;     /* Undo 데이터 길이 */
    uint16_t target_attr;     /* 대상 속성 (Open Attribute Table 인덱스) */
    uint16_t lcns_to_follow;  /* 후속 LCN 배열 수 */
    uint16_t record_offset;   /* 레코드 내 대상 오프셋 */
    uint16_t attr_offset;     /* 속성 내 대상 오프셋 */
    uint16_t cluster_index;   /* 클러스터 내 인덱스 */
    uint16_t reserved;
    uint64_t target_vcn;     /* 대상 VCN */
    /* uint64_t lcn_list[]; */ /* LCN 배열 */
    /* uint8_t redo_data[]; */ /* Redo 데이터 */
    /* uint8_t undo_data[]; */ /* Undo 데이터 */
};

/* ntfs3 저널 재생 (fs/ntfs3/fsntfs.c 개념) */
static int ntfs_replay_log(struct ntfs_sb_info *sbi)
{
    /* 1. Restart Area 2개 중 유효한 것 선택 */
    log_read_restart(sbi);

    /* 2. Analysis: 체크포인트부터 스캔 */
    log_analysis_pass(sbi, &dirty_pages, &open_trans);

    /* 3. Redo: 커밋된 변경 재실행 */
    log_redo_pass(sbi, &dirty_pages);

    /* 4. Undo: 미완료 트랜잭션 롤백 */
    log_undo_pass(sbi, &open_trans);

    /* 5. 새 체크포인트 기록 */
    log_write_restart(sbi);

    return 0;
}
$LogFile vs $UsnJrnl 차이:
  • $LogFile: 내부 메타데이터 복구용 저널. 크래시 복구에만 사용되며, 정상 동작 시 재활용됩니다.
  • $UsnJrnl: 외부 변경 알림용 저널. 어떤 파일이 언제 변경되었는지 유저스페이스에 알려주는 변경 추적 기록입니다.
  • $LogFile은 시스템 전용이고, $UsnJrnl은 백업/검색 소프트웨어가 참조합니다.

B+tree 인덱스 심화

NTFS 디렉터리는 B+ tree를 사용하여 파일명을 정렬 관리합니다. 이 인덱스 구조는 $INDEX_ROOT(루트 노드, 항상 Resident)와 $INDEX_ALLOCATION(비-루트 노드, Non-Resident)으로 구성됩니다. 각 인덱스 블록(보통 4096바이트)은 정렬된 인덱스 엔트리를 포함하며, $FILE_NAME 속성의 사본을 키로 사용하여 $UpCase 테이블 기반 유니코드 비교로 검색합니다.

NTFS B+tree 인덱스 구조와 노드 분할 작은 디렉터리 (INDEX_ROOT만 사용) $INDEX_ROOT (MFT 내부, Resident) abc.txt | def.doc | ghi.c | END 큰 디렉터리 (노드 분할 발생) $INDEX_ROOT + $INDEX_ALLOC 루트 노드 + 외부 Index Block들 분할 후 B+tree 구조 $INDEX_ROOT (루트) def.doc | mno.txt | END Index Block 0 abc.txt | bbb.c | END Index Block 1 ghi.c | jkl.h | END Index Block 2 ppp.dat | zzz.log | END < def def~mno > mno 인덱스 엔트리 상세 구조 MFT Reference 48b rec# + 16b seq Entry Len 전체 길이 Content Len 키 데이터 길이 Flags 0x01|0x02 Key: $FILE_NAME 사본 (가변 길이) 부모 참조 + 타임스탬프 + 크기 + 파일명 Sub-node VCN CHILD_NODE 시 노드 분할 알고리즘 1. 삽입할 엔트리의 위치를 $UpCase 비교로 결정 → 해당 노드에 공간이 있으면 직접 삽입 2. 노드가 가득 차면 → 중간 키를 부모로 승격(push up) → 노드를 좌/우로 분할 3. $INDEX_ROOT만 있던 작은 디렉터리 → 분할 시 LARGE_INDEX 플래그 설정 → $INDEX_ALLOCATION + $BITMAP 생성 4. 루트 노드 분할 → 트리 높이 1 증가 (새 루트 생성) | 모든 변경은 $LogFile에 WAL 기록 ntfs3 구현: fs/ntfs3/index.c — indx_insert_entry(), indx_add_allocate() 함수

인덱스 검색 과정 (lookup)

/* ntfs3 디렉터리 검색 개념 (fs/ntfs3/index.c) */
static int indx_find(struct ntfs_index *indx,
                     struct ntfs_inode *dir_ni,
                     const struct cpu_str *name,
                     struct NTFS_DE **entry)
{
    struct INDEX_ROOT *root;
    struct NTFS_DE *e;
    int cmp;

    /* 1. $INDEX_ROOT에서 루트 노드 로드 */
    root = indx_get_root(indx, dir_ni);

    /* 2. 루트 노드 엔트리 순회 */
    e = hdr_first_de(&root->ihdr);
    while (!de_is_last(e)) {
        /* $UpCase 기반 유니코드 비교 */
        cmp = ntfs_cmp_names(name, de_get_fname(e),
                            sbi->upcase);
        if (cmp == 0) {
            *entry = e;   /* 찾음! */
            return 0;
        }
        if (cmp < 0 && de_has_child(e)) {
            /* 하위 노드로 내려감 */
            goto descend;
        }
        e = hdr_next_de(&root->ihdr, e);
    }
    /* 마지막 엔트리도 하위 노드 확인 */
    if (de_has_child(e)) {
descend:
        /* 3. $INDEX_ALLOCATION에서 하위 Index Block 로드 */
        indx_read(indx, dir_ni, de_get_vcn(e), &node);
        /* 해당 블록에서 재귀 검색 */
        return indx_find_in_node(indx, node, name, entry);
    }
    return -ENOENT; /* 파일 없음 */
}

인덱스 성능 특성

작업시간 복잡도설명
파일 검색 (lookup)O(log N)B+ tree 높이 = logB(N), B는 인덱스 블록의 분기 계수
파일 삽입 (create)O(log N)검색 + 삽입 + 가능한 분할 (분할 비용 상각)
파일 삭제 (unlink)O(log N)검색 + 삭제 + 가능한 병합
디렉터리 열거 (readdir)O(N)리프 노드 순차 스캔 (정렬된 순서)
인덱스 블록 크기: 기본 4096바이트로, 하나의 블록에 약 20~40개의 인덱스 엔트리가 들어갑니다. 100,000개 파일 디렉터리도 B+ tree 높이 3~4 수준이므로, 3~4번의 디스크 읽기로 파일을 찾을 수 있습니다. NTFS의 인덱스는 디렉터리뿐 아니라 $Secure($SII, $SDH)와 $ObjId 등 시스템 메타데이터에도 동일 구조를 사용합니다.

NTFS 압축 심화

NTFS는 $DATA 속성에 LZNT1 알고리즘을 사용한 투명한 압축을 지원합니다. 압축은 16개 클러스터(기본 64 KiB)를 하나의 압축 단위(Compression Unit)로 처리하며, 각 단위를 독립적으로 압축/해제할 수 있어 랜덤 접근 시에도 전체 파일을 해제할 필요가 없습니다. 압축 후 크기가 원본보다 줄어들면 절약된 공간을 Sparse Run으로 표현합니다.

NTFS 압축 유닛 구조 (16 클러스터 = 64 KiB) 원본 (비압축): 16 클러스터 = 64 KiB C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12 C13 C14 C15 압축 성공: 10 클러스터로 축소 → 6 클러스터 절약 압축 데이터 (10 클러스터, 할당됨) Sparse Run (6 클러스터, 비할당) Data Run: [10 clusters @ LCN 500] + [6 clusters @ Sparse (offset=0)] 압축 실패: 압축 크기 >= 원본 → 비압축 저장 비압축 데이터 그대로 저장 (16 클러스터, 할당됨, 절약 없음) Data Run: [16 clusters @ LCN 700] (압축 단위가 온전히 16 클러스터면 비압축으로 판단) LZNT1 압축 알고리즘 구조 Chunk Header (2B) 크기 + 압축 플래그 Flag Byte 8비트: 리터럴/참조 Literal Byte 비압축 원본 바이트 Back Reference (2B) offset:N + length:M LZNT1: LZ77 변형, 4KB 청크 단위 처리. Flag byte의 각 비트가 0=리터럴, 1=역참조를 결정 ntfs3 구현: fs/ntfs3/lznt.c — decompress_lznt(), 읽기 시 on-demand 해제 | 쓰기 시 compress 마운트 옵션 필요

압축 트레이드오프

장점단점
디스크 공간 절약 (텍스트 파일 50~70% 축소)CPU 오버헤드 (특히 쓰기 시)
16 클러스터 단위 독립 접근 (랜덤 읽기 가능)쓰기 증폭 (수정 시 전체 압축 단위 재압축)
투명한 동작 (애플리케이션 수정 불필요)단편화 증가 (압축률 변동으로 Data Run 변경)
Sparse 파일과 자연스럽게 결합클러스터 크기 4 KiB에서만 지원

Sparse 파일

Sparse 파일은 실제 데이터가 없는 영역에 디스크 공간을 할당하지 않는 최적화입니다. Data Run에서 offset_size가 0인 Run이 Sparse(비할당) 영역을 나타내며, 해당 영역을 읽으면 드라이버가 0으로 채워진 데이터를 반환합니다.

/* Sparse 파일 Data Run 예시 */
/* Run 1: 10 clusters @ LCN 100 (실제 데이터) */
/* Run 2: 100 clusters @ Sparse (0으로 읽힘, 디스크 미할당) */
/* Run 3: 5 clusters @ LCN 500 (실제 데이터) */
/* → 총 논리 크기: 115 clusters × 4KB = 460 KB */
/* → 실제 할당: 15 clusters × 4KB = 60 KB */

/* ntfs3에서 Sparse 파일 생성 */
/* mount -t ntfs3 -o sparse /dev/sdb1 /mnt */
/* fallocate -l 1G /mnt/sparse_file */
/* → PUNCH_HOLE로 구멍 생성 가능 */

/* fs/ntfs3/file.c: ntfs_fallocate() */
static long ntfs_fallocate(struct file *file, int mode,
                          loff_t offset, loff_t len)
{
    if (mode & FALLOC_FL_PUNCH_HOLE) {
        /* Data Run에서 해당 범위를 Sparse로 변환 */
        ntfs_punch_hole(inode, offset, len);
    }
    /* ... */
}

NTFS 보안 디스크립터 심화

NTFS는 Windows NT 보안 모델을 기반으로 각 파일/디렉터리에 세밀한 접근 제어를 적용합니다. 보안 정보는 $Secure 파일(MFT #9)에서 중앙 관리되며, 각 파일의 $STANDARD_INFORMATION 속성에는 Security ID만 저장하여 공간을 절약합니다. Security Descriptor는 소유자(Owner SID), 그룹(Group SID), DACL(접근 제어), SACL(감사)로 구성됩니다.

NTFS 보안 디스크립터와 $Secure 구조 파일 MFT 레코드 $STD_INFO Security ID = 256 참조 $Secure:$SII (인덱스) Security ID 256 → $SDS offset: 0x1A000 $Secure:$SDS (데이터 스트림) offset 0x1A000: Security Descriptor Owner + Group + DACL + SACL Security Descriptor 내부 구조 Header Revision: 1 Control Flags + 4개 오프셋 Owner SID S-1-5-21-... 파일 소유자 Group SID S-1-5-21-...-512 소유 그룹 DACL ACE[0]: Allow Admin Full ACE[1]: Allow Users Read 접근 허용/거부 규칙 SACL 감사(Audit) 규칙 성공/실패 접근 로깅 ACE (Access Control Entry) 구조 Type (1B) Allow/Deny Flags (1B) 상속 플래그 Size (2B) ACE 전체 크기 Access Mask (4B) Read|Write|Execute|Delete Trustee SID (가변) S-1-5-21-...-1001 (사용자/그룹) $Secure 파일의 3개 스트림 $SDS (Data Stream) Security Descriptor 실체 (추가 전용) 256KB 경계에 이중 저장 (복구용) $SDH (Hash Index) 해시 → $SDS 오프셋 (B+tree) 동일 SD 중복 저장 방지 $SII (ID Index) Security ID → $SDS 오프셋 (B+tree) 파일 접근 시 빠른 SD 조회 SD 설정 흐름: 새 SD 해시 계산 → $SDH에서 기존 동일 SD 검색 → 없으면 $SDS에 추가 + 새 Security ID 할당 → 파일 $STD_INFO에 ID 저장

SID (Security Identifier) 구조

/* Windows SID (Security Identifier) 구조 */
struct SID {
    uint8_t  revision;            /* 항상 1 */
    uint8_t  sub_authority_count;  /* Sub Authority 개수 (1~15) */
    uint8_t  authority[6];        /* Identifier Authority (Big-Endian) */
    uint32_t sub_authority[];      /* Sub Authority 배열 (Little-Endian) */
};

/* 일반적인 SID 예시 */
/* S-1-5-21-3623811015-3361044348-30300820-1013 */
/*   S: SID 접두사 */
/*   1: Revision */
/*   5: NT Authority */
/*   21: SECURITY_NT_NON_UNIQUE */
/*   3623811015-3361044348-30300820: 도메인/머신 식별자 */
/*   1013: RID (Relative ID) = 사용자 식별자 */

/* Well-known SID */
/* S-1-5-18: Local System */
/* S-1-5-32-544: Administrators */
/* S-1-5-32-545: Users */
/* S-1-1-0: Everyone */

ntfs3의 ACL 매핑

ntfs3 드라이버는 NTFS Security Descriptor를 Linux POSIX 권한으로 매핑할 때 다음 규칙을 적용합니다:

NTFS ACLLinux POSIX매핑 방식
Owner SIDuid마운트 옵션 uid= 또는 SID→uid 매핑
Group SIDgid마운트 옵션 gid= 또는 SID→gid 매핑
DACL Allow/Denyrwx 비트Access Mask → Read/Write/Execute 변환
DACL (acl 옵션)POSIX ACLsetfacl/getfacl로 확장 ACL 관리
파일 속성 (Read-Only 등)xattrsystem.ntfs_attrib로 읽기/쓰기
SD 바이너리xattrsystem.ntfs_security로 원시 SD 접근
# ntfs3에서 보안 정보 확인
# NTFS 파일 속성 읽기
getfattr -n system.ntfs_attrib /mnt/ntfs/file.txt

# Security Descriptor 원시 데이터 (바이너리)
getfattr -n system.ntfs_security /mnt/ntfs/file.txt

# POSIX ACL 설정 (acl 마운트 옵션 필요)
mount -t ntfs3 -o acl /dev/sdb1 /mnt/ntfs
setfacl -m u:www-data:rx /mnt/ntfs/webroot/
setfacl -m g:developers:rwx /mnt/ntfs/project/
getfacl /mnt/ntfs/webroot/

# 기본 ACL 설정 (새 파일에 자동 적용)
setfacl -d -m u:www-data:rx /mnt/ntfs/webroot/

NTFS3 커널 드라이버 내부 구조

ntfs3 드라이버는 fs/ntfs3/ 디렉터리에 위치하며, NTFS 온-디스크 구조를 Linux VFS 인터페이스로 변환합니다. 핵심 자료구조인 ntfs_sb_info(슈퍼블록), ntfs_inode(inode), runs_tree(Data Run 관리)가 NTFS 메타데이터를 메모리에 캐싱하고 VFS 콜백에 응답합니다.

ntfs3 커널 드라이버 내부 아키텍처 VFS Layer (inode_operations, file_operations, ...) ntfs3.ko (fs/ntfs3/) ntfs_sb_info cluster_size, record_size *mft_ni ($MFT inode) *security_ni ($Secure) used (cluster bitmap) mft_bitmap, *upcase super.c: ntfs_fill_super() ntfs_inode vfs_inode (내장) mft_no (MFT 번호) *mi (MFT_REC 포인터) run ($DATA runs_tree) std_fa, std_security_id inode.c: ntfs_iget() runs_tree (Data Run 캐시) run[] 배열: {vcn, lcn, len} VCN → LCN 매핑 캐시 클러스터 할당/해제 추적 run_pack/unpack: 디스크 인코딩 run_lookup: VCN으로 LCN 검색 run.c: 이진 검색 O(log N) attrib.c 속성 읽기/쓰기/삭제 Res↔NonRes 전환 index.c B+tree 검색/삽입/삭제 노드 분할/병합 record.c MFT 레코드 I/O USA fixup 처리 bitmap.c 클러스터/MFT 할당 wnd_bitmap 관리 lznt.c / xattr.c LZNT1 압축 해제 xattr/ADS/ACL 매핑 Block I/O Layer + Page Cache (sb_bread, submit_bio) NTFS 볼륨 (블록 디바이스)

읽기/쓰기 경로 상세

/* ntfs3 읽기 경로 (fs/ntfs3/file.c) */
static ssize_t ntfs_file_read_iter(struct kiocb *iocb,
                                   struct iov_iter *iter)
{
    /* 1. Resident 파일: MFT 레코드에서 직접 데이터 반환 */
    if (is_resident(ni)) {
        /* MFT 레코드 → 속성 값 → 버퍼 복사 */
        return ntfs_read_resident(ni, iter);
    }

    /* 2. 압축 파일: 압축 단위별 해제 후 반환 */
    if (ni->std_fa & FILE_ATTR_COMPRESSED) {
        return ntfs_file_read_compressed(iocb, iter);
    }

    /* 3. 일반 Non-Resident: generic_file_read_iter() 사용 */
    /*    → Page Cache → readahead → bio 제출 */
    return generic_file_read_iter(iocb, iter);
}

/* ntfs3 쓰기 경로 (fs/ntfs3/file.c) */
static ssize_t ntfs_file_write_iter(struct kiocb *iocb,
                                    struct iov_iter *from)
{
    /* 1. 파일 크기가 Resident 임계값 초과 시 Non-Resident 전환 */
    if (is_resident(ni) && new_size > resident_threshold) {
        attr_make_nonresident(ni);
    }

    /* 2. 클러스터 할당 필요 시 bitmap에서 할당 */
    ntfs_extend(ni, new_size);

    /* 3. generic_file_write_iter() → Page Cache → writeback */
    ret = generic_file_write_iter(iocb, from);

    /* 4. MFT 레코드 메타데이터 업데이트 (크기, 타임스탬프) */
    ntfs_update_time(ni);

    return ret;
}

잠금 모델

위치보호 대상
ni_lockntfs_inodeMFT 레코드 수정 (속성 추가/삭제/변경)
run_lockntfs_inode (file)Data Run 수정 (클러스터 할당/해제)
vol_semntfs_sb_info볼륨 메타데이터 ($LogFile, $Volume)
bitmap lockwnd_bitmap클러스터/MFT 비트맵 수정
i_rwsemVFS inode파일 크기 변경 (truncate, extend)

ftrace/bpftrace NTFS 작업 추적

ntfs3 드라이버의 동작을 디버깅하거나 성능 분석할 때 Linux 커널 트레이싱 도구를 활용할 수 있습니다. ftrace로 ntfs3 함수 호출을 추적하고, bpftrace로 I/O 패턴이나 지연 시간을 측정할 수 있습니다.

ftrace를 이용한 ntfs3 함수 추적

# ntfs3 함수 목록 확인
cat /sys/kernel/debug/tracing/available_filter_functions | grep ntfs

# ntfs3 함수 호출 트레이싱 설정
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo 'ntfs_*' > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on

# NTFS 파일 접근 작업 수행
ls /mnt/ntfs/
cat /mnt/ntfs/test.txt

# 트레이스 결과 확인
cat /sys/kernel/debug/tracing/trace

# 출력 예시:
#  0)               |  ntfs_lookup() {
#  0)               |    ntfs_iget() {
#  0)   1.234 us    |      ntfs_read_mft() {
#  0)               |        ntfs_read_run_nb();
#  0)   0.456 us    |      }
#  0)   2.345 us    |    }
#  0)   3.456 us    |  }

# 특정 함수만 트레이싱
echo 'ntfs_file_read_iter ntfs_file_write_iter ntfs_lookup ntfs_create' \
    > /sys/kernel/debug/tracing/set_ftrace_filter

# 트레이싱 종료
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo nop > /sys/kernel/debug/tracing/current_tracer

bpftrace를 이용한 NTFS I/O 분석

# ntfs3 읽기/쓰기 호출 빈도 측정
bpftrace -e '
kprobe:ntfs_file_read_iter  { @reads = count(); }
kprobe:ntfs_file_write_iter { @writes = count(); }
interval:s:5 { print(@reads); print(@writes); clear(@reads); clear(@writes); }
'

# ntfs3 lookup 지연 시간 히스토그램
bpftrace -e '
kprobe:ntfs_lookup { @start[tid] = nsecs; }
kretprobe:ntfs_lookup /@start[tid]/ {
    @lookup_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
'

# MFT 레코드 읽기 추적 (어떤 MFT 번호가 접근되는지)
bpftrace -e '
kprobe:ntfs_read_mft {
    printf("MFT read: pid=%d comm=%s\n", pid, comm);
}
'

# ntfs3 클러스터 할당 추적
bpftrace -e '
kprobe:ntfs_alloc_clusters {
    printf("Cluster alloc: pid=%d comm=%s\n", pid, comm);
    @alloc_stack = count();
}
'

# NTFS 파일 생성/삭제 모니터링
bpftrace -e '
kprobe:ntfs_create {
    printf("CREATE: pid=%d comm=%s\n", pid, comm);
}
kprobe:ntfs_unlink {
    printf("UNLINK: pid=%d comm=%s\n", pid, comm);
}
'

perf를 이용한 ntfs3 프로파일링

# ntfs3 관련 함수의 CPU 사용 프로파일링
perf record -g -p $(pidof -x fio) -- sleep 10
perf report --symbol-filter=ntfs

# ntfs3 블록 I/O 추적
perf trace -e block:block_rq_issue -p $(pidof -x cp) -- sleep 5

# 파일시스템 이벤트 트레이싱
perf stat -e 'ext4:*,ntfs:*' -- ls /mnt/ntfs/large_dir/

# ntfs3 함수 호출 통계
perf stat -e 'probe:ntfs_lookup,probe:ntfs_file_read_iter' \
    -- find /mnt/ntfs/ -name "*.txt"
디버깅 팁: ntfs3 드라이버 디버그 메시지를 활성화하려면 동적 디버그를 사용합니다:
# ntfs3 모든 디버그 메시지 활성화
echo 'module ntfs3 +p' > /sys/kernel/debug/dynamic_debug/control
dmesg -w | grep ntfs3

# 특정 파일만 활성화
echo 'file fs/ntfs3/super.c +p' > /sys/kernel/debug/dynamic_debug/control
echo 'file fs/ntfs3/inode.c +p' > /sys/kernel/debug/dynamic_debug/control

상호 운용성 (Windows/Linux 공유)

NTFS를 Windows와 Linux 간 데이터 공유 매체로 사용할 때는 파일 이름 호환성, 권한 매핑, 타임스탬프 해석, 대소문자 처리 등 여러 차이점을 고려해야 합니다. 이 섹션에서는 듀얼 부팅, USB 드라이브 공유, 네트워크 파일 공유 시나리오에서의 실전 지침을 제공합니다.

Windows/Linux NTFS 상호 운용성 Windows (NTFS 네이티브) - 대소문자 비구분 (기본) - ACL/SID 기반 보안 모델 - FILETIME (100ns, 1601 기준) - ADS, Reparse Point 완전 지원 - EFS 암호화, VSS 스냅샷 - 예약 문자: \ / : * ? " < > | - Fast Startup → $LogFile dirty 유지 Linux (ntfs3 드라이버) - 대소문자 구분 (POSIX 기본) - POSIX uid/gid/mode 매핑 - time_t/timespec64 (1970 기준) - ADS → xattr, symlink 매핑 - EFS 미지원, VSS 미지원 - 금지 문자: / 와 NUL만 - dirty 시 기본 RO 마운트 호환성 이슈 파일명 호환성 매트릭스 Linux에서 생성 주의 - 파일명에 : \ * ? " < > | 사용 금지 - CON, PRN, AUX, NUL 등 예약 이름 - 대소문자만 다른 파일 동시 생성 불가 타임스탬프 차이 - Windows: FILETIME (100ns, 1601-01-01) - Linux: timespec64 (1ns, 1970-01-01) - ntfs3가 자동 변환, 1601 이전 불가 권한 매핑 - NTFS ACL ↔ POSIX mode 변환 손실 - 세밀한 ACL은 POSIX로 완전 표현 불가 - acl 옵션으로 확장 ACL 사용 가능 실전 권장사항 1. Windows Fast Startup 비활성화: powercfg /h off → NTFS dirty 방지 → Linux에서 RW 마운트 가능 2. 파일명 규칙: Windows 예약 문자(: \ * ? " < > |) 회피, ASCII 파일명 권장, 255자 이내 3. 마운트 옵션: uid/gid/fmask/dmask 설정으로 기본 권한 지정, noatime으로 불필요한 MFT 쓰기 방지 4. 대소문자: ntfs3는 NTFS 규칙(비구분)을 따름 → Linux에서도 File.TXT = file.txt로 접근 가능 5. 듀얼 부팅: Windows 종료 시 "완전 종료"(Shift+종료) 사용 → 하이버네이션 잔류 방지

파일 이름 호환성 상세

문자/이름WindowsLinux (NTFS)상호 운용 시
: (콜론)ADS 구분자일반 문자 (ext4)NTFS에서는 ADS로 해석되므로 파일명에 사용 금지
\ (백슬래시)경로 구분자일반 문자 (ext4)NTFS에서는 경로 구분자이므로 파일명에 사용 금지
CON, PRN, NUL예약 디바이스일반 파일명NTFS에서 생성 불가, Linux에서 생성해도 Windows 접근 불가
파일명 끝 . 또는 공백자동 제거허용Windows에서 보이지 않거나 접근 불가
대소문자 차이 파일같은 파일 취급다른 파일 (ext4)ntfs3에서 대소문자 비구분으로 동작, 충돌 방지

듀얼 부팅 설정 가이드

# 1. Windows Fast Startup 비활성화 (Windows 관리자 CMD)
# powercfg /h off

# 2. Linux에서 NTFS 파티션 확인
lsblk -f | grep ntfs
# NAME  FSTYPE LABEL  UUID                    MOUNTPOINT
# sda3  ntfs   Windows C23456789...

# 3. dirty 상태 확인 및 수정
ntfsfix /dev/sda3
# Mounting volume... Windows is hibernated, refusing to mount.
# → Windows에서 완전 종료 후 재시도

# 4. fstab 설정 (자동 마운트)
# UUID=C23456789... /mnt/windows ntfs3 defaults,noatime,uid=1000,gid=1000,dmask=022,fmask=133 0 0

# 5. Windows 하이버네이션 감지 시 읽기 전용 대안
# UUID=C23456789... /mnt/windows ntfs3 ro,noatime 0 0

# 6. systemd automount 설정 (필요 시 자동 마운트)
# /etc/systemd/system/mnt-windows.automount
# [Automount]
# Where=/mnt/windows
# TimeoutIdleSec=300

Samba를 통한 NTFS 네트워크 공유

# NTFS 볼륨을 Samba로 공유 시 주의사항
# /etc/samba/smb.conf
[shared_ntfs]
    path = /mnt/ntfs_data
    read only = no
    browseable = yes
    # NTFS는 이미 대소문자 비구분이므로
    case sensitive = no
    # NTFS ACL 활용
    vfs objects = acl_xattr
    map acl inherit = yes
    # DOS 속성 매핑
    store dos attributes = yes
    map archive = no
    map hidden = no
    map system = no

# NTFS xattr을 Samba DOS 속성으로 노출
# ntfs3의 system.ntfs_attrib xattr 활용
getfattr -d -m ".*" /mnt/ntfs_data/file.txt
USB 드라이브 공유: Windows/Linux 간 USB 드라이브 공유 시, NTFS보다 exFAT가 더 호환성이 좋은 경우가 있습니다 (ACL/저널링 불필요 시). 하지만 4GB 이상 파일과 저널링이 필요하면 NTFS가 적합하며, ntfs3 드라이버의 성능 이점을 활용할 수 있습니다. 외장 드라이브를 안전하게 분리하려면 반드시 umount 후 제거하세요.

NTFS와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.