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

使用io_uring写入文件时文件偏移量不自动递增的问题

问题

使用io_uring向磁盘写入数据时,发现发出写入请求后文件偏移量不会自动递增。通过liburing发起两次写入请求时,第二次会覆盖第一次的内容——两者都写入文件开头。但单独使用POSIX API(write和writev)时无此问题,偏移量能正常推进。

根据liburing手册说明,将offset设为-1应该会自动推进偏移量,但实际测试并未生效。示例中预期先写入4096字节0到1023的数字,再写入4096字节0,但最终文件仅包含4096字节0;移除第二次写入调用时,文件能正确保存数字内容,说明第二次写入覆盖了第一次。调用lseek始终返回0,也证实偏移量从未改变。

测试环境:RHEL 9.3(内核版本5.14),gcc编译。

原测试代码:

#include <cstring>
#include <iostream>
#include <liburing.h>
#include <unistd.h>

// a small macro to check for errors
#define SYSCALL(expr) if ((expr) < 0) { \
    perror("System call error");        \
}

const int WRITE_SIZE = 4096; // satisfy alignment requirement of O_DIRECT
int fd; // file descriptor
int *buffer; // write buffer
struct io_uring ring;

// write the content of the buffer to fd; the data argument sets user_data in the sqe, which shouldn't affect the result
void write_buffer_to_file(int data) {
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    io_uring_sqe_set_data(sqe, (void*)(intptr_t)data);
    // according to the documentation, setting offset to -1 will advance the offset
    // neither 0 nor -1 work in my testing
    io_uring_prep_write(sqe, fd, buffer, WRITE_SIZE, -1);
    SYSCALL(io_uring_submit(&ring))
    std::cout << "Submitted " << sqe->user_data << std::endl;

    // now wait for it to complete
    struct io_uring_cqe *cqe;
    SYSCALL(io_uring_wait_cqe(&ring, &cqe));
    if (cqe->res < 0) {
        perror("cqe res less than 0");
        std::cerr << std::strerror(-cqe->res) << std::endl;
    }
    io_uring_cqe_seen(&ring, cqe);
    std::cout << "Reaped " << io_uring_cqe_get_data(cqe) << std::endl;
    // this line always prints 0 even though it's supposed to advanced 4096 bytes
    std::cout << "Current offset: " << lseek(fd, 0, SEEK_CUR) << std::endl;
}

int main() {
    // set up the file and the write buffer
    fd = open("test_file", O_CREAT | O_WRONLY | O_DIRECT, 0744);
    SYSCALL(fd);
    // O_DIRECT has stricter memory alignment requirements
    posix_memalign((void**)&buffer, 512, WRITE_SIZE);
    for (int i = 0; i < WRITE_SIZE / sizeof(int); i++) {
        buffer[i] = i;
    }
    
    io_uring_queue_init(5, &ring, 0);
    write_buffer_to_file(1);

    // set everything in the buffer to 0 and then write again
    memset(buffer, 0, WRITE_SIZE);
    write_buffer_to_file(2);

    io_uring_queue_exit(&ring);

    close(fd);
    return 0;
}
原因分析

核心问题在于O_DIRECT标志与自动偏移逻辑的冲突:

  • 当使用O_DIRECT打开文件时,内核不会维护文件的当前偏移量(pos)。这是因为O_DIRECT设计为绕过页缓存、直接与磁盘交互,要求所有IO操作必须指定明确的偏移和对齐的缓冲区。
  • liburing中offset=-1的自动偏移逻辑,依赖内核维护的文件当前偏移量来计算下一次写入位置,但O_DIRECT模式下这个偏移量不会被更新,导致每次写入都默认从0开始。
  • POSIX的write/writev在O_DIRECT下能正常推进偏移,是因为glibc在用户空间模拟维护了偏移量;而io_uring直接与内核交互,不走glibc的这个模拟逻辑,因此无法自动更新偏移。
解决方案

有两种可行的处理方式:

  • 手动维护并指定偏移量:自己跟踪当前写入位置,每次发起io_uring写入时传入正确的偏移,完成后根据实际写入字节数更新偏移值。这是推荐方案,能保留O_DIRECT的性能特性。
  • 移除O_DIRECT标志:如果不需要直接IO的性能优势,可以去掉O_DIRECT,此时内核会正常维护文件偏移,offset=-1的自动推进逻辑就能生效。
修正后的代码
#include <cstring>
#include <iostream>
#include <liburing.h>
#include <unistd.h>
#include <stdlib.h>

// 错误检查宏
#define SYSCALL(expr) if ((expr) < 0) { \
    perror("System call error");        \
}

const int WRITE_SIZE = 4096; // 满足O_DIRECT的对齐要求
int fd; // 文件描述符
int *buffer; // 写入缓冲区
struct io_uring ring;
off_t current_offset = 0; // 手动维护当前写入偏移

// 将缓冲区内容写入文件,手动指定偏移
void write_buffer_to_file(int data) {
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    io_uring_sqe_set_data(sqe, (void*)(intptr_t)data);
    // 使用手动维护的偏移量
    io_uring_prep_write(sqe, fd, buffer, WRITE_SIZE, current_offset);
    SYSCALL(io_uring_submit(&ring))
    std::cout << "Submitted " << sqe->user_data << std::endl;

    // 等待完成
    struct io_uring_cqe *cqe;
    SYSCALL(io_uring_wait_cqe(&ring, &cqe));
    if (cqe->res < 0) {
        perror("cqe res less than 0");
        std::cerr << std::strerror(-cqe->res) << std::endl;
    } else {
        // 写入完成后更新偏移量
        current_offset += cqe->res;
    }
    io_uring_cqe_seen(&ring, cqe);
    std::cout << "Reaped " << io_uring_cqe_get_data(cqe) << std::endl;
    std::cout << "Current offset: " << lseek(fd, 0, SEEK_CUR) << std::endl;
}

int main() {
    // 打开文件(保留O_DIRECT)
    fd = open("test_file", O_CREAT | O_WRONLY | O_DIRECT, 0744);
    SYSCALL(fd);
    // 分配对齐的缓冲区
    posix_memalign((void**)&buffer, 512, WRITE_SIZE);
    for (int i = 0; i < WRITE_SIZE / sizeof(int); i++) {
        buffer[i] = i;
    }
    
    io_uring_queue_init(5, &ring, 0);
    write_buffer_to_file(1);

    // 缓冲区置0后再次写入
    memset(buffer, 0, WRITE_SIZE);
    write_buffer_to_file(2);

    io_uring_queue_exit(&ring);

    close(fd);
    free(buffer); // 释放内存,原代码遗漏此操作
    return 0;
}

内容的提问来源于stack exchange,提问作者Peter Li

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 16:24:51