我正在研究基准程序。进行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()
会挂在大型二进制文件上?
编辑:更新代码示例以更准确地反映错误检查。
答案 0 :(得分:3)
您的代码存在多个问题。
如果errno
的值等于EINTR
,则此代码将无法正常运行:
while (errno == EINTR && tries < MAX_ATTEMPTS) {
fd = open(fpath.c_str(), O_RDONLY | O_DIRECT | O_LARGEFILE);
tries++;
}
该代码在文件成功打开后不会停止,并会一遍又一遍地重新打开文件并泄漏文件描述符,因为它在errno
为EINTR
时保持循环。
这会更好:
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
答案 2 :(得分:-1)
read()
悬挂的大多数示例似乎是在使用管道或非标准I / O设备(例如,串行)时。磁盘I / O,不是那么多。
O_DIRECT
标志对文件系统和块设备很有用。有了这个标志,人们通常会将页面映射到用户空间。
对于套接字,管道和串行设备,它显然没用,因为内核不会缓存该数据。
您的更新代码会挂起,因为fd
初始化为0
STDIN_FILENO
并且它永远不会打开该文件,然后挂起来自stdin
的读取。