0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NTFSの仕組みとWin32 APIによる読み書きの実装例

Posted at

はじめに

NTFSのファイル管理の基本的な方式とプログラミングインタフェース、実装例を説明します。

Windowsのファイルシステムの基本概念と方式

WindowsのファイルシステムはNTFS (NT File System) であり、Microsoftが開発したWindows NT系(現在のWindowsを含む)OSのファイルシステムです。

NTFSのファイル管理の基本は、MFT (マスターファイルテーブル) と呼ばれるデータ構造です。これはLinuxのInodeとメタデータの概念に相当します。
MFTは、すべてのファイルとディレクトリのメタデータを保持する表であり、それぞれはMFTレコードと呼ばれるエントリを割り当てられます。ファイルに関する全ての情報(ファイルサイズ、作成日時、更新日時、アクセス権限、データブロックの位置など)はMFTエントリに格納されます。実際のファイルのデータは、非常に小さなファイルの場合にはMFTエントリ内に格納され、そうでない場合はMFTエントリが指すデータブロックに格納されます。
MFTの領域はフォーマット時に予約され、これをMFTゾーンと呼びます。

NTFSがディスクを管理する基本単位をクラスタと呼び、他のファイルシステムのデータブロックに相当します。
MFTエントリ内に入り切らないデータはクラスタに分割されて格納され、そのクラスタの位置はMFTエントリに記録されます。

NTFSのジャーナリングは、$LogFile と呼ばれるログファイルによって行われます。
ファイルに対する操作はまず $LogFile に記録され、その後に実際のファイルデータが更新されます。
途中で障害が発生した場合でも、次回起動時に $LogFile を参照してデータを復元し、データの損失を防ぎます。
また、NTFSには自己修復NTFSと呼ばれる機能があり、ストレージが接続されているままの状態で軽微なファイルシステムの破損を修復できます。深刻な場合は chkdsk コマンドで修復します。

ファイルのキャッシュについては、NTFS単独ではなく、Windowsのキャッシュマネージャが担当します。他のファイルシステムでも動作します。Windowsでは読み書き双方でキャッシュが有効です。
書き込み時には遅延書き込みが行われ、すぐには補助記憶装置に書き込まれません。一定時間使われなかったキャッシュはキャッシュフラッシュのタイミングで補助記憶装置に書き込まれます。
大きなファイルを書き込む場合など、キャッシュを無効にしたほうが良い場合は、ファイルを開く際のコマンド CreateFile のフラグとして FILE_FLAG_NO_BUFFERING を指定することで、キャッシュを無効にして直接補助記憶装置にアクセスできます。通常は FILE_FLAG_WRITE_THROUGH とともに設定します。なお、ファイルのメタデータは必ずキャッシュされます。
ファイルキャッシュからフラッシュする頻度は、OSの効率と信頼性のトレードオフです。効率のためにはキャッシュに長時間ファイルを保持するほうが良いですが、途中の障害でデータが失われる可能性があります。

ファイルシステムが提供する機能には他にも、アクセス制御、暗号化、圧縮などがあります。
アクセス制御は、ファイルやディレクトリに対して、読み取り、書き込み、実行などのアクセス権限を設定することで、ユーザやグループごとに異なるアクセス権限を設定できます。WindowsのNTFSではアクセス制御リスト (ACL) を使用して管理します。ユーザやグループに対して権限を設定したものをアクセス制御エントリ (ACE) と呼び、アクセス制御リストはこれらのリストです。
アクセス制御リストには二つの種類があり、DACL(随意ACL)とSACL(システムACL)があります。
DACLは基本的なアクセス制御リストで、アクセス時に参照されます。DACLが設定されていない場合はすべてのアクセスが許可され、DACLが設定されてACLが存在しない場合はすべてのアクセスが拒否されます。
SACLは監査のためのアクセス制御リストで、オブジェクトへのアクセスが監査されるかどうかを設定します。具体的には、ファイルなどへの操作が行われるときにログに残すなどの監査ルールを設定できます。

NTFSはドライブの暗号化もサポートしています。Encrypting File System (EFS) という機能で、ファイルやディレクトリを暗号化してデータを保護します。ユーザが暗号化したいファイルやディレクトリを選択して暗号化できます。Windowsには他に、ドライブ全体を暗号化するBitLockerという機能もあります。TPMというハードウェアのセキュリティ機能を利用して、ドライブ全体を暗号化し、持ち出された場合でもデータを保護できます。

プログラミングインタフェース

