6

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

下面的代码报告

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

2 回答 2

7

在做了一些研究之后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;
    }
}

首先,我使用usingalias 指令使代码的本机调用更像是在 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);
        }
    }
}

最初是 C/C++ 代码中的IoCtl.CTL_CODE宏,但 c# 没有宏,因此我将声明更改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转换是线性变换/组合,我只写了Transformfor LBAto CHS。的参数geometryTransform用于转换的几何图形,它是必需的,因为线性地址可以转换为具有不同几何图形的不同坐标

关于命名,表示like的词SectorsPerTrack应该是like的复数形式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);
    }
}
于 2013-02-25T18:15:07.800 回答
1

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

以下是在线双向转换脚本

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

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 旁边的柱面,并使用最大扇区chs(1002, 255, 63)作为逻辑最后一个扇区是安全的。

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

于 2013-02-24T13:27:25.313 回答