[VC]ディスクキャッシュを確実にフラッシュ

かなりディープなネタかも・・・?

Windows Embedded系のソフト開発をしていて、
OSの領域はEWFで保護しつつも、
データ領域として別ボリュームを保護せずに使いたい・・・

これ自体は、OS構築時のボリュームの設定だけで出来る*

でも実際には、ディスクキャッシュの影響で、
データ領域に書き込んだ後にOS領域がEWFだからと電源断すると、
書き込んだデータが壊れるコトが多々・・・。

このディスクキャッシュを確実に書き込ませるってのが、
全く見えない部分だけあって、なかなか難しい。。。

まず1番手軽に出来そうなFlushFileBuffers関数。
少なくとも自分の環境では全然ダメ。

次の方法は、一旦ボリュームをロックして、
フラッシュさせてからアンロックするやり方。これ
これは上手く行ったり行かなかったり・・・
そもそも誰かがアクセスしてると出来ない。

更に次は、ファイルを書き込むときのCreateFile関数の第6引数に
FILE_FLAG_WRITE_THROUGHってフラグを立ててファイルを開く。
このフラグが立ってると、ディスクに対して
書き込みキャッシュをバイパスするように指示するみたい?
これはかなりの成功率。
データ領域のファイルを更新するときとかに、
CopyFile関数とか使わず自力でFILE_FLAG_WRITE_THROUGHを
立てたコピー処理を実装すれば確実にコピー出来た。

1度はコレで落ち着いたんだけど、更に消えちゃうパターンが発覚・・・
不定期に数バイトだけ書き込んて、その数秒後に電源断すると消えたorz

ただ、ある程度書き込んでから放っておいた後に電源断すると、
ちゃんと書き込まれていたから、キャッシュ的な物が働いてるっぽい。。。

調べてると、どうもSSDだとFILE_FLAG_WRITE_THROUGHを
サポートしてないっぽい・・・?(確証なし)

でもシャットダウンしたとか電源断する前に
確実に書き込まれるようになってるし、
何かしら方法があるだろう・・・と、更に調べる。

結局コレと言った有力な情報も見つけられず、
途方に暮れていると、ふとディスクに向けて直接ATAコマンドを
投げつけると言う超強引な方法を思い付いた!^^;

ATAコマンドを調べると、まさに求めていたコマンドが!
0xE7が「FLUSH CACHE」で、
ディスクキャッシュをフラッシュするみたい。
これを元に実装してみたー

/*
 *	ディスク内のキャッシュをフラッシュ
 * @param lpVolumeName
 *	フラッシュする論理ボリューム名
 */
void DiskFlushCache(LPCWSTR lpVolumeName)
{
	// 論理ボリュームを開く
	HANDLE hDevice = CreateFile(
		lpVolumeName, GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
	if (hDevice == INVALID_HANDLE_VALUE) return;

	VOLUME_DISK_EXTENTS diskExtents;
	DWORD c;
	// ディスク番号を取得
	BOOL bRet = DeviceIoControl(hDevice,
		IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
		nullptr, 0, &diskExtents,
		sizeof(VOLUME_DISK_EXTENTS), &c, nullptr);
	CloseHandle(hDevice);
	if (!bRet) return;

	WCHAR hysicalName[30];
	// 物理ドライブ名
	wsprintf(hysicalName, L"\\\\.\\PhysicalDrive%u",
		diskExtents.Extents[0].DiskNumber);

	// 物理ドライブを開く
	hDevice = CreateFile(
		hysicalName, GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
	if (hDevice == INVALID_HANDLE_VALUE) return;

	// ATAコマンドを作成
	ATA_PASS_THROUGH_EX ata;
	ZeroMemory(&ata, sizeof(ATA_PASS_THROUGH_EX));
	ata.Length = sizeof(ATA_PASS_THROUGH_EX);
	ata.TimeOutValue = 5;			// タイムアウト
	ata.CurrentTaskFile[6] = 0xE7;	// FLUSH CACHE
	
	BYTE buf[512];
	// ATAコマンドを送信
	DeviceIoControl(hDevice, IOCTL_ATA_PASS_THROUGH, &ata,
		sizeof(ATA_PASS_THROUGH_EX), buf, sizeof(buf), &c, nullptr);
	
	CloseHandle(hDevice);
}

フラッシュしたいボリューム名(”\\.\D:”とか)を指定するだけ。
あとは勝手にドライブの番号を取得して、物理ドライブを開いて、
ATAコマンドの0xE7を投げてくれる。

ただ全く見えない部分だけに、本当に書けてるのか微妙^^;
ATAコマンドを実際に投げる処理も殆ど情報がなくて・・・
少なくとも今回の環境だと、この処理で数バイト書いて
すぐ電源断しても100%データが残ってた!

かなり低レベルなやり方だし、これなら大丈夫じゃないかなぁ。
まぁ参考までに自己責任で・・・^^;

じゃ、ゲームして寝るー
バイニー☆

*・・・ただしHORM機能を利用する場合は、
実機で休止状態へ移行する前にデータ領域にしたいボリュームを
1度DeviceIoControl関数でアンマウントしてから
休止状態へ移行しないと上手くいかないみたい。(参考

〜追記〜
やっぱりロック・アンロックもやるべきかも。
数バイトの書き換えのみならATAコマンドを投げると書き込まれたけど、
大量のファイルを書き換えたあとにATAコマンドを投げてもダメだった・・・
全てのハンドルを確実に解放してロック・アンロックするといけた。
うむ・・・謎は深まるばかり・・・

test?

コメントを残す