我正在使用
io_uring
将一些数据写入磁盘,并注意到发出写入请求后文件偏移量不会自动增加。因此,如果我通过 liburing
发出两个写入请求,第二个请求将覆盖第一个请求,因为两个请求都试图写入文件的开头。单独使用这些 posix api(write
和 writev
)不会导致任何问题,但通过 liburing
使用它们永远不会提前文件偏移量。 liburing
的手册页说将 offset
设置为 -1 将自动推进文件偏移量,但情况似乎并非如此。
下面是一个小例子。预期行为是将 4096 字节的 0 到 1023 数字写入文件,后跟 4096 字节的 0。然而,该文件仅包含 4096 字节的 0。如果我删除行
write_buffer_to_file(2)
,它现在包含 4096 字节的数字,因此第二次调用似乎覆盖了第一个调用的内容。 lseek
始终返回 0 的事实证实了文件偏移量永远不会改变。
代码片段使用 gcc 编译并在内核 5.14 的 RHEL 9.3 上运行。
#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;
}
原来这是旧内核版本的问题。在 Ubuntu 22.04(内核 6.2)上尝试相同的代码不会导致任何问题。
请参阅此 GitHub 问题。最好能确定修复此错误的提交,但修复发生在很久以前,因此很难找到。