If FILE_FLAG_WRITE_THROUGH is used but FILE_FLAG_NO_BUFFERING is not also specified, so that system caching is in effect, then the data is written to the system cache but is flushed to disk without delay.
てきとうな訳: FILE_FLAG_NO_BUFFERINGを指定せずにFILE_FLAG_WRITE_THROUGHを指定すると、システムキャッシュは有効となり、データはWindowsのシステムキャッシュに書き込まれますがディスクには遅延なくフラッシュされます。
とあるように、CreateFileの第6パラメータdwFlagsAndAttributesにFILE_FLAG_WRITE_THROUGHを指定します。
しかし大量のログを保存するようなケースで保存先がSSDだと、FILE_FLAG_WRITE_THROUGHの指定によりSSDの劣化が通常よりも早く進行することが予想されます。ということで保存先がSSDかどうかでこれらのフラグを付加するかどうかを決めるようにすればいい、ということになります。しかしドライブがSSDかどうかについては、(1)DeviceIOControl (en)でPropertyIdにStorageDeviceSeekPenaltyPropertyを指定したIOCTL_STORAGE_QUERY_PROPERTYを発行してDEVICE_SEEK_PENALTY_DESCRIPTOR構造体のIncursSeekPenaltyが0(False)になっている("no seek penalty")、(2)ATA8-ACSでドライブの回転数を取得してNominal Media Rotation Rate(0x01)になっている、のどちらかで調べることができる、ということまではわかったものの、さて実際のコード例はというとなかなか参考になるものがなく、手詰まりになっていました。
ところがその数日後、NyaRuRuさんがほぼそのまんまの
SSD なら動作を変えるアプリケーションを作る - NyaRuRuが地球にいたころ
という記事を書いているのを見つけました。これだけきちんとしたサンプルがあればDelphiに置き換えるのも簡単です。ということでパート1として(1)の"no seek penalty"を取得する方法を試してみます(以下NyaRuRuさんのコードそのままなので詳細な説明は省略します)。
まずはDeviceIOControlで使用する構造体、定数などを定義します。
{$IF RTLversion < 22.0}
const
FILE_READ_DATA = $0001;
{$EXTERNALSYM FILE_READ_DATA}
FILE_READ_ATTRIBUTES = $0080;
{$EXTERNALSYM FILE_READ_ATTRIBUTES}
FILE_DEVICE_MASS_STORAGE = $0000002d;
{$EXTERNALSYM FILE_DEVICE_MASS_STORAGE}
IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE;
{$EXTERNALSYM IOCTL_STORAGE_BASE}
FILE_ANY_ACCESS = 0;
{$EXTERNALSYM FILE_ANY_ACCESS}
METHOD_BUFFERED = 0;
{$EXTERNALSYM METHOD_BUFFERED}
IOCTL_STORAGE_QUERY_PROPERTY = (IOCTL_STORAGE_BASE shl 16) or
(FILE_ANY_ACCESS shl 14) or
($0500 shl 2) or
(METHOD_BUFFERED);
{$EXTERNALSYM IOCTL_STORAGE_QUERY_PROPERTY}
{$IFEND}
type
{ STORAGE_PROPERTY_ID }
_STORAGE_PROPERTY_ID = (
StorageDeviceProperty = 0,
StorageAdapterProperty = 1,
StorageDeviceIdProperty = 2,
StorageDeviceUniqueIdProperty = 3,
StorageDeviceWriteCacheProperty = 4,
StorageMiniportProperty = 5,
StorageAccessAlignmentProperty = 6,
StorageDeviceSeekPenaltyProperty = 7,
StorageDeviceTrimProperty = 8,
StorageDeviceWriteAggregationProperty = 9,
StorageDeviceDeviceTelemetryProperty = 10
);
{$EXTERNALSYM _STORAGE_PROPERTY_ID}
STORAGE_PROPERTY_ID = _STORAGE_PROPERTY_ID;
{$EXTERNALSYM STORAGE_PROPERTY_ID}
TStoragePropertyId = _STORAGE_PROPERTY_ID;
PStoragePropertyId = ^TStoragePropertyId;
{ STORAGE_QUERY_TYPE }
_STORAGE_QUERY_TYPE = (
PropertyStandardQuery = 0,
PropertyExistsQuery = 1,
PropertyMaskQuery = 2,
PropertyQueryMaxDefined = 3
);
{$EXTERNALSYM _STORAGE_QUERY_TYPE}
STORAGE_QUERY_TYPE = _STORAGE_QUERY_TYPE;
{$EXTERNALSYM STORAGE_QUERY_TYPE}
TStorageQueryType = _STORAGE_QUERY_TYPE;
PStorageQueryType = ^TStorageQueryType;
{ STORAGE_PROPERTY_QUERY }
_STORAGE_PROPERTY_QUERY = packed record
PropertyId: DWORD;
QueryType: DWORD;
AdditionalParameters: array[0..9] of Byte;
end;
{$EXTERNALSYM _STORAGE_PROPERTY_QUERY}
STORAGE_PROPERTY_QUERY = _STORAGE_PROPERTY_QUERY;
{$EXTERNALSYM STORAGE_PROPERTY_QUERY}
TStoragePropertyQuery = _STORAGE_PROPERTY_QUERY;
PStoragePropertyQuery = ^TStoragePropertyQuery;
{ DEVICE_SEEK_PENALTY_DESCRIPTOR }
_DEVICE_SEEK_PENALTY_DESCRIPTOR = packed record
Version: DWORD;
Size: DWORD;
IncursSeekPenalty: ByteBool;
Reserved: array[0..2] of Byte;
end;
{$EXTERNALSYM _DEVICE_SEEK_PENALTY_DESCRIPTOR}
DEVICE_SEEK_PENALTY_DESCRIPTOR = _DEVICE_SEEK_PENALTY_DESCRIPTOR;
{$EXTERNALSYM DEVICE_SEEK_PENALTY_DESCRIPTOR}
TDeviceSeekPenaltyDescriptor = _DEVICE_SEEK_PENALTY_DESCRIPTOR;
PDeviceSeekPenaltyDescriptor = ^TDeviceSeekPenaltyDescriptor;
FILE_READ_DATA、FILE_READ_ATTRIBUTES、FILE_DEVICE_MASS_STORAGE、FILE_ANY_ACCESS、METHOD_BUFFERED、IOCTL_STORAGE_QUERY_PROPERTYはDelphi XE以降では定義済ですので、Delphi 2009およびそれ以前のバージョン用として定義しています。これらを使用して物理ドライブ名"\\.\PhysicalDrive#"で指定したドライブが"no seek penalty"かどうかを調べる関数です。
function HasNoSeekPenalty(const PhysicalDrivePath: String): Boolean;
var
h :THandle;
StoragePropertyQuery: TStoragePropertyQuery;
DeviceSeekPenaltyDescriptor: TDeviceSeekPenaltyDescriptor;
RSize: DWORD;
begin
h := CreateFile(PChar(PhysicalDrivePath),FILE_READ_ATTRIBUTES,
FILE_SHARE_READ or FILE_SHARE_WRITE,nil,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if h = INVALID_HANDLE_VALUE then
begin
RaiseLastOSError;
end;
try
with StoragePropertyQuery do
begin
PropertyId := Ord(StorageDeviceSeekPenaltyProperty);
QueryType := Ord(PropertyStandardQuery);
end;
FillChar(DeviceSeekPenaltyDescriptor,SizeOf(DeviceSeekPenaltyDescriptor),0);
RSize := 0;
if DeviceIoControl(h,IOCTL_STORAGE_QUERY_PROPERTY,
@StoragePropertyQuery,SizeOf(StoragePropertyQuery),
@DeviceSeekPenaltyDescriptor,SizeOf(DeviceSeekPenaltyDescriptor),
RSize,nil) = False then
begin
RaiseLastOSError;
end;
Result := not DeviceSeekPenaltyDescriptor.IncursSeekPenalty;
finally
CloseHandle(h);
end;
end;
Trueが返ってくればそのドライブはseek penaltyがない、つまりSSDと判断できる、ということになります。ただしこの方法はWindows 7およびそれ以降でしか使用できません(そのかわり管理者権限は不要です)。次にファイル名/パス名から物理ドライブ番号("\\.\PhysicalDrive#"の#)を取得する方法です。まず構造体、定数などの定義です。
type
{ DISK_EXTENT }
_DISK_EXTENT = packed record
DiskNumber: DWORD;
StartingOffset: LARGE_INTEGER;
ExtentLength: LARGE_INTEGER;
Reserved: array [0..3] of Byte;
end;
{$EXTERNALSYM _DISK_EXTENT}
DISK_EXTENT = _DISK_EXTENT;
{$EXTERNALSYM DISK_EXTENT}
TDiskExtent = _DISK_EXTENT;
PDiskExtent = ^TDiskExtent;
{ VOLUME_DISK_EXTENTS }
_VOLUME_DISK_EXTENTS = packed record
NumberOfDiskExtents: DWORD;
Reserved: array [0..3] of Byte;
Extents: array [0..0] of DISK_EXTENT;
end;
{$EXTERNALSYM _VOLUME_DISK_EXTENTS}
VOLUME_DISK_EXTENTS = _VOLUME_DISK_EXTENTS;
{$EXTERNALSYM VOLUME_DISK_EXTENTS}
TVolumeDiskExtents = VOLUME_DISK_EXTENTS;
PVolumeDiskExtents = ^TVolumeDiskExtents;
{$IF RTLVersion < 22.0}
const
IOCTL_VOLUME_BASE = $00000056;
{$EXTERNALSYM IOCTL_VOLUME_BASE}
IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = (IOCTL_VOLUME_BASE shl 16) or
(FILE_ANY_ACCESS shl 14) or
(0 shl 2) or
(METHOD_BUFFERED);
{$EXTERNALSYM IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS}
{$IFEND}
さきほどと同様にIOCTL_VOLUME_GET_VOLUME_DISK_EXTENTSはDelphi XE以降で定義済ですのでDelphi 2009およびそれ以前のバージョンでのみ定義が必要です。指定したファイル名/パス名の存在する物理ドライブ番号を動的配列に格納する関数です。
uses
Types;
procedure PathnameToPhysicalDriveNumber(const Path: String; var PhysicalDrives: TIntegerDynArray);
var
h: THandle;
I: Integer;
MountPoint: String;
VolumeName: String;
Size: DWORD;
RSize: DWORD;
P: PVolumeDiskExtents;
begin
SetLength(PhysicalDrives,0);
{ Pathname to mount point }
Size := GetFullPathName(PChar(Path),0,nil,nil);
SetLength(MountPoint,Size);
if GetVolumePathName(PChar(Path),PChar(MountPoint),Size) = False then
begin
RaiseLastOSError;
end;
SetLength(MountPoint,StrLen(PChar(MountPoint)));
{ Mount point to logical volume name }
Size := 50; // Recomended size from http://msdn.microsoft.com/en-us/library/windows/desktop/aa364994.aspx
SetLength(VolumeName,Size);
if GetVolumeNameForVolumeMountPoint(PChar(MountPoint),PChar(VolumeName),Size) = False then
begin
RaiseLastOSError;
end;
SetLength(VolumeName,StrLen(PChar(VolumeName)));
VolumeName := ExcludeTrailingPathDelimiter(VolumeName);
{ Open volume }
h := CreateFile(PChar(VolumeName),FILE_READ_ATTRIBUTES,
FILE_SHARE_READ or FILE_SHARE_WRITE,nil,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if h = INVALID_HANDLE_VALUE then
begin
RaiseLastOSError;
end;
try
Size := SizeOf(TVolumeDiskExtents);
P := AllocMem(Size);
try
FillChar(P^,Size,0);
RSize := 0;
if DeviceIoControl(h,IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
nil,0,
P,Size,
RSize,nil) = False then
begin
if GetLastError <> ERROR_MORE_DATA then
begin
RaiseLastOSError;
end;
Size := SizeOf(TVolumeDiskExtents) +
SizeOf(DISK_EXTENT) * (P^.NumberOfDiskExtents - 1);
ReallocMem(P,Size);
FillChar(P^,Size,0);
if DeviceIoControl(h,IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
nil,0,
P,Size,
RSize,nil) = False then
begin
RaiseLastOSError;
end;
end;
SetLength(PhysicalDrives,P^.NumberOfDiskExtents);
for I := 0 to P^.NumberOfDiskExtents - 1 do
begin
PhysicalDrives[I] := P^.Extents[I].DiskNumber;
end;
finally
FreeMem(P);
end;
finally
CloseHandle(h);
end;
end;
これで第2パラメータPhysicalDrivesに物理ドライブ番号が格納されます。これらの関数を組み合わせて指定したファイル/パスがSSD上に書き込まれるかどうかを調べてみます。
var
Index: Integer;
Filename: String;
PhysicalDrives: TIntegerDynArray;
PhysicalDrivePath: String;
IsSSD: Boolean;
begin
Filename := "C:\"; // 例: "C:\"を調べます
SetLength(PhysicalDrives,0);
PathnameToPhysicalDriveNumber(Filename,PhysicalDrives);
try
IsSSD := False;
for Index := Low(PhysicalDrives) to High(PhysicalDrives) do
begin
PhysicalDrivePath := Format('\\.\PhysicalDrive%d',[PhysicalDrives[Index]]);
try
IsSSD := IsSSD or HasNoSeekPenalty(PhysicalDrivePath);
except
{ Ignore }
end;
if IsSSD = True then
begin
Break;
end;
end;
if IsSSD = True then
begin
MessageDlg(Format('ファイル ''%s'' はSSDに書き込まれます。',[Filename]),
mtInformation,[mbOk],0);
end
else
begin
MessageDlg(Format('ファイル ''%s'' はSSDには書き込まれません。',[Filename]),
mtInformation,[mbOk],0);
end;
finally
SetLength(PhysicalDrives,0);
end;
end;
ATA8-ACSでドライブの回転数を取得する方法については次のアーティクルで。元ねたはもちろんNyaRuRuさんのSSD なら動作を変えるアプリケーションを作る - NyaRuRuが地球にいたころ。すばらしいサンプルを書いていただいたNyaRuRuさんに深く感謝いたします。
2012/10/05追記: CreateFileのフラグについて当初FILE_FLAG_NO_BUFFERINGとFILE_FLAG_WRITE_THROUGHの両方を指定するという記述がありましたが、FILE_FLAG_NO_BUFFERINGを指定したファイルへの書き込みにはFile Bufferingにあるように制限があり、通常の使用には向かないと考えられるため、この点を削除しました。
0 件のコメント:
コメントを投稿