NTFS 파일시스템 심화
Windows 기본 파일시스템인 NTFS를 Linux 관점에서 심층 분석합니다. MFT 레코드와 속성(attribute) 기반 데이터 모델, 디렉터리 인덱스(B-tree) 검색, `$LogFile` 트랜잭션 저널 복구 개념, ACL/보안 식별자/대체 데이터 스트림(ADS) 기능, ntfs3 드라이버의 읽기·쓰기 경로와 호환성 제약, 대용량 볼륨 운용 시 성능·무결성 튜닝 포인트를 상세히 설명합니다.
핵심 요약
- 계층 이해 — VFS, 캐시, 하위 FS 경계를 구분합니다.
- 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
- 저장 정책 — 저널링/압축/할당 정책 차이를 비교합니다.
- 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
- 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.
단계별 이해
- 경계 계층 파악
요청이 VFS에서 어디로 내려가는지 확인합니다. - 메타/데이터 분리
어느 경로에서 무엇이 갱신되는지 나눠 봅니다. - 동기화/플러시 확인
쓰기 반영 시점과 순서를 검증합니다. - 복구 시나리오 점검
비정상 종료 후 일관성 회복을 확인합니다.
개요 & 역사
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 구조
로그 레코드 타입
| 작업 코드 | 설명 | 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단계:
- 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) |
커널 설정
# 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):
- 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(확장 속성) 인터페이스로 노출합니다:
- 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에서 지원되지 않습니다.
MFT 레코드 구조 심화
MFT 레코드는 NTFS에서 파일과 디렉터리를 표현하는 기본 단위입니다. 1024바이트 고정 크기의 레코드 안에 헤더, Fixup Array, 그리고 하나 이상의 속성이 순서대로 배치됩니다. 작은 파일은 데이터까지 레코드 내부에 저장(Resident)되지만, 큰 파일은 외부 클러스터를 참조(Non-Resident)합니다. 하나의 레코드로 모든 속성을 담지 못하면 확장 레코드(Extension Record)를 할당하고 $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_USE | 0x01 | 레코드가 활성 파일/디렉터리에 사용 중 |
| DIRECTORY | 0x02 | 레코드가 디렉터리 ($INDEX_ROOT 포함) |
| IN_EXTEND | 0x04 | $Extend 디렉터리의 시스템 파일 |
| IS_VIEW_INDEX | 0x08 | 뷰 인덱스 (보안/객체 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 구분에 따라 저장 방식이 달라집니다. 이 섹션에서는 주요 속성의 내부 구조를 상세히 분석합니다.
$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 파일명 */
};
NTFS3 vs NTFS-3G 비교
Linux에서 NTFS 읽기/쓰기를 지원하는 두 가지 주요 구현의 아키텍처와 성능 차이를 비교합니다. ntfs3는 커널 공간에서 직접 실행되어 커널 I/O 스택과 긴밀히 통합되고, ntfs-3g는 FUSE 프레임워크를 통해 유저스페이스에서 실행됩니다.
성능 비교
| 벤치마크 항목 | ntfs3 | ntfs-3g | 차이 요인 |
|---|---|---|---|
| 순차 읽기 | ~400 MB/s | ~250 MB/s | Page 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 데몬 프로세스 메모리 |
기능 매트릭스
| 기능 | ntfs3 | ntfs-3g | 비고 |
|---|---|---|---|
| LZNT1 압축 읽기/쓰기 | 지원 | 지원 | 동등 |
| WOF 압축 (LZX/Xpress) | 읽기만 | 미지원 | CONFIG_NTFS3_LZX_XPRESS 필요 |
| ADS (대체 데이터 스트림) | xattr | xattr | 접근 방식 동일 |
| 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로 롤백합니다.
로그 레코드 상세 구조
/* $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: 내부 메타데이터 복구용 저널. 크래시 복구에만 사용되며, 정상 동작 시 재활용됩니다.
- $UsnJrnl: 외부 변경 알림용 저널. 어떤 파일이 언제 변경되었는지 유저스페이스에 알려주는 변경 추적 기록입니다.
- $LogFile은 시스템 전용이고, $UsnJrnl은 백업/검색 소프트웨어가 참조합니다.
B+tree 인덱스 심화
NTFS 디렉터리는 B+ tree를 사용하여 파일명을 정렬 관리합니다. 이 인덱스 구조는 $INDEX_ROOT(루트 노드, 항상 Resident)와 $INDEX_ALLOCATION(비-루트 노드, Non-Resident)으로 구성됩니다. 각 인덱스 블록(보통 4096바이트)은 정렬된 인덱스 엔트리를 포함하며, $FILE_NAME 속성의 사본을 키로 사용하여 $UpCase 테이블 기반 유니코드 비교로 검색합니다.
인덱스 검색 과정 (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) | 리프 노드 순차 스캔 (정렬된 순서) |
NTFS 압축 심화
NTFS는 $DATA 속성에 LZNT1 알고리즘을 사용한 투명한 압축을 지원합니다. 압축은 16개 클러스터(기본 64 KiB)를 하나의 압축 단위(Compression Unit)로 처리하며, 각 단위를 독립적으로 압축/해제할 수 있어 랜덤 접근 시에도 전체 파일을 해제할 필요가 없습니다. 압축 후 크기가 원본보다 줄어들면 절약된 공간을 Sparse Run으로 표현합니다.
압축 트레이드오프
| 장점 | 단점 |
|---|---|
| 디스크 공간 절약 (텍스트 파일 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(감사)로 구성됩니다.
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 ACL | Linux POSIX | 매핑 방식 |
|---|---|---|
| Owner SID | uid | 마운트 옵션 uid= 또는 SID→uid 매핑 |
| Group SID | gid | 마운트 옵션 gid= 또는 SID→gid 매핑 |
| DACL Allow/Deny | rwx 비트 | Access Mask → Read/Write/Execute 변환 |
| DACL (acl 옵션) | POSIX ACL | setfacl/getfacl로 확장 ACL 관리 |
| 파일 속성 (Read-Only 등) | xattr | system.ntfs_attrib로 읽기/쓰기 |
| SD 바이너리 | xattr | system.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 읽기 경로 (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_lock | ntfs_inode | MFT 레코드 수정 (속성 추가/삭제/변경) |
| run_lock | ntfs_inode (file) | Data Run 수정 (클러스터 할당/해제) |
| vol_sem | ntfs_sb_info | 볼륨 메타데이터 ($LogFile, $Volume) |
| bitmap lock | wnd_bitmap | 클러스터/MFT 비트맵 수정 |
| i_rwsem | VFS 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 모든 디버그 메시지 활성화
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) | 상호 운용 시 |
|---|---|---|---|
: (콜론) | 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
umount 후 제거하세요.
관련 문서
NTFS와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.