NTFSはWindowsが提供するAPIを通じてアクセスされます。ファイルのオープン、クローズ、読み書きなどの操作はWindows API (Win32 API) を通じて行われ、C言語やC++などから利用可能です。
Win32 APIでは、ファイルやディレクトリを扱うための次のような関数が提供されています(Fileapi.h ヘッダーに定義)。Windowsのファイルシステムにアクセスするための基本的な関数です。

  • CreateFile: ファイルやI/Oデバイスを開く・作成する。文字コードによって CreateFileACreateFileW があり、通常は CreateFileW を使用。
  • ReadFile: ファイルからデータを読み込む
  • WriteFile: ファイルにデータを書き込む
  • CloseHandle: ファイルを閉じる
  • SetFilePointerEx: ファイルポインタを移動する
  • GetFileAttributesEx: ファイル・ディレクトリの属性を取得する
  • GetFileSizeEx: ファイルのサイズを取得する
  • LockFile / UnlockFile: ファイルのロック・アンロック

関数プロトタイプ例

CreateFileA

HANDLE CreateFileA(
  LPCSTR lpFileName,
  DWORD dwDesiredAccess,
  DWORD dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD dwCreationDisposition,
  DWORD dwFlagsAndAttributes,
  HANDLE hTemplateFile
);
  • lpFileName: ファイルパスを指定
  • dwDesiredAccess: アクセス権(GENERIC_READ, GENERIC_WRITE など)
  • dwShareMode: 他プロセスとの共有モード
  • lpSecurityAttributes: セキュリティ属性(通常はNULL)
  • dwCreationDisposition: 作成方法(新規: CREATE_NEW、既存: OPEN_EXISTING など)
  • dwFlagsAndAttributes: フラグと属性(通常は FILE_ATTRIBUTE_NORMAL。キャッシュ関連は FILE_FLAG_NO_BUFFERING, FILE_FLAG_WRITE_THROUGH など)
  • hTemplateFile: テンプレートファイル(通常はNULL)

ReadFile

BOOL ReadFile(
  HANDLE hFile,
  LPVOID lpBuffer,
  DWORD nNumberOfBytesToRead,
  LPDWORD lpNumberOfBytesRead,
  LPOVERLAPPED lpOverlapped
);
  • hFile: 開いたファイルのハンドル(CreateFileで取得)
  • lpBuffer: 読み込んだデータを格納するバッファ
  • nNumberOfBytesToRead: 読み込むバイト数
  • lpNumberOfBytesRead: 実際に読み込まれたバイト数
  • lpOverlapped: 非同期I/O用(通常はNULL)

WriteFile

BOOL WriteFile(
  HANDLE hFile,
  LPCVOID lpBuffer,
  DWORD nNumberOfBytesToWrite,
  LPDWORD lpNumberOfBytesWritten,
  LPOVERLAPPED lpOverlapped
);
  • hFile: 開いたファイルのハンドル
  • lpBuffer: 書き込むデータのバッファ
  • nNumberOfBytesToWrite: 書き込むバイト数
  • lpNumberOfBytesWritten: 実際に書き込まれたバイト数
  • lpOverlapped: 非同期I/O用(通常はNULL)

サンプルプログラム

WindowsのファイルシステムAPIを用いたファイルの読み書き性能測定プログラム例と、その簡単な解説を以下に示します。

実装例(C言語)

Visual Studio 2022でのコンソールアプリケーションとしてビルドしています。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <stdint.h>

#define TOTAL_SIZE (1LL << 30)
#define CHUNK_SIZE (16 * 1024 * 1024)
#define TMP_PATH "D:\\"
#define TMP_FILENAME "bench.tmp"

uint64_t get_time_ms() {
  FILETIME ft;
  GetSystemTimeAsFileTime(&ft);
  ULARGE_INTEGER uli;
  uli.LowPart = ft.dwLowDateTime;
  uli.HighPart = ft.dwHighDateTime;
  return uli.QuadPart / 10000;
}

void fill_random_data(char* buf, size_t size) {
  for (size_t i = 0; i < size; i++) {
    buf[i] = (char)(rand() % 256);
  }
}

void print_datetime() {
  time_t now = time(NULL);
  struct tm local;
  localtime_s(&local, &now);
  char buf[64];
  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &local);
  printf("実行日時: %s\n", buf);
}

