【问题标题】:Physical disk size not correct (IoCtlDiskGetDriveGeometry)物理磁盘大小不正确 (IoCtlDiskGetDriveGeometry)
【发布时间】:2013-02-09 16:37:54
【问题描述】:

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

下面的代码报告

总磁盘空间: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);
}

【问题讨论】:

  • 我稍后会修改代码以提高可读性。如果您同意我的修改,您可以批准。
  • 您的帖子已修改。我不会更改您代码的任何逻辑,只是为问题发布格式。如果您不同意,您仍然可以回滚。这是为了让其他人更清楚地阅读您的代码。
  • @Ken Kin;同意后期修改。但是知道为什么尺寸报告错误吗?

标签: c# size disk ioctl deviceiocontrol


【解决方案1】:

在对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<T>(
            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 directive 使代码的本机调用更像是在 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这样的声明改成static readonly的值,当作运行时常量强>。像IOCTL_ 这样的一些常量的前缀被删除了,因为有类名来限定它们。这部分最大的亮点是类CubicAddress,它是新定义的类DiskGeometry 的基础。您可能想知道为什么甚至更多想知道。

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

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

  • 提供扇区地址
  • 描述几何形状

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

关于命名,SectorsPerTrack等术语的表示应采用复数形式,如Sectors。但是,由于CubicAddress 的双重用法,我宁愿使用单数形式。

最后,这是测试类

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);
    }
}

【讨论】:

  • 我会试一试,我会研究你的代码和解释。我现在没有太多时间,但肯定是明天。干得好!
  • 我等不及了:media type: RemovableMedia - maximum linear address: 16121855 - last cylinder number: 1003 - last head number: 137 - last sector number: 30 - cylinders: 1003 - tracks per cylinder: 255 - sectors per track: 63 - bytes per sector: 512 - bytes per cylinder: 8225280 - total disk space: 8254390272
  • @Robertico:我等不及了,言辞激烈。现在你得到了正确的结果。
  • 再次做得好,感谢您的解释。我会学习(还有你提到的部分)。解释使示例更有意义和易于理解。这是一个很好的例子,因为关于这个问题的 C# 信息很少。将您的答案标记为有用并接受。
【解决方案2】:

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

下面是一个在线双向转换脚本

根据您的帖子,物理上的最后一个扇区将在

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

大小是

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

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

既然你指定它应该是8,254,390,272,我根据那个大小计算最后一个物理扇区。

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

[柱面总数] * [每柱面的磁道(也是磁头)] * [每磁道的扇区数]

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

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

【讨论】:

  • 这是我的值:Cylinders: 1003 - Tracks per Cylinder: 255 - Sectors per Track: 63 - Bytes per Sector: 512 - Bytes per Cylinder: 8225280
  • 知道如何使用可用值正确计算大小。我被卡住了:-(
  • ;好的。我真的不知道如何解决这个问题。对不起,但我没有那么有经验。这对我来说是全新的。
  • 我理解你的评论,但我想以正确的方式去做,并尝试学习一些东西。所以我一直在寻找一个我可以学习的例子。放弃不是一种选择;-) 无论如何谢谢!
  • @Robertico:好吧,看看我能不能稍后再发布一个答案。
猜你喜欢
  • 2017-02-14
  • 2013-02-25
  • 1970-01-01
  • 2020-09-25
  • 2013-04-03
  • 1970-01-01
  • 2012-09-13
  • 2012-12-28
  • 2013-05-18
相关资源
最近更新 更多