You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用O_DIRECT读取持续写入文件时read()返回EINVAL的问题

O_DIRECT读取并发写入文件时返回EINVAL的问题解答

问题背景

我编写了一个使用O_DIRECT标志读取文件的程序,该文件正被另一个进程持续写入。读取循环在到达写入操作的位置前运行正常,但此时read()会因EINVAL(无效参数)失败;若不使用O_DIRECT则无此问题,且read()会返回有效值。已确认块大小为4096。

代码示例

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <malloc.h>
#include <iostream>

#define BUFFER_SIZE 524288  // 512 KB

int main() {
    int fd;
    char *buffer;
    ssize_t bytesRead;

    // Open file with O_DIRECT
    fd = open("/data/file.lz4", O_RDONLY | O_DIRECT);
    if (fd == -1) {
        perror("Error opening file");
        return EXIT_FAILURE;
    }

    // Allocate aligned memory for O_DIRECT
    buffer = (char *) aligned_alloc(4096, BUFFER_SIZE);

    // Read in a loop
    while (1) {
        bytesRead = read(fd, buffer, BUFFER_SIZE);
        if (bytesRead == -1) {
            std::cerr << "Error reading file: " << strerror(errno) << std::endl;
            std::cerr << "fd: " << fd << " buffer: " << (void*)buffer << " BUFFER_SIZE: " << BUFFER_SIZE << std::endl;
            break;
        }
        std::cout << bytesRead << std::endl;
    }

    // Free the aligned buffer
    free(buffer);

    // Close file
    close(fd);

    return 0;
}

观测到的行为

程序在文件有数据时读取正常;当到达写入位置时,输出如下:

524288
524288
524288
524288
524288
172020
0
0
Error reading file: Invalid argument
fd: 3 buffer: 0x7f4140e88000 BUFFER_SIZE: 524288

这说明read()在尝试读取正在写入的数据时失败。

已检查内容

  • 使用aligned_alloc(4096, BUFFER_SIZE)正确对齐了缓冲区
  • BUFFER_SIZE是4096的倍数,满足O_DIRECT对齐要求
  • 错误发生前fd有效
  • 文件存在且正被另一个进程持续更新

环境细节

  • 文件系统:ext4(本地SSD)
  • CPU型号:Intel(R) Core(TM) i9-14900KS
  • Linux版本:5.15.77-1-lts

疑问解答

1. 为什么到达写入点时read()会返回EINVAL?

O_DIRECT要求读取的偏移量、缓冲区地址、读取长度都必须严格对齐到文件系统的块大小(这里是4096)。当读取到文件末尾的未完全写入的块时,问题就出现了:

  • 并发写入时,文件长度可能处于“中间状态”——写入进程正在写入一个块,文件长度已更新,但该块尚未完全写入磁盘。
  • 此时read()请求跨越了已写入块的边界,尝试读取未完全初始化的块。O_DIRECT绕过页缓存,无法像普通读取那样用0填充未写入区域,内核会直接返回EINVAL拒绝这种“部分块”读取。
  • 普通读取(无O_DIRECT)依赖页缓存,会自动处理这种情况:要么等待写入完成,要么返回已写入部分并填充剩余为0,不会返回错误。

2. 当读取到未写入部分时,能否等待更多数据而非直接失败?

默认情况下不行,O_DIRECT的设计目标是直接与存储设备交互,不经过页缓存,因此不提供“等待数据”的语义。但可以通过以下方式实现类似效果:

  • 检测EINVAL并重试:当read()返回EINVAL时,短暂休眠(比如用usleep())后重新尝试读取,直到写入进程完成该块写入,读取即可成功。
  • 结合O_NONBLOCK轮询:打开文件时加上O_NONBLOCK,但O_DIRECT与O_NONBLOCK的组合行为需测试,部分场景仍可能返回EINVAL,最终还是需要重试逻辑。
  • 放弃O_DIRECT:如果业务允许,去掉O_DIRECT标志,依赖页缓存处理并发写入的同步问题,这是最简单的解决方案。

3. O_DIRECT与并发写入的交互机制

O_DIRECT绕过Linux页缓存,直接在用户缓冲区和存储设备间传输数据,与并发写入的交互有以下核心特点:

  • 无缓存一致性保证:页缓存会自动处理读写一致性,而O_DIRECT直接从磁盘取数据,无法感知写入进程在页缓存中的未刷新数据——只有写入进程调用fsync()或fdatasync()将数据刷到磁盘后,O_DIRECT才能读取到最新内容。
  • 严格对齐约束:任何不符合块对齐的读取请求(包括读取未完全写入的块)都会被内核拒绝,返回EINVAL。
  • 无自动等待/重试:内核不会为O_DIRECT请求等待写入操作完成,因为不涉及页缓存的同步逻辑,一旦检测到不符合要求的读取条件,直接返回错误。
  • 数据可见性延迟:写入的数据必须刷到磁盘后才能被O_DIRECT读取,否则读取的可能是旧数据。

内容的提问来源于stack exchange,提问作者rishi jain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:03:17