int main(int argc, char* argv[]) {
  size_t chunk_size = CHUNK_SIZE;
  int64_t total_size = TOTAL_SIZE;
  char tmp_path[256] = TMP_PATH;
  int use_cache = 0;

  if (argc >= 2) total_size = _strtoi64(argv[1], NULL, 10);
  if (argc >= 3) chunk_size = (size_t)atoll(argv[2]);
  if (argc >= 4) strncpy(tmp_path, argv[3], sizeof(tmp_path) - 1);
  if (argc >= 5) use_cache = atoi(argv[4]);
  if (argc >= 6) {
    printf("%s [total_size] [chunk_size] [tmp_path] [use_cache]\n", argv[0]);
    return 1;
  }

  char file_path[MAX_PATH];
  snprintf(file_path, sizeof(file_path), "%s%s", tmp_path, TMP_FILENAME);

  char* buf = (char*)malloc(chunk_size);
  if (!buf) {
    fprintf(stderr, "メモリ確保失敗\n");
    return 1;
  }
  fill_random_data(buf, chunk_size);

  DWORD flags = use_cache ? FILE_ATTRIBUTE_NORMAL : (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH);
  printf("=== スピードテスト ===\n");
  printf("ファイルパス: %s\n", file_path);
  printf("書込サイズ: %lld バイト (%lldメガバイト)\n", (long long)total_size, (long long)(total_size >> 20));
  printf("チャンクサイズ: %zu バイト (%lldメガバイト)\n", chunk_size, (long long)chunk_size >> 20);
  printf("キャッシュ%s\n", use_cache ? "有効" : "無効");

  HANDLE hFile = CreateFileA(file_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, flags, NULL);
  if (hFile == INVALID_HANDLE_VALUE) {
    fprintf(stderr, "ファイルオープン失敗: %lu\n", GetLastError());
    free(buf);
    return 1;
  }

  uint64_t start_write_time = get_time_ms();
  int64_t total_written = 0;
  DWORD written;
  while (total_written < total_size) {
    size_t to_write = (total_size - total_written < chunk_size) ? (total_size - total_written) : chunk_size;
    if (!WriteFile(hFile, buf, (DWORD)to_write, &written, NULL)) {
      fprintf(stderr, "書込失敗: %lu\n", GetLastError());
      CloseHandle(hFile);
      free(buf);
      DeleteFileW(file_path);
      return 1;
    }
    total_written += to_write;
  }
  FlushFileBuffers(hFile);
  uint64_t end_write_time = get_time_ms();
  CloseHandle(hFile);

  double write_time = (end_write_time - start_write_time) / 1000.0;
  double write_mb_per_sec = (total_size / (1 << 20)) / write_time;
  printf("書き込み: %.3f MB/s\n書込時間: %.3f s\n", write_mb_per_sec, write_time);

  hFile = CreateFileA(file_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, flags, NULL);
  if (hFile == INVALID_HANDLE_VALUE) {
    fprintf(stderr, "ファイルオープン失敗: %lu\n", GetLastError());
    free(buf);
    return 1;
  }

  uint64_t start_read_time = get_time_ms();
  int64_t total_read = 0;
  DWORD read = 0;
  while (total_read < total_size) {
    size_t to_read = (total_size - total_read < chunk_size) ? (total_size - total_read) : chunk_size;
    if (!ReadFile(hFile, buf, (DWORD)to_read, &read, NULL) || read != (DWORD)to_read) {
      fprintf(stderr, "読み込み失敗: %lu\n", GetLastError());
      CloseHandle(hFile);
      free(buf);
      DeleteFileW(file_path);
      return 1;
    }
    total_read += to_read;
  }
  CloseHandle(hFile);
  uint64_t end_read_time = get_time_ms();
  double read_time = (end_read_time - start_read_time) / 1000.0;
  double read_mb_per_sec = (total_size / (1 << 20)) / read_time;
  printf("読み込み: %.3f MB/s\n読み込み時間: %.3f s\n", read_mb_per_sec, read_time);

  free(buf);
  DeleteFileA(file_path);
  printf("テスト終了\n");
  print_datetime();
  return 0;
}

解説

このプログラムは、WindowsのファイルシステムAPI(CreateFileA, WriteFile, ReadFileなど)を用いて、指定サイズのファイルを書き込み・読み込みし、その速度を計測します。

主な流れは以下の通りです。

  1. コマンドライン引数でファイルサイズ・チャンクサイズ・一時ファイルパス・キャッシュ有無を指定可能。
  2. ランダムデータでバッファを埋め、キャッシュ有無に応じたフラグでファイルを作成。
  3. WriteFileで書き込み、FlushFileBuffersで強制フラッシュ。
  4. 書き込み速度・時間を計測。
  5. ReadFileで読み込み、読み込み速度・時間を計測。
  6. 終了後は一時ファイルを削除。

Windows APIのHANDLE型やFILETIME構造体、エラー取得関数など、Windows特有の型・関数を多用しています。
キャッシュ有効時はFILE_ATTRIBUTE_NORMAL、無効時はFILE_FLAG_NO_BUFFERINGとFILE_FLAG_WRITE_THROUGHを指定し、キャッシュの有無による性能差も検証できます。


