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

File read() hangs on binary large file

本文关键字:文件 二进制 read      更新时间:2023-10-16

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

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

下面捕获了基本的编码详细信息:

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()挂在大型二进制文件上? 编辑:更新了代码示例以更准确地反映错误检查。

您的代码存在多个问题。

如果errno的值等于EINTR,则此代码将永远无法正常工作:

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

当文件成功打开时,该代码不会停止,并且会一遍又一遍地重新打开文件并泄漏文件描述符,因为一旦EINTRerrno,它就会不断循环。

这样会更好:

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实现可能非常命中或未命中。

粘贴你的程序并运行在我的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,则可能会失败。
  • 如果文件是管道,则可以阻止,直到数据可用。

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

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

对于套接字、管道和串行设备,它毫无用处,因为内核不缓存该数据。


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