3

我在 VirtualBox 中的 Windows XP 上遇到了非常奇怪的问题。

ReadFile()函数拒绝在单次调用中读取超过 16Mb 的数据。它返回错误代码 87 ( ERROR_INVALID_ARGUMENT)。看起来数据长度限制为 24 位。

这是允许我找出确切限制的示例代码。

#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>

int _tmain(int argc, _TCHAR* argv[])
{
    int fd,len,readed;
    char *buffer;
    char *fname="Z:\\test.dat";
    fd=_open(fname,_O_RDWR|_O_BINARY,_S_IREAD|_S_IWRITE);
    if (fd==-1) {
        printf("Error opening file : %s\n",strerror(errno));
        getch();
        return -1;
    }
    len=_lseek(fd,0,SEEK_END);
    _lseek(fd,0,SEEK_SET);
    if (!len) {
        printf("File length is 0.\n");
        getch();
        return -2;
    }
    buffer=(char *)malloc(len);
    if (!buffer) {
        printf("Failed to allocate memory.\n");
        getch();
        return -3;
    }
    readed=0;
    while (readed<len) {
        len-=100;
        readed=_read(fd,buffer,len);
        if (len<=100) break;
    }
    if (readed!=len) {
        printf("Failed to read file: result %d error %s\n",readed,strerror(errno));
        getch();
        return -4;
    }
    _close(fd);
    printf("Success (%u).",len);
    getch();
    return 0;
}

文件Z:\test.dat长度为 21Mb。

结果是“ Success (16777200).