使用している主な関数・型

  • HANDLE : Windowsのファイルハンドル型。ファイルやI/Oデバイスを識別する。
  • CreateFileA : ファイルを開く/作成するAPI。パス・権限・フラグ等を指定。
  • WriteFile / ReadFile : ファイルへの書き込み・読み込みAPI。バッファ・サイズを指定。
  • FlushFileBuffers : 書き込みバッファを強制的にディスクへフラッシュ。
  • GetLastError : 直近のAPIエラーコードを取得。
  • FILETIME / ULARGE_INTEGER : Windowsの高精度時刻管理用構造体。
  • GetSystemTimeAsFileTime : システム時刻をFILETIMEで取得。
  • DeleteFileA / DeleteFileW : ファイル削除API。
  • malloc / free : メモリ確保・解放(C標準)。
  • snprintf / strncpy : 文字列操作(C標準)。
  • time / localtime_s / strftime : 日時取得・整形(C標準)。

これらの関数・型を組み合わせることで、Windows環境でのファイル操作・性能測定・エラー処理・時刻管理などを実現しています。

他のファイルシステムとの比較

NTFSを、Linuxのext4、FAT32と比較します。

項目 NTFS ext4 FAT32
最大ファイルサイズ 16EB 16TB 4GB
最大ボリュームサイズ 8PB 1EB 2TB
ジャーナリング あり($LogFile) あり(ext4ジャーナル) なし
アクセス制御 あり あり なし
暗号化機能 あり あり なし
ファイル圧縮 あり あり(ツール使用) なし

NTFSとext4は、ジャーナリングやアクセス制御、暗号化機能など現代OSに必要な機能を備えています。一方FAT32は最大ファイルサイズが4GBと小さく、ジャーナリングやアクセス制御がないため、OS用途には不適切で、SDカードやUSBメモリなど小容量ストレージに使われます。

NTFSはファイルのプロパティから圧縮設定が可能ですが、ext4は標準圧縮機能がなく、ツールや他のファイルシステムを使う必要があります。ext4の暗号化はfscryptでディレクトリ単位で行われます。

仮想環境におけるファイルシステム

Windows上でLinux環境を直接実行できるWSL2(Windows Subsystem for Linux)は、Windows環境にありながらLinuxのファイルシステム(ext4)を利用できます。
物理的には、この仮想環境のデータはWindowsのファイルシステム(NTFS)上に存在しますが、WSL2ではVHD(Virtual Hard Disk)やVHDX形式で仮想ディスクを作成し、その中身はext4でフォーマットされます。

CygwinなどのPOSIX互換環境では、WindowsのNTFSをLinuxのファイルシステムのように扱うため、LinuxのシステムコールをWindows APIに変換しています。Cygwinの中枢はcygwin1.dllというDLLで、これがPOSIX APIの機能を提供し、Linuxプログラムからのファイル操作やパーミッション機能などをリアルタイムでWindows APIに変換します。
詳細な実装については公式ドキュメントを参照してください。

Unix系OSのファイルシステムの実機性能を比較する場合、CygwinではなくWSL2やデュアルブート等で実機Linuxを用いるのが望ましいです。またWSL2内でも、/mnt/cなど実ドライブにアクセスするとNTFSを用いることになるため注意が必要です。

参考文献

  1. IBM.fsck コマンド.IBM https://www.ibm.com/docs/ja/aix/7.3.0?topic=f-fsck-command.2024-12-06更新.2025-07-13閲覧.
  2. Microsoft.Master File Table(Local File Systems). Microsoft Learn.https://learn.microsoft.com/en-us/windows/win32/fileio/master-file-table.2024-09-06更新.2025-07-13閲覧.
  3. Microsoft.File Caching.Microsoft Learn.https://learn.microsoft.com/en-us/windows/win32/fileio/file-caching.2024-01-08更新.2025-07-13閲覧.
  4. Microsoft.Access Control Lists.Microsoft Learn.https://learn.microsoft.com/en-us/windows/win32/secauthz/access-control-lists.2025-07-10更新.2025-07-13閲覧.
  5. Microsoft.NTFS Overview.Microsoft Learn.https://learn.microsoft.com/en-us/windows-server/storage/file-server/ntfs-overview.2025-06-19更新.2025-07-13閲覧.
  6. Microsoft.fileapi.h ヘッダー.Microsoft Learn.https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/.2025-04-12更新.2025-07-11閲覧.
  7. Incept Inc.VHD【Virtual Hard Disk】.vhdファイル.IT用語辞典 e-Words.https://e-words.jp/w/VHD.html
  8. Cygwin authors.Cygwin.Cygwin.https://www.cygwin.com/.2025-07-12閲覧.
  9. Cygwin authors.POSIX accounts, permission, and security Chapter 3. Using Cygwin.Cygwin User's Guide.https://cygwin.com/cygwin-ug-net/ntsec.html.2020-09-10更新.2025-07-12閲覧.
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?