文件read()挂起在二进制大文件上

时间:2018-03-22 17:07:12

标签: c++ linux c++11

我正在研究基准程序。进行read()系统调用后,程序似乎无限期挂起。目标文件是1 GB的二进制数据,我试图直接读取大小为1,10或100 MB的缓冲区。

我正在使用std::vector<char>来实现动态大小的缓冲区并将&vec[0]移交给read()。我还使用open()标志调用O_DIRECT来绕过内核缓存。

基本编码细节如下:

std::string fpath{"/path/to/file"};
size_t tries{};
int fd{};
while (errno == EINTR && tries < MAX_ATTEMPTS) {
    fd = open(fpath.c_str(), O_RDONLY | O_DIRECT | O_LARGEFILE);
    tries++;
}

// Throw exception if error opening file
if (fd == -1) {
    ostringstream ss {};
    switch (errno) {
    case EACCES:
        ss << "Error accessing file " << fpath << ": Permission denied";
        break;
    case EINVAL:
        ss << "Invalid file open flags; system may also not support O_DIRECT flag, required for this benchmark";
        break;
    case ENAMETOOLONG:
        ss << "Invalid path name: Too long";
        break;
    case ENOMEM:
        ss << "Kernel error: Out of memory";
    }
    throw invalid_argument {ss.str()};
}

size_t buf_sz{1024*1024};          // 1 MiB buffer
std::vector<char> buffer(buf_sz);  // Creates vector pre-allocated with buf_sz chars (bytes)
                                   // Result is 0-filled buffer of size buf_sz

auto bytes_read = read(fd, &buffer[0], buf_sz);

使用gdb查看可执行文件显示正确分配了缓冲区,并且我测试过的文件在xxd中检出。我正在使用g ++ 7.3.1(支持C ++ 11)在Fedora Server 27 VM上编译我的代码。

为什么read()会挂在大型二进制文件上? 编辑:更新代码示例以更准确地反映错误检查。

3 个答案:

答案 0 :(得分:3)

您的代码存在多个问题。

如果errno的值等于EINTR,则此代码将无法正常运行:

while (errno == EINTR && tries < MAX_ATTEMPTS) {
    fd = open(fpath.c_str(), O_RDONLY | O_DIRECT | O_LARGEFILE);
    tries++;
}

该代码在文件成功打开后不会停止,并会一遍又一遍地重新打开文件并泄漏文件描述符,因为它在errnoEINTR时保持循环。

这会更好:

do
{
    fd = open(fpath.c_str(), O_RDONLY | O_DIRECT | O_LARGEFILE);
    tries++;
}
while ( ( -1 == fd ) && ( EINTR == errno ) && ( tries < MAX_ATTEMPTS ) );

其次,如评论中所述,O_DIRECT可以对内存施加对齐限制。您可能需要页面对齐的内存:

所以

size_t buf_sz{1024*1024};          // 1 MiB buffer
std::vector<char> buffer(buf_sz);  // Creates vector pre-allocated with buf_sz chars (bytes)
                                   // Result is 0-filled buffer of size buf_sz

auto bytes_read = read(fd, &buffer[0], buf_sz);

变为

size_t buf_sz{1024*1024};          // 1 MiB buffer

// page-aligned buffer
buffer = mmap( 0, buf_sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, NULL );

auto bytes_read = read(fd, &buffer[0], buf_sz);

另请注意,O_DIRECT的Linux实现可能非常狡猾。它已经变得越来越好,但仍有潜在的缺陷,根本没有很好的记录。除了对齐限制之外,如果文件中的最后一个数据量不是整页,例如,如果直接IO的文件系统实现不允许,您可能无法读取它你可以阅读除完整页面(或其他一些块大小)以外的任何内容。同样地,对于write()调用 - 您可能无法编写任意数量的字节,您可能会被限制为类似4k页的内容。

这也很关键:

  

大多数read()挂起的例子似乎是在使用管道或非标准I / O设备(例如,串行)时。磁盘I / O,不是那么多。

有些设备根本不支持直接IO。他们应该返回一个错误,但同样,Linux上的O_DIRECT实现可能会非常突然。

答案 1 :(得分:0)

粘贴程序并在我的linux系统上运行,这是一个有效且无挂起的程序。

导致失败的最可能原因是文件不是文件系统项,或者它的硬件元素不起作用。

尝试使用较小的尺寸 - 确认,并尝试使用其他机器来帮助诊断

我的完整代码(没有错误检查)

#include <vector>
#include <string>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main( int argc, char ** argv )
{
    std::string fpath{"myfile.txt" };
    auto fd = open(fpath.c_str(), O_RDONLY | O_DIRECT | O_LARGEFILE);

    size_t buf_sz{1024*1024};          // 1 MiB buffer
    std::vector<char> buffer(buf_sz);  // Creates vector pre-allocated with buf_sz chars (bytes)
                                       // Result is 0-filled buffer of size buf_sz

    auto bytes_read = read(fd, &buffer[0], buf_sz);
}

myfile.txt是用

创建的
dd if=/dev/zero of=myfile.txt bs=1024 count=1024
  • 如果文件大小不是1Mb,则可能会失败。
  • 如果文件是管道,它可以阻止,直到数据可用。

答案 2 :(得分:-1)

  

read()悬挂的大多数示例似乎是在使用管道或非标准I / O设备(例如,串行)时。磁盘I / O,不是那么多。

O_DIRECT标志对文件系统和块设备很有用。有了这个标志,人们通常会将页面映射到用户空间。

对于套接字,管道和串行设备,它显然没用,因为内核不会缓存该数据。

您的更新代码会挂起,因为fd初始化为0 STDIN_FILENO并且它永远不会打开该文件,然后挂起来自stdin的读取。

相关问题