第一次使用USN日志时,必须使用FSCTL_ENUM_USN_DATA控制代码枚举卷的整个USN记录集。这通常是一个冗长的操作。
有没有一种方法可以在运行卷之前估计卷上的记录数,以便显示进度?
我猜整个卷的USN数据是从MFT生成的,每个文件(大约)有一条记录。因此,估计MFT中活动文件数量的一种方法可能会起作用。
发布于 2012-07-05 12:36:25
您可以使用FSCTL_GET_NTFS_VOLUME_DATA来获取MFT的字节长度。如果将其与选定的代表性卷上的记录数量进行比较,则可以估计单个MFT记录的平均长度,并使用该长度计算特定卷上的记录数量的估计值。
因为MFT包含(例如)每个文件的安全信息,所以卷的平均长度将因卷而异,所以我认为您只能获得数量级的精度,但在大多数情况下可能已经足够好了。
另一种方法是假设文件引用编号线性增加,这大致是正确的。您可以使用FSCTL_ENUM_USN_DATA来确定是否存在引用编号大于特定猜测的文件;您不需要超过128次猜测就可以确定实际的最大引用编号。这至少会给你一个在0到100之间的完成百分比,它不会完全一致,但进度条永远不会是一致的。:-)
附加:
仔细观察,在Windows7 x64上,由FSCTL_ENUM_USN_DATA (在第一个USN_RECORD结构之前返回的四字)返回的"next id“字段毕竟不是文件引用编号,而是文件记录段编号。因此,正如您所观察到的,最后返回的id号乘以BytesPerFileRecordSegment (1024)等于MftValidDataLength。
文件参考编号似乎由两部分组成。低六个字节包含文件记录段编号。每次请求返回的第一条记录总是有一个FRN,它的段号与输入到StartFileReferenceNumber的"next id“相同,当StartFileReferenceNumber为零时,第一次调用除外。上面的两个字节包含未指定的附加信息,它永远不会为零。
FSCTL_ENUM_USN_DATA似乎接受文件记录段编号(在这种情况下,最高的两个字节为零)或文件引用编号(在这种情况下,最高的两个字节为非零)。
奇怪的是,我找不到具有相同记录段编号的两个记录。这表明每个文件记录在MFT中至少使用1K,这似乎是不合理的。
无论如何,结果是将"next id“乘以BytesPerFileRecordSegment,再除以MftValidDataLength得到一个完成百分比,只要在返回无意义的结果时优雅地处理即可。
发布于 2019-09-01 12:00:59
事实上,NTFS_VOLUME_DATA_BUFFER / NTFS_EXTENDED_VOLUME_DATA结构的MftValidDataLength字段对FSCTL_ENUM_USN_DATA将要/将要返回的USN记录的数量设置了上限(即,假设在测量估计和枚举之间没有向日志中添加额外的记录……)
在下面的C#示例中,我将vd.MftValidDataLength值除以vd.BytesPerFileRecordSegment,确保通过在除法之前先添加来向上舍入。至于除数,我相信它的值在任何平台或系统上都是通用的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;很好,那么你能用这个值做什么呢?不幸的是,知道FSCTL_ENUM_USN_DATA将返回的USN记录数量的最大上限对为DeviceIoControl/FSCTL_ENUM_USN_DATA调用本身选择缓冲区大小没有帮助,因为每次迭代中返回的USN_RECORD结构的大小随报告的文件名的长度而变化。
因此,如果您碰巧为所有 of 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操作。
https://stackoverflow.com/questions/11336390
复制相似问题