在x86_64上读取6GB大文件的read()操作失败

15

以下是我的问题描述:

我想使用C语言中的read系统调用将一个大约6.3GB大小的文件全部读入内存,但是出现了错误。 以下是代码:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>

int main(int argc, char* argv[]) {
    int _fd = open(argv[1], O_RDONLY, (mode_t) 0400);
    if (_fd == -1)
        return 1;
    off_t size = lseek(_fd, 0, SEEK_END);
    printf("total size: %lld\n", size);
    lseek(_fd, 0, SEEK_SET);
    char *buffer = malloc(size);
    assert(buffer);
    off_t total = 0;
    ssize_t ret = read(_fd, buffer, size);
    if (ret != size) {
        printf("read fail, %lld, reason:%s\n", ret, strerror(errno));
        printf("int max: %d\n", INT_MAX);
    }
}

用以下命令进行编译:

gcc read_test.c

然后使用以下命令运行:

./a.out bigfile

输出:

total size: 6685526352
read fail, 2147479552, reason:Success
int max: 2147483647

系统环境是什么?请提供更多上下文以便我能够为您提供准确的翻译。
 3.10.0_1-0-0-8 #1 SMP Thu Oct 29 13:04:32 CST 2015 x86_64 x86_64 x86_64 GNU/Linux

有两个地方我不理解:

  1. 读取大文件时失败,但读小文件却没有问题。
  2. 即使出现错误,errno 似乎也没有正确设置。

5
通俗易懂翻译:成功读取了“2147479552”字节的电话呼叫。你需要循环直到消耗完所有数据。顺便问一下,你总共有多少内存? - UmNyobe
为什么?很少有情况需要将整个文件存储在内存中。 - user207421
你的系统是否限制了可用内存?你尝试过 ulimit -s unlimited 吗? - amn41
相关链接:http://stackoverflow.com/q/10178660/694576 - alk
2
如果您正在使用像open()read()这样的POSIX函数,您也可以使用POSIX stat()和/或fstat()直接获取文件的大小。 - Andrew Henle
4
根据您将如何使用内存中的大文件,mmap 可能比 read 更合适。 - zwol
4个回答

15

read系统调用可能会因多种原因返回小于请求大小的值,正数非零的返回值并不是错误,此时errno未设置,其值也是不确定的。您应该在循环中继续读取,直到read返回0表示文件结尾或-1表示错误。即使从常规文件中读取,仍然有一种很常见的bug,就是依赖于read在单个调用中读取完整块。使用fread可以得到更简单的语义。

您打印了INT_MAX的值,这与您的问题无关。重要的是off_tsize_t的大小。在您的平台上,64位GNU / Linux,您很幸运,off_tsize_t都是64位长。按定义ssize_t具有与size_t相同的大小。在其他64位平台上,off_t可能比size_t小,从而无法正确评估文件大小,或者size_t可能比off_t小,从而让malloc分配一个小于文件大小的块。请注意,在这种情况下,read将传递相同的较小大小,因为size在两次调用中都会被静默截断。


非常感谢!当我在循环中一直读取,直到达到0或-1时,一切都正常。 - zhanglistar
3
这个问题没有确定的答案:对于小块数据,fread 可能会更快,因为标准I/O包默认进行缓冲;对于大块数据,它取决于实际的实现。请注意,fread 是可移植的解决方案。read() 是在Posix中标准化的系统调用,不是所有系统都支持。 - chqrlie

7
如果读取返回 -1,则您应该中止读取。从手册页面上看到:
成功时会返回已读取的字节数(零表示文件结束),并且文件位置将因此向前移动。如果此数字小于请求的字节数,则不是错误;
我的猜测是,在文件系统的2G边界处,read()可能会读取一个短缓冲区。

0
尝试在打开时使用#define _FILE_OFFSET_BITS 64,以及在使用lseek64时使用#define _LARGEFILE64_SOURCE。这样就可以读写大于2GB的文件了。

0

read() 系统调用无法一次读取大量数据。这取决于许多因素,如内核的内部缓冲区、媒体的设备驱动程序实现等。在您的示例中,您正在尝试检查 read() 是否已读取长度为 size 的数据,如果没有,则打印失败。您需要不断读取数据,直到读取的字节数为 0,并且需要检查 read() 返回的返回代码,如果是 -1,则表示读取失败,在这种情况下,您需要检查 errno 是否被设置。

此外,我建议不要一次分配大量内存,即使系统能够分配大量内存,因为这不是一个好的实现。如果可能的话,请考虑将大小分成几个块。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接