Оцените количество записей USN на томе NTFS.
Когда журнал USN используется впервые, весь набор записей USN тома должен быть перечислен с использованием управляющего кода FSCTL_ENUM_USN_DATA. Обычно это длительная операция.
Есть ли способ оценить количество записей на томе до его запуска, чтобы можно было отображать прогресс?
Я предполагаю, что данные USN для всего тома генерируются из MFT, с одной записью на файл (приблизительно). Так что, возможно, сработает способ оценки количества активных файлов в MFT.
1 ответ
Вы можете использовать FSCTL_GET_NTFS_VOLUME_DATA, чтобы получить длину в байтах MFT. Если вы сравните это с количеством записей в выборке репрезентативных томов, вы можете оценить среднюю длину одной записи MFT и использовать ее для расчета оценки количества записей в конкретном томе.
Поскольку MFT содержит (например) информацию о безопасности для каждого файла, средняя длина будет значительно варьироваться от объема к объему, поэтому я думаю, что вы получите точность только по порядку, но в большинстве случаев она может быть достаточно хорошей,
Другой подход заключается в предположении, что ссылочные номера файлов увеличиваются линейно, что примерно соответствует действительности. Вы можете использовать FSCTL_ENUM_USN_DATA, чтобы узнать, есть ли какие-либо файлы со ссылочным номером выше определенного предположения или нет; вам нужно не более 128 предположений, чтобы определить фактический максимальный ссылочный номер. Это, по крайней мере, даст вам полный процент между 0 и 100 в любой заданной точке, он не будет полностью равномерным, но тогда индикаторы прогресса никогда не будут.:-)
Дополнительно:
Более подробно, в Windows 7 x64 поле "следующий идентификатор", возвращаемое FSCTL_ENUM_USN_DATA (четырехзначное слово, возвращаемое до первой структуры USN_RECORD), в конце концов, является не ссылочным номером файла, а номером сегмента записи файла. Итак, как вы заметили, последний возвращенный номер идентификатора, умноженный на BytesPerFileRecordSegment (1024), равен MftValidDataLength.
Ссылочные номера файлов, как представляется, состоят из двух частей. Младшие шесть байтов содержат номер сегмента записи файла. Первая запись, возвращаемая из каждого запроса, всегда имеет FRN, номер сегмента которого совпадает со "следующим идентификатором", введенным в StartFileReferenceNumber, за исключением первого вызова, когда StartFileReferenceNumber равен нулю. Верхние два байта содержат неопределенную дополнительную информацию, которая никогда не равна нулю.
Кажется, что FSCTL_ENUM_USN_DATA принимает либо номер сегмента записи файла (в этом случае верхние два байта равны нулю), либо номер ссылки на файл (в этом случае верхние два байта отличны от нуля).
Одна странность в том, что я не могу найти две записи с одинаковым номером сегмента записи. Это говорит о том, что каждая запись файла использует не менее 1 КБ в MFT, что не представляется разумным.
В любом случае, результат заключается в том, что, вероятно, разумно умножить "следующий идентификатор" на BytesPerFileRecordSegment и разделить его на MftValidDataLength, чтобы получить процент выполнения, при условии, что вы корректно справляетесь, если это возвращает бессмысленный результат.
Фактически MftValidDataLength
поле NTFS_VOLUME_DATA_BUFFER
/ NTFS_EXTENDED_VOLUME_DATA
структура (и) устанавливает верхний предел количества записей USN, которые будут / будут возвращеныFSCTL_ENUM_USN_DATA
(то есть при условии, что дополнительные записи не добавляются в журнал между моментом измерения оценки и перечислением...)
В приведенном ниже примере C# я делюvd.MftValidDataLength
значение по vd.BytesPerFileRecordSegment
, обязательно округлите, сначала добавивdividend - 1
перед делением. Что касается делителя, я считаю, что его значение здесь всегда универсально.1,024
на любой платформе или системе, если вы предпочитаете жестко ее кодировать.
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct NTFS_EXTENDED_VOLUME_DATA
{
public VOLUME_ID /**/ VolumeSerialNumber;
public long /**/ NumberSectors;
public long /**/ TotalClusters;
public long /**/ FreeClusters;
public long /**/ TotalReserved;
public uint /**/ BytesPerSector;
public uint /**/ BytesPerCluster;
public int /**/ BytesPerFileRecordSegment; // <--
public uint /**/ ClustersPerFileRecordSegment;
public long /**/ MftValidDataLength; // <--
public long /**/ MftStartLcn;
public long /**/ Mft2StartLcn;
public long /**/ MftZoneStart;
public long /**/ MftZoneEnd;
public uint /**/ ByteCount;
public ushort /**/ MajorVersion;
public ushort /**/ MinorVersion;
public uint /**/ BytesPerPhysicalSector;
public ushort /**/ LfsMajorVersion;
public ushort /**/ LfsMinorVersion;
public uint /**/ MaxDeviceTrimExtentCount;
public uint /**/ MaxDeviceTrimByteCount;
public uint /**/ MaxVolumeTrimExtentCount;
public uint /**/ MaxVolumeTrimByteCount;
};
Типовые константы, сокращенные для наглядности:
public enum FSCTL : uint
{
// etc... etc...
FILESYSTEM_GET_STATISTICS /**/ = (9 << 16) | 0x0060,
GET_NTFS_VOLUME_DATA /**/ = (9 << 16) | 0x0064, // <--
GET_NTFS_FILE_RECORD /**/ = (9 << 16) | 0x0068,
GET_VOLUME_BITMAP /**/ = (9 << 16) | 0x006f,
GET_RETRIEVAL_POINTERS /**/ = (9 << 16) | 0x0073,
// etc... etc...
ENUM_USN_DATA /**/ = (9 << 16) | 0x00b3,
READ_USN_JOURNAL /**/ = (9 << 16) | 0x00bb,
// etc... etc...
CREATE_USN_JOURNAL /**/ = (9 << 16) | 0x00e7,
// etc... etc...
};
Далее следует псевдокод, поскольку у каждого есть свои любимые способы выполнения P/Invoke...
// etc..
if (!GetDeviceIoControl(h_vol, FSCTL.GET_NTFS_VOLUME_DATA, out NTFS_EXTENDED_VOLUME_DATA vd))
throw new Win32Exception(Marshal.GetLastWin32Error());
var c_mft_estimate = (vd.MftValidDataLength + (vd.BytesPerFileRecordSegment - 1))
/ vd.BytesPerFileRecordSegment;
Отлично, что вы можете сделать с этим значением? К сожалению, зная об этом максимальном ограничении количества записей USN,FSCTL_ENUM_USN_DATA
вернется не помогает с выбором размера буфера для DeviceIoControl/FSCTL_ENUM_USN_DATA
называют себя, поскольку USN_RECORD
структуры, возвращаемые в каждой итерации, различаются по размеру в зависимости от длины сообщаемых имён файлов.
Таким образом, хотя это правда, что, если вам случится, чтобы обеспечить буфер достаточно большой для всех изUSN_RECORD
структуры, то DeviceIoControl
действительно послушно предоставит вам их все за один вызов (таким образом, избегая усложнения цикла итеративного вызова, который значительно упрощает код), небольшой расчет выше не дает никакой принципиальной оценки этого размера буфера, если только вы не готовы согласиться на то, чтобы использовать его для грубой переоценки.
То, что значение является полезным для, скорее, для предварительного выделения собственных фиксированного размера, структуры данных, которые вы обязательно нужно, до проведенияFSCTL_ENUM_USN_DATA
операция перечисления. Итак, если у вас есть собственный тип значения, который вы создадите для каждой записи USN (фиктивная структура, например...)
[StructLayout(LayoutKind.Sequential)]
public struct MFT_IX_REC
{
public ushort seq;
public ushort parent_ix_hi;
public uint parent_ix;
};
Затем, используя оценку сверху, вы можете предварительно выделить массив из них перед DeviceIoControl
и никогда не нужно беспокоиться об изменении размера во время итерации.
var med = new MFT_ENUM_DATA { ... };
// ...
var rg_mftix = new MFT_IX_REC[c_mft_estimate];
// ... ready to go, without having to check whether the array needs resizing within the loop
for (int i=0; DeviceIoControl(h_vol, FSCTL.ENUM_USN_DATA, in med, out USN_RECORD usn, ...); i++)
{
// etc..
rg_mftix[i].parent_ix = (uint)usn.ParentId;
// etc..
}
Это устранение динамического изменения размера массива, которое обычно требуется, когда вы заранее не знаете количество записей, является нетривиальным преимуществом в производительности, поскольку позволяет избежать дорогостоящих больших размеров. memcpy
операции, необходимые для копирования существующих данных из старого массива в новый, более крупный, каждый раз при изменении размера.