NTFS 파일시스템 심화
NTFS MFT 구조, 속성 시스템, B-tree 인덱싱, $LogFile 저널링, ntfs3 커널 드라이버, 보안 모델, 성능 튜닝 종합 가이드.
개요 & 역사
NTFS(New Technology File System)는 1993년 Windows NT 3.1과 함께 출시된 Microsoft의 주력 파일시스템입니다. FAT/HPFS를 대체하기 위해 설계되었으며, 메타데이터 저널링, ACL 기반 보안, 압축, 암호화(EFS), Alternate Data Streams 등 엔터프라이즈 기능을 제공합니다. 현재까지 Windows 운영체제의 기본 파일시스템으로 사용되고 있습니다.
NTFS 버전 역사
| 버전 | OS | 주요 변경사항 |
|---|---|---|
| 1.0 | NT 3.1 (1993) | 최초 릴리스, 기본 MFT/저널링 구조 |
| 1.1 | NT 3.5 (1994) | 압축 파일 지원 (LZNT1) |
| 1.2 | NT 4.0 (1996) | Security Descriptor 스트림, 디스크 쿼터 기반 구축 |
| 3.0 | Windows 2000 | 디스크 쿼터, EFS 암호화, Reparse Point, $UsnJrnl 변경 저널, Sparse 파일 |
| 3.1 | Windows XP+ | $MFT 미러 확장, 셀프 힐링(Self-Healing), MFT 엔트리 재사용 시퀀스 번호 강화 |
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 방식) |
| 대소문자 | 대소문자 보존, 기본적으로 대소문자 비구분 |
| 파일시스템 ID | MBR: 0x07, GPT: EBD0A0A2-B9E5-4433-... |
Linux에서의 NTFS 지원 개요
Linux에서 NTFS 접근은 세 가지 방식으로 발전해왔습니다:
- ntfs.ko (커널 2.6~6.8): 읽기 전용 레거시 드라이버, 6.9에서 삭제
- ntfs-3g (FUSE): 완전한 읽기/쓰기 지원, 유저스페이스 구현으로 성능 제한
- ntfs3 (커널 5.15+): Paragon Software 기여, 커널 네이티브 읽기/쓰기 완전 지원
NTFS 디스크 레이아웃
NTFS 볼륨은 크게 VBR(Volume Boot Record), MFT Zone, Data Area로 구성됩니다. 모든 것이 파일로 표현되는 것이 NTFS의 핵심 설계 원칙입니다. 디렉터리조차 특별한 속성을 가진 MFT 레코드에 불과합니다.
시스템 메타데이터 파일 상세
| MFT # | 이름 | 역할 |
|---|---|---|
| 0 | $MFT | MFT 자체를 기술하는 레코드 (자기 참조) |
| 1 | $MFTMirr | $MFT 처음 4개 레코드의 백업 (복구용) |
| 2 | $LogFile | 트랜잭션 저널 (메타데이터 일관성 보장) |
| 3 | $Volume | 볼륨 이름, NTFS 버전, 플래그 (dirty bit 등) |
| 4 | $AttrDef | 속성 타입 정의 테이블 |
| 5 | . (root) | 루트 디렉터리 (\) |
| 6 | $Bitmap | 클러스터 할당 비트맵 (1비트/클러스터) |
| 7 | $Boot | VBR(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 시그니처 */
MFT (Master File Table) 구조
MFT는 NTFS의 핵심 자료구조로, 볼륨의 모든 파일과 디렉터리를 레코드 단위로 관리합니다. 각 MFT 엔트리(레코드)는 1024바이트 고정 크기이며, 파일의 모든 메타데이터와 작은 파일의 데이터까지 포함할 수 있습니다. MFT 자체도 하나의 파일($MFT, 레코드 #0)로 볼륨에 존재합니다.
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_ID | GUID 기반 파일 고유 식별자 | 항상 |
| 0x50 | $SECURITY_DESCRIPTOR | 레거시 보안 디스크립터 (NT 4.0 이하) | 보통 |
| 0x60 | $VOLUME_NAME | 볼륨 이름 (NTFS 레이블) | 항상 |
| 0x70 | $VOLUME_INFORMATION | NTFS 버전, 볼륨 플래그 | 항상 |
| 0x80 | $DATA | 파일 데이터 스트림 (기본 + ADS) | 가변 |
| 0x90 | $INDEX_ROOT | B-tree 인덱스 루트 노드 (디렉터리) | 항상 |
| 0xA0 | $INDEX_ALLOCATION | B-tree 인덱스 노드 저장소 | Non-Res |
| 0xB0 | $BITMAP | 인덱스/MFT 할당 비트맵 | 가변 |
| 0xC0 | $REPARSE_POINT | Reparse Point 데이터 (심볼릭 링크, 마운트 포인트) | 항상 |
| 0xD0 | $EA_INFORMATION | Extended Attribute 정보 | 항상 |
| 0xE0 | $EA | Extended 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
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 테이블을 사용하여 대소문자를 구분하지 않는 유니코드 비교를 수행합니다.
$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%, 최대 64 MiB)│
* └─────────────────────────────────────────┘
*/
로그 레코드 타입
| 작업 코드 | 설명 | Undo 작업 |
|---|---|---|
| InitializeFileRecordSegment | MFT 레코드 초기화 | DeallocateFileRecordSegment |
| CreateAttribute | 속성 생성 | DeleteAttribute |
| DeleteAttribute | 속성 삭제 | CreateAttribute |
| UpdateResidentValue | Resident 속성 값 변경 | UpdateResidentValue (이전 값) |
| UpdateNonResidentValue | Non-Resident 데이터 변경 | Noop 또는 역 데이터 |
| AddIndexEntryRoot | 인덱스 루트에 엔트리 추가 | DeleteIndexEntryRoot |
| AddIndexEntryAllocation | 인덱스 할당에 엔트리 추가 | DeleteIndexEntryAllocation |
| SetBitsInNonResidentBitMap | 비트맵 비트 설정 | ClearBitsInNonResidentBitMap |
| UpdateMappingPairs | Data Run 업데이트 | UpdateMappingPairs (이전 값) |
복구 과정 (Analysis → Redo → Undo)
/*
* NTFS 복구 3단계:
*
* 1. Analysis Pass (분석):
* - Restart Area에서 마지막 체크포인트 LSN 읽기
* - 체크포인트 이후의 모든 로그 레코드 스캔
* - Dirty Page Table + Transaction Table 재구성
*
* 2. Redo Pass (재실행):
* - 체크포인트 이후 커밋된 트랜잭션의 Redo 레코드 순서대로 재실행
* - 디스크에 반영되지 않은 메타데이터 변경 완료
*
* 3. 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) |
커널 설정
# 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.c | inode operations, ntfs_iget(), 속성 읽기/쓰기 |
| file.c | file operations, 읽기/쓰기, fallocate, fiemap |
| dir.c | directory operations, readdir, lookup |
| namei.c | inode operations (create, link, unlink, rename) |
| attrib.c | NTFS 속성 관리 (읽기/쓰기/삽입/삭제) |
| index.c | B-tree 인덱스 operations (삽입/삭제/검색) |
| record.c | MFT 레코드 I/O, USA 처리 |
| run.c | Data Run 인코딩/디코딩, 클러스터 할당 |
| bitmap.c | 비트맵 operations (클러스터/MFT 할당) |
| fsntfs.c | NTFS 전용 유틸리티 (MFT Mirror, $LogFile, 볼륨 플래그) |
| frechet.c | 이름 해싱, 검색 최적화 |
| lznt.c | LZNT1 압축/해제 |
| 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):
*
* 1. VBR 읽기 및 검증
* - "NTFS " OEM ID 확인
* - bytes_per_sector, sectors_per_cluster 파싱
* - MFT/MFTMirr LCN 계산
*
* 2. ntfs_sb_info 초기화
* - 슈퍼블록 정보 구조체 할당/초기화
* - 클러스터 크기, MFT 레코드 크기 설정
*
* 3. 시스템 메타데이터 파일 로드
* - $MFT(#0) → MFT 자체 Data Run 파싱
* - $MFTMirr(#1) → 미러 검증
* - $LogFile(#2) → 저널 상태 확인, 필요 시 재생
* - $Volume(#3) → dirty 플래그 확인
* - $AttrDef(#4) → 속성 정의 로드
* - $Bitmap(#6) → 클러스터 비트맵 로드
* - $Secure(#9) → 보안 디스크립터 인덱스
* - $UpCase(#10) → 대문자 변환 테이블 로드
*
* 4. 루트 디렉터리 inode 로드
* - MFT 레코드 #5 (루트 디렉터리)
* - d_make_root()로 루트 dentry 생성
*
* 5. 볼륨 dirty 플래그 설정 (읽기-쓰기 마운트 시)
*/
xattr 매핑
ntfs3는 NTFS의 다양한 속성을 Linux xattr(확장 속성) 인터페이스로 노출합니다:
- system.ntfs_attrib: NTFS 파일 속성 플래그 (읽기 전용, 숨김, 시스템 등)
- system.ntfs_security: Security Descriptor (바이너리)
- user.*: ADS(Alternate Data Streams) 매핑
- system.posix_acl_access / system.posix_acl_default: POSIX ACL (acl 옵션 시)
핵심 커널 자료구조 (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 압축 적용 |
성능 고려사항
- noatime: atime 업데이트를 방지하여 불필요한 MFT 레코드 쓰기를 줄입니다.
- prealloc: 대용량 파일 쓰기 시 사전 할당으로 단편화를 방지합니다.
- discard: SSD에서 TRIM 명령을 발행하여 가비지 컬렉션 효율을 높입니다.
- 압축 비활성화: LZNT1 압축은 CPU 오버헤드가 크므로, 성능이 중요한 경우 비활성화합니다.
- MFT 단편화: 볼륨이 거의 가득 차면 MFT Zone이 소진되어 MFT 단편화가 발생합니다. 볼륨 사용률을 80% 이하로 유지하는 것이 좋습니다.
- 클러스터 크기: 대용량 파일 위주라면 큰 클러스터(64 KiB) 사용 시 메타데이터 오버헤드가 감소합니다. 단, 압축은 기본 클러스터(4 KiB)에서만 지원됩니다.
마운트 예시
# 기본 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 리셋) |
| ntfsresize | NTFS 볼륨 크기 조정 (축소/확장) |
| ntfsclone | NTFS 볼륨 복제 (사용 중인 클러스터만 복사) |
| ntfslabel | 볼륨 레이블 읽기/변경 |
| ntfsinfo | NTFS 볼륨/파일 메타데이터 정보 출력 |
| ntfsls | NTFS 디렉터리 목록 (마운트 없이 직접 읽기) |
| ntfscat | NTFS 파일 내용 출력 (마운트 없이 직접 읽기) |
| ntfscp | NTFS 볼륨에 파일 복사 (마운트 없이 직접 쓰기) |
| ntfscmp | 두 NTFS 볼륨 비교 |
| ntfswipe | NTFS 볼륨 데이터 안전 삭제 |
| ntfsdecrypt | EFS 암호화 파일 복호화 |
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 다른 파일시스템 비교
| 항목 | NTFS | ext4 | XFS | Btrfs | exFAT |
|---|---|---|---|---|---|
| 저널링 | 메타데이터 | 메타+데이터(옵션) | 메타데이터(WAL) | COW(저널 불필요) | 없음 |
| 최대 볼륨 | 256 TiB | 1 EiB | 8 EiB | 16 EiB | 128 PiB |
| 최대 파일 | 256 TiB | 16 TiB | 8 EiB | 16 EiB | 128 PiB |
| 압축 | LZNT1 | 없음 | 없음 | zstd/lzo/zlib | 없음 |
| 암호화 | EFS | fscrypt | 없음 | 없음 | 없음 |
| 스냅샷 | VSS (Windows) | 없음 | 없음 | 네이티브 | 없음 |
| Reflink | 없음 | 없음 | 지원 | 지원 | 없음 |
| 데이터 체크섬 | 없음 | 메타만 | 메타만 | 데이터+메타 | 없음 |
| Linux 지원 | ntfs3(5.15+) | 네이티브 | 네이티브 | 네이티브 | 네이티브(5.4+) |
| Windows 지원 | 네이티브 | 서드파티 | 없음 | WinBtrfs | 네이티브 |
| 주 사용처 | Windows 시스템 | Linux 범용 | 대용량/엔터프라이즈 | Linux 데스크탑/NAS | 이동식 미디어 |
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에서 지원되지 않습니다.