我试图在谷歌中找到同样的问题但没有成功:(

可能有人知道问题的原因是什么?

4

5 回答 5

3

问题不在于它ReadFile()本身。真正的问题是你的while()循环一开始就有问题。您对lenreaded变量管理不善。在循环的每次迭代中,您递减len和重置readed. 最终,len递减到匹配的值readed并且循环停止运行。您的“成功”消息报告 16MB 的事实是巧合,因为您在读取文件时正在修改这两个变量。 len最初设置为 21MB 并倒计时,直到_read()在请求 16MB 时碰巧返回 16MB 缓冲区。这并不意味着ReadFile()在读取 16MB 时失败(如果是这种情况,第一次循环迭代将失败,因为它要求读取 21MB)。

你需要修复你的while()循环,而不是责备ReadFile()。正确的循环逻辑应该更像这样:

int total = 0; 

while (total < len)
{ 
    readed = _read(fd, &buffer[total], len-total); 
    if (readed < 1) break;
    total += readed;
} 

_close(fd); 

if (total != len)
{ 
    printf("Failed to read file: %d out of %d, error %s\n", total, len, strerror(errno)); 
    ...
    return -4; 
} 

printf("Success (%u).",total); 
...
于 2010-08-27T22:56:55.133 回答
2

我建议您使用Memory-Mapped Files。(另见http://msdn.microsoft.com/en-us/library/aa366556.aspx)。以下简单代码显示了一种方法:

LPCTSTR pszSrcFilename = TEXT("Z:\\test.dat");
HANDLE hSrcFile = CreateFile (pszSrcFilename, GENERIC_READ, FILE_SHARE_READ,
                              NULL, OPEN_EXISTING,
                              FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                              NULL);
HANDLE hMapSrcFile = CreateFileMapping (hSrcFile, NULL, PAGE_READONLY, 0, 0, NULL);
PBYTE pSrcFile = (PBYTE) MapViewOfFile (hMapSrcFile, FILE_MAP_READ, 0, 0, 0);
DWORD dwInFileSizeHigh, dwInFileSizeLow;
dwInFileSizeLow = GetFileSize (hInFile, &dwInFileSizeHigh);

经过一些简单的步骤后,您就有了一个pSrcFile代表整个文件内容的指针。这不是你需要的吗?存储在 dwInFileSizeHighdwInFileSizeLow:中的内存块的总大小((__int64)dwInFileSizeHigh << 32)+dwInFileSizeLow

这使用了用于实现交换文件(页面文件)的 Windows 内核的相同功能。它由磁盘缓存缓冲并且非常有效。如果计划主要按顺序访问文件,包括调用中的标志 FILE_FLAG_SEQUENTIAL_SCANCreateFile()将向系统提示这一事实,使其尝试提前读取以获得更好的性能。

我看到您在测试示例中读取的文件名为“Z:\test.dat”。如果它是来自网络驱动器的文件,您将看到明显的性能优势。Morover 对应于http://msdn.microsoft.com/en-us/library/aa366542.aspx你有大约 2 GB 而不是 16Mb 的限制。我建议您将文件映射到 1 GB,然后仅针对以下内容创建一个网络视图MapViewOfFile(我不确定您的代码是否需要处理如此大的文件)。更重要的是,在同一个 MSDN 页面上,您可以阅读以下内容

您选择的文件映射对象的大小控制着您可以通过内存映射“看到”文件的深度。如果创建大小为 500 Kb 的文件映射对象,则无论文件大小如何,您都只能访问文件的前 500 Kb。由于创建更大的文件映射对象不会花费您任何系统资源,因此即使您不希望查看整个文件,也要创建一个文件大小的文件映射对象(将两者的dwMaximumSizeHighdwMaximumSizeLow参数 设置为零) CreateFileMapping. 系统资源的成本来自于创建视图和访问它们。

所以内存映射文件的使用真的很便宜。如果您的程序只读取文件内容的一部分而跳过文件的大部分内容,那么您还将获得很大的性能优势,因为它只会读取您真正访问过的文件部分(四舍五入到 16K 页)。

用于文件映射的更干净的代码如下

DWORD MapFileInMemory (LPCTSTR pszFileName,
                       PBYTE *ppbyFile,
                       PDWORD pdwFileSizeLow, OUT PDWORD pdwFileSizeHigh)
{
    HANDLE  hFile = INVALID_HANDLE_VALUE, hFileMapping = NULL;
    DWORD dwStatus = NO_ERROR;
    const DWORD dwSourceId = MSG_SOURCE_MAP_FILE_IN_MEMORY;

    __try {
        hFile = CreateFile (pszFileName, FILE_READ_DATA, 0, NULL, OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
                            NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
            dwStatus = GetLastError();
            __leave;
        }

        *pdwFileSizeLow = GetFileSize (hFile, pdwFileSizeHigh);
        if (*pdwFileSizeLow == INVALID_FILE_SIZE){
            dwStatus = GetLastError();
            __leave;
        }

        hFileMapping = CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL);
        if (!hFileMapping){
            dwStatus = GetLastError();
            __leave;
        }

        *ppbyFile = (PBYTE) MapViewOfFile (hFileMapping, FILE_MAP_READ, 0, 0, 0);
        if (*ppbyFile == NULL) {
            dwStatus = GetLastError();
            __leave;
        }
    }
    __finally {
        if (hFileMapping) CloseHandle (hFileMapping);
        if (hFile != INVALID_HANDLE_VALUE) CloseHandle (hFile);
    }

    return dwStatus;
}

BOOL UnmapFileFromMemory (LPCVOID lpBaseAddress)
{
    return UnmapViewOfFile (lpBaseAddress);
}
于 2010-08-27T22:12:12.947 回答
2

设备驱动程序返回的字节数少于请求的字节数是完全合法的。这就是 ReadFile() 具有 lpNumberOfBytesRead 参数的原因。您应该避免使用低级 CRT 实现细节,例如 _read()。请改用 fread()。

更新:这不是正确的答案。看起来您的虚拟机只是拒绝考虑要求超过 16MB 的 ReadFile() 调用。可能与它用来与主机操作系统对话的内部缓冲区有关。除了在循环中调用 fread() 之外,您无能为力,这样您就可以保持在这个上限以下。

于 2010-08-27T18:42:45.543 回答
2

我假设,Z:在你的例子中是一个共享文件夹。我只是偶然发现了同样的错误,并花了一些时间试图将其固定下来。

看来,这个问题已经有一段时间了:https ://www.virtualbox.org/ticket/5830 。

于 2012-01-09T03:15:17.750 回答
1

我认为这是 Windows 的限制。根据我的经验,在 Windows XP 和 2003 x86 和 x64 上,ReadFile()无法读取超过 16 MB。在 Windows 2008 r2 和 Windows 8 x64 上,阈值要高得多 > 1 GB。我将无缓冲 IO 用于备份实用程序。

我从来没有用过MMF,但是 ReadFile 和 WriteFile 用起来非常快FILE_FLAG_NO_BUFFERING。以 147 MB​​/s 的速度读取时,CPU 使用率* 几乎为 0。

英特尔 i7

于 2012-09-23T12:39:10.517 回答