文件 read() 挂在二进制大文件上
File read() hangs on binary large file
我正在研究一个基准测试程序。进行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++;
}
当文件成功打开时,该代码不会停止,并且会一遍又一遍地重新打开文件并泄漏文件描述符,因为一旦EINTR
errno
,它就会不断循环。
这样会更好:
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_FILENO
0
初始化的,并且它永远不会打开该文件,然后它挂起读取stdin
。
- 正在读取二进制文件(is_open)
- 在C++中将类(带有Vector成员)保存为二进制文件
- 如何从二进制文件中读取字符串
- 如何从dicom文件中读取二进制数据
- 保存/加载大量短数组到二进制文件
- 从二进制文件中读取整数数组
- Android 在编译二进制文件时重建静态库
- 在 C++ 中将双精度变量写入二进制文件
- clang 的 libFuzzer 可以在同一二进制文件中测试超过 1 个 API 吗?
- 如何使字符串出现在编译的二进制可执行文件的开头?
- C++:实际上不是从二进制文件中读取
- 如何从二进制文件中的给定符号中获取调用程序图
- 将内部带有矢量的结构保存/读取到二进制文件中
- 编译多个C++文件.调用二进制文件以运行代码
- 如何使用位字段将数据从二进制文件复制到结构中?
- 在Ostream上使用std :: endl使我的文件二进制
- C++文件二进制模式副本
- 文本文件二进制搜索
- 文件二进制与文本
- C/ c++中对大文件二进制数据的逻辑运算