1

我正在使用 C# 直接从磁盘读取并调用 kernel32 ReadFile 方法。我注意到,对于较大的读取(当前仅读取单个块),缓冲区大小超出范围。

有谁知道这里读取缓冲区的最大大小?

如果是这样,当我有多余的内存读入时限制缓冲区大小的目的是什么?我了解缓冲和保持小内存占用的概念,但为什么要强制我们使用小尺寸?也许只是旧 Win32 API 的产物?

编辑:收到的错误Marshal.GetLastWin32Error()是“值不在预期范围内”。

我收到此错误之前的上限是 8192 字节(8KB - 因此我很困惑)。

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace DiskRead
{
    class Program
    {
        public const uint GenericRead = 0x80000000;
        public const uint FileShareRead = 1;
        public const uint FileShareWrite = 2;
        public const uint OpenExisting = 3;

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess,
          uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
          uint dwFlagsAndAttributes, IntPtr hTemplateFile);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer,
           uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);

        static void Main(string[] args)
        {
            string path = @"\\.\PhysicalDrive0";

            IntPtr ptr = CreateFile(path, GenericRead, FileShareRead | FileShareWrite, IntPtr.Zero, OpenExisting, 0, IntPtr.Zero);

            SafeFileHandle handleValue = new SafeFileHandle(ptr, true);
            FileStream fileStream = new FileStream(handleValue, FileAccess.Read);

            const uint numberOfBytesToRead = 8193;

            uint bytesRead;
            byte[] buffer = new byte[numberOfBytesToRead];


            if (!ReadFile(handleValue.DangerousGetHandle(), buffer, numberOfBytesToRead, out bytesRead, IntPtr.Zero))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

        }
    }
}

提前致谢。

4

4 回答 4

1

我不确定这是否对您有帮助,但它可能会帮助这篇文章的其他读者:

  1. 当涉及到 ReadFile() 时,它的行为和成功很大程度上取决于如何使用 CreateFile() 打开文件。只是为了给您指明正确方向的观点,请考虑一下;当您调用 CreateFile() 来读取缓冲区时,您通常不会想要 FILE_FLAG_NO_BUFFERING 除非您准备好处理数据对齐并绕开一些特定于硬件的信息,尽管它可能只是更容易不。

  2. 您的代码似乎没有这样做,但它互操作的,因此在某处可能会出现乱码,并且 .NET 因骚扰(?)内存块而臭名昭著,因此您可能想尝试通过互操作分配内存保证内存块不会移动到任何地方。另外,我会尽量减少互操作,在最需要的时候似乎有点不可靠。相反,您可能会考虑编写一个更好的 DLL使用您的代码并进行互操作。我在很多情况下都这样做了,它工作得很好,并且增加了不必编写相同代码的好处,只需在尽可能多的程序中重用它就可以了...至于您的问题的根本原因,我自己无法复制它,我尝试过的最大文件约为 800MB,它打开、读取、关闭都可以...您能否提供源代码以便其他人可以对其进行测试(或一个非常冗长的内置可执行文件)看看其他人是否有同样的问题?无论如何,希望这对某人有所帮助。

资源

于 2011-10-07T01:07:08.517 回答
1

没有这样的限制。你误会了。

显然,您受到地址空间和缓冲区是连续虚拟内存块的要求的限制。在 32 位系统上,每个进程只能寻址 2GB 的虚拟内存。更重要的是,您将无法分配 2GB 的连续内存块。

但这些都是一般限制。API 将愉快地读入您可以分配的ReadFile尽可能大的缓冲区。

您声称已达到 8KB 的限制,但我刚刚使用WriteFileand成功写入和读取了一个 1GB 文件ReadFile。显然你有一些问题,但它不是你认为的那样。如果您可以显示其余代码,尤其是调用您的 p/invoke 的代码,那么我相信它会变得显而易见。


现在您已经发布了完整的代码,我们可以看到问题所在。您不是在读取文件,而是在执行物理磁盘的读取。我现在明白这就是“直接从磁盘读取”的意思,但我认为你可以更具体一点!

无论如何,我不知道这里发生了什么的细节,但问题显然不是ReadFile本身,而是你的句柄是物理磁盘而不是文件的事实。

状态的文档CreateFile

一个卷包含一个或多个已安装的文件系统。卷句柄可以根据特定文件系统的判断以非缓存方式打开,即使在 CreateFile 中未指定非缓存选项也是如此。您应该假设所有 Microsoft 文件系统都以非缓存方式打开卷句柄。对文件的非缓存 I/O 的限制也适用于卷。

即使数据未缓存,文件系统也可能需要也可能不需要缓冲区对齐。但是,如果在打开卷时指定了 noncached 选项,则无论卷上的文件系统如何,都会强制执行缓冲区对齐。建议在所有文件系统上将卷句柄打开为非缓存,并遵循非缓存 I/O 限制。

我认为您应该考虑提出一个关于如何从物理磁盘读取的新问题。

于 2011-10-04T13:29:57.217 回答
0

ReadFile运行时,内核必须锁定缓冲区。它必须这样做,否则可能会发生邪恶的事情(当它试图复制数据时,一个页面可能不存在,或者更糟糕的是...... DMA 进入它)。

每个应用程序对其工作集都有限制,您也可以通过编程方式对其进行配置。如果您(或代表您的内核)尝试锁定超过工作集限制所允许的内存,则会失败。您不能锁定超过您的最小工作集大小,该大小默认为一个相当小的值(IIRC 类似于 16MB)。

请注意,“最大工作集大小”不是允许您的应用程序使用多少内存(这将是悲剧性的)。它是一个属于您的进程的页面何时被视为“可能被分页以防其他人想要内存”的数字。

所有这一切背后的目的是确保系统使用未知数量的内存继续与许多同时运行的程序一起工作。

于 2011-10-04T13:11:04.433 回答
0

不确定要读入的缓冲区的最大大小,但是缓冲区大小的原因是,在函数内部可以进行检查,这样就不会将数据写入缓冲区的末尾。如果函数没有输入缓冲区大小的概念,它可能会写入缓冲区的末尾,从而导致缓冲区溢出。那将是一个严重的安全漏洞。

于 2011-10-04T11:08:32.153 回答