物理磁盘大小不正确(IoCtlDiskGetDriveGeometry)

我使用下面的代码来获取物理磁盘大小 ,但返回的大小不正确。 我用其他工具检查了尺寸。

以下代码报告

总磁盘空间: 8.249.955.840字节

它应该是

总磁盘空间: 8.254.390.272字节

如何检索实际/正确的物理磁盘大小? 在USB驱动器和普通硬盘上测试。 代码很长,这里将它分开来显示。

结构:

[StructLayout(LayoutKind.Sequential)] internal struct DiskGeometry { public long Cylinders; public int MediaType; public int TracksPerCylinder; public int SectorsPerTrack; public int BytesPerSector; } 

原生方法:

 internal static class NativeMethods { [DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern SafeFileHandle CreateFile( string fileName, uint fileAccess, uint fileShare, IntPtr securityAttributes, uint creationDisposition, uint flags, IntPtr template ); [DllImport("Kernel32.dll", SetLastError=false, CharSet=CharSet.Auto)] public static extern int DeviceIoControl( SafeFileHandle device, uint controlCode, IntPtr inBuffer, uint inBufferSize, IntPtr outBuffer, uint outBufferSize, ref uint bytesReturned, IntPtr overlapped ); internal const uint FileAccessGenericRead=0x80000000; internal const uint FileShareWrite=0x2; internal const uint FileShareRead=0x1; internal const uint CreationDispositionOpenExisting=0x3; internal const uint IoCtlDiskGetDriveGeometry=0x70000; } 

主要条目:

 internal const uint IoCtlDiskGetDriveGeometry=0x70000; public static void Main() { SafeFileHandle diskHandle= NativeMethods.CreateFile( @"\\.\PhysicalDrive0", NativeMethods.FileAccessGenericRead, NativeMethods.FileShareWrite|NativeMethods.FileShareRead, IntPtr.Zero, NativeMethods.CreationDispositionOpenExisting, 0, IntPtr.Zero ); if(diskHandle.IsInvalid) { Console.WriteLine("CreateFile failed with error: {0}", Marshal.GetLastWin32Error()); return; } int geometrySize=Marshal.SizeOf(typeof(DiskGeometry)); Console.WriteLine("geometry size = {0}", geometrySize); IntPtr geometryBlob=Marshal.AllocHGlobal(geometrySize); uint numBytesRead=0; if( 0==NativeMethods.DeviceIoControl( diskHandle, NativeMethods.IoCtlDiskGetDriveGeometry, IntPtr.Zero, 0, geometryBlob, (uint)geometrySize, ref numBytesRead, IntPtr.Zero ) ) { Console.WriteLine( "DeviceIoControl failed with error: {0}", Marshal.GetLastWin32Error() ); return; } Console.WriteLine("Bytes read = {0}", numBytesRead); DiskGeometry geometry=(DiskGeometry)Marshal.PtrToStructure(geometryBlob, typeof(DiskGeometry)); Marshal.FreeHGlobal(geometryBlob); long bytesPerCylinder=(long)geometry.TracksPerCylinder*(long)geometry.SectorsPerTrack*(long)geometry.BytesPerSector; long totalSize=geometry.Cylinders*bytesPerCylinder; Console.WriteLine("Media Type: {0}", geometry.MediaType); Console.WriteLine("Cylinders: {0}", geometry.Cylinders); Console.WriteLine("Tracks per Cylinder: {0}", geometry.TracksPerCylinder); Console.WriteLine("Sectors per Track: {0}", geometry.SectorsPerTrack); Console.WriteLine("Bytes per Sector: {0}", geometry.BytesPerSector); Console.WriteLine("Bytes per Cylinder: {0}", bytesPerCylinder); Console.WriteLine("Total disk space: {0}", totalSize); } 

在对DeviceIocontrol进行了一些研究之后,我花了大部分时间进行设计。 在这里,我将代码分为两部分,为了清晰起见,用命名空间和部分类分隔,你可以合并它们,但不能单独使用它们

 namespace DiskManagement { using Microsoft.Win32.SafeHandles; using LPSECURITY_ATTRIBUTES=IntPtr; using LPOVERLAPPED=IntPtr; using LPVOID=IntPtr; using HANDLE=IntPtr; using LARGE_INTEGER=Int64; using DWORD=UInt32; using LPCTSTR=String; public static partial class IoCtl /* methods */ { [DllImport("kernel32.dll", SetLastError=true)] static extern SafeFileHandle CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ); [DllImport("kernel32.dll", SetLastError=true)] static extern DWORD DeviceIoControl( SafeFileHandle hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, int nOutBufferSize, ref DWORD lpBytesReturned, LPOVERLAPPED lpOverlapped ); static DWORD CTL_CODE(DWORD DeviceType, DWORD Function, DWORD Method, DWORD Access) { return (((DeviceType)<<16)|((Access)<<14)|((Function)<<2)|(Method)); } public static void Execute( ref T x, DWORD dwIoControlCode, LPCTSTR lpFileName, DWORD dwDesiredAccess=GENERIC_READ, DWORD dwShareMode=FILE_SHARE_WRITE|FILE_SHARE_READ, LPSECURITY_ATTRIBUTES lpSecurityAttributes=default(LPSECURITY_ATTRIBUTES), DWORD dwCreationDisposition=OPEN_EXISTING, DWORD dwFlagsAndAttributes=0, HANDLE hTemplateFile=default(IntPtr) ) { using( var hDevice= CreateFile( lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile ) ) { if(null==hDevice||hDevice.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error()); var nOutBufferSize=Marshal.SizeOf(typeof(T)); var lpOutBuffer=Marshal.AllocHGlobal(nOutBufferSize); var lpBytesReturned=default(DWORD); var NULL=IntPtr.Zero; var result= DeviceIoControl( hDevice, dwIoControlCode, NULL, 0, lpOutBuffer, nOutBufferSize, ref lpBytesReturned, NULL ); if(0==result) throw new Win32Exception(Marshal.GetLastWin32Error()); x=(T)Marshal.PtrToStructure(lpOutBuffer, typeof(T)); Marshal.FreeHGlobal(lpOutBuffer); } } } public enum MEDIA_TYPE: int { Unknown=0, F5_1Pt2_512=1, F3_1Pt44_512=2, F3_2Pt88_512=3, F3_20Pt8_512=4, F3_720_512=5, F5_360_512=6, F5_320_512=7, F5_320_1024=8, F5_180_512=9, F5_160_512=10, RemovableMedia=11, FixedMedia=12, F3_120M_512=13, F3_640_512=14, F5_640_512=15, F5_720_512=16, F3_1Pt2_512=17, F3_1Pt23_1024=18, F5_1Pt23_1024=19, F3_128Mb_512=20, F3_230Mb_512=21, F8_256_128=22, F3_200Mb_512=23, F3_240M_512=24, F3_32M_512=25 } partial class DiskGeometry /* structures */ { [StructLayout(LayoutKind.Sequential)] struct DISK_GEOMETRY { internal LARGE_INTEGER Cylinders; internal MEDIA_TYPE MediaType; internal DWORD TracksPerCylinder; internal DWORD SectorsPerTrack; internal DWORD BytesPerSector; } [StructLayout(LayoutKind.Sequential)] struct DISK_GEOMETRY_EX { internal DISK_GEOMETRY Geometry; internal LARGE_INTEGER DiskSize; [MarshalAs(UnmanagedType.ByValArray, SizeConst=1)] internal byte[] Data; } } partial class DiskGeometry /* properties and fields */ { public MEDIA_TYPE MediaType { get { return m_Geometry.MediaType; } } public String MediaTypeName { get { return Enum.GetName(typeof(MEDIA_TYPE), this.MediaType); } } public override long Cylinder { get { return m_Geometry.Cylinders; } } public override uint Head { get { return m_Geometry.TracksPerCylinder; } } public override uint Sector { get { return m_Geometry.SectorsPerTrack; } } public DWORD BytesPerSector { get { return m_Geometry.BytesPerSector; } } public long DiskSize { get { return m_DiskSize; } } public long MaximumLinearAddress { get { return m_MaximumLinearAddress; } } public CubicAddress MaximumCubicAddress { get { return m_MaximumCubicAddress; } } public DWORD BytesPerCylinder { get { return m_BytesPerCylinder; } } CubicAddress m_MaximumCubicAddress; long m_MaximumLinearAddress; DWORD m_BytesPerCylinder; LARGE_INTEGER m_DiskSize; DISK_GEOMETRY m_Geometry; } } 

首先,我使用using alias指令来进行代码的本机调用,就像在C / C ++中一样。 第一部分的重点是IoCtl.Execute方法。 它是一种通用方法,类型是根据传递的第一个参数 。 它隐藏了使用P/Invoke方法编组结构和指针的复杂性。 第二个参数是所需的控制代码,它将传递给DeviceIoControl 。 从第三个参数到最后一个参数与CreateFile完全相同,并且都具有默认值,它们是可选的

以下是代码的下一部分,可能还有更多内容需要注意。

 namespace DiskManagement { using Microsoft.Win32.SafeHandles; using LPSECURITY_ATTRIBUTES=IntPtr; using LPOVERLAPPED=IntPtr; using LPVOID=IntPtr; using HANDLE=IntPtr; using LARGE_INTEGER=Int64; using DWORD=UInt32; using LPCTSTR=String; partial class IoCtl /* constants */ { public const DWORD DISK_BASE=0x00000007, METHOD_BUFFERED=0, FILE_ANY_ACCESS=0; public const DWORD GENERIC_READ=0x80000000, FILE_SHARE_WRITE=0x2, FILE_SHARE_READ=0x1, OPEN_EXISTING=0x3; public static readonly DWORD DISK_GET_DRIVE_GEOMETRY_EX= IoCtl.CTL_CODE(DISK_BASE, 0x0028, METHOD_BUFFERED, FILE_ANY_ACCESS); public static readonly DWORD DISK_GET_DRIVE_GEOMETRY= IoCtl.CTL_CODE(DISK_BASE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS); } public partial class CubicAddress { public static CubicAddress Transform(long linearAddress, CubicAddress geometry) { var cubicAddress=new CubicAddress(); var sectorsPerCylinder=geometry.Sector*geometry.Head; long remainder; cubicAddress.Cylinder=Math.DivRem(linearAddress, sectorsPerCylinder, out remainder); cubicAddress.Head=(uint)Math.DivRem(remainder, geometry.Sector, out remainder); cubicAddress.Sector=1+(uint)remainder; return cubicAddress; } public virtual long Cylinder { get; set; } public virtual uint Head { get; set; } public virtual uint Sector { get; set; } } public partial class DiskGeometry: CubicAddress { internal static void ThrowIfDiskSizeOutOfIntegrity(long remainder) { if(0!=remainder) { var message="DiskSize is not an integral multiple of a sector size"; throw new ArithmeticException(message); } } public static DiskGeometry FromDevice(String deviceName) { return new DiskGeometry(deviceName); } DiskGeometry(String deviceName) { var x=new DISK_GEOMETRY_EX(); IoCtl.Execute(ref x, IoCtl.DISK_GET_DRIVE_GEOMETRY_EX, deviceName); m_DiskSize=x.DiskSize; m_Geometry=x.Geometry; long remainder; m_MaximumLinearAddress=Math.DivRem(DiskSize, BytesPerSector, out remainder)-1; ThrowIfDiskSizeOutOfIntegrity(remainder); m_BytesPerCylinder=BytesPerSector*Sector*Head; m_MaximumCubicAddress=DiskGeometry.Transform(m_MaximumLinearAddress, this); } } } 

IoCtl.CTL_CODE最初是C / C ++代码中的宏,但是c#没有宏,因此我将DISK_GET_DRIVE_GEOMETRY_EX之类的声明DISK_GET_DRIVE_GEOMETRY_EXstatic readonly值,将其视为运行时常量 。 删除了一些常量(如IOCTL_前缀,因为有类名来限定它们。 这部分的最大特点是类CubicAddress ,它是新定义的类DiskGeometry 。 你可能想知道为什么甚至更多的想知道。

事实上,类CubicAddress是一个简单的类用于存储物理磁盘的CHS address ,并提供一种从LBA格式转换地址的方法,我将其命名为Transform 。 虽然我从来没有听说有人将CHS命名为立方体,但我认为像几何/体积这样的术语在数学和环绕物理磁盘中具有相同的用法。

CHS可能是(x ,y, z)(R, G, B)或您可以以立方方式对其进行建模的任何其他事物。 它们可能有一个寻址坐标 ,也可能用于描述几何,如矢量 。 因此,类CubicAddress有两个用法:

  • 提供一个部门的地址
  • 描述几何

CHS / LBA转换是线性转换/组合,我只写了LBA TransformCHSTransform的参数geometry是为Transform引用的几何,它是必需的,因为线性地址可以转换为具有不同几何的不同坐标

关于命名,表示像SectorsPerTrack这样的SectorsPerTrack应该是像Sectors这样的复数forms。 但是,由于CubicAddress的双重用法,我宁愿使用单数forms。

最后,这是测试类

 public partial class TestClass { public static void TestMethod() { var diskGeometry=DiskGeometry.FromDevice(@"\\.\PhysicalDrive3"); var cubicAddress=diskGeometry.MaximumCubicAddress; Console.WriteLine(" media type: {0}", diskGeometry.MediaTypeName); Console.WriteLine(); Console.WriteLine("maximum linear address: {0}", diskGeometry.MaximumLinearAddress); Console.WriteLine(" last cylinder number: {0}", cubicAddress.Cylinder); Console.WriteLine(" last head number: {0}", cubicAddress.Head); Console.WriteLine(" last sector number: {0}", cubicAddress.Sector); Console.WriteLine(); Console.WriteLine(" cylinders: {0}", diskGeometry.Cylinder); Console.WriteLine(" tracks per cylinder: {0}", diskGeometry.Head); Console.WriteLine(" sectors per track: {0}", diskGeometry.Sector); Console.WriteLine(); Console.WriteLine(" bytes per sector: {0}", diskGeometry.BytesPerSector); Console.WriteLine(" bytes per cylinder: {0}", diskGeometry.BytesPerCylinder); Console.WriteLine(" total disk space: {0}", diskGeometry.DiskSize); } } 

您的代码以错误的方式计算它 。 关于物理到逻辑扇区数量计算的描述,请看维基百科上的artical

  • 逻辑块寻址

以下是在线双向转换脚本

  • CHS / LBA转换

根据你的post,实际的最后一个部门将是

chs(1003,137,30)=((1003 * 255)+ 137)* 63 + 30 – 1 = lba(16121855)

而且大小会是

总扇区= 1 + 16121855 = 16121856个扇区

16121856 *每扇区512字节= 8254390272字节

由于您指定它应该是 8,254,390,272 ,我根据该大小计算最后一个物理扇区。

255 * 63仅用于对齐 ,它被称为柱面边界 。 通常,物理最后一个扇区不是在边界处结束,但由于不访问不存在扇区的原因,它应该大于

[总气缸] * [每个气缸的气道(也是气缸头)] * [每个气缸的扇区]

例如,如果您的物理最后一个扇区是上面的计算值,那么只需忽略1002旁边的柱面 ,并使用扇区max to chs(1002, 255, 63)因为您的逻辑最后一个扇区是安全的。

要获取物理磁盘大小,可以使用控制代码IOCTL_DISK_GET_DRIVE_GEOMETRY_EX调用DeviceIoControl 。 这是MSDN上的参考

  • IOCTL_DISK_GET_DRIVE_GEOMETRY_EX控制代码(Windows)