1

我正在玩 io_uring,https: //kernel.dk/io_uring.pdf,看看它是否可以用于异步文件 I/O 以进行日志记录。这是一个简单的程序,它打开一个文件,统计文件,然后从文件中读取前 4k。当文件存在并且可读时,该程序成功运行完成。但完成队列条目中的 user_data 字段始终为零。io_uring 的文档说:

user_data 在操作码中很常见,并且不受内核影响。当为此请求发布完成事件时,它只是简单地复制到完成事件 cqe。

由于完成没有排序,因此需要 user_data 字段来匹配完成与提交。如果该字段始终为零,那么如何使用它?

#include <iostream>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <liburing.h>
#include <stdlib.h>

int main() {
  struct io_uring ring;
  // see man io_uring_setup for what this does
  auto ret = io_uring_queue_init(64, &ring, 0);

  if (ret) {
    perror("Failed initialize uring.");
    exit(1);
  }

  std::cout << "I/O uring initialized successfully. " << std::endl;

  auto directory_fd = open("/tmp", O_RDONLY);
  if (directory_fd < 0) {
    perror("Failed to open current directory.");
    exit(1);
  }

  struct io_uring_sqe *submission_queue_entry = io_uring_get_sqe(&ring);
  submission_queue_entry->user_data = 100;
  io_uring_prep_openat(submission_queue_entry, directory_fd, "stuff", O_RDONLY, 0);


  submission_queue_entry = io_uring_get_sqe(&ring);
  submission_queue_entry->user_data = 1000;
  struct statx statx_info;
  io_uring_prep_statx(submission_queue_entry, directory_fd, "stuff", 0, STATX_SIZE, &statx_info);

  //TODO: what does this actually return?
  auto submit_error = io_uring_submit(&ring);
  if (submit_error != 2) {
    std::cerr << strerror(submit_error) << std::endl;
    exit(2);
  }

  int file_fd = -1;
  uint32_t responses = 0;
  while (responses != 2) {
    struct io_uring_cqe *completion_queue_entry = 0;
    auto wait_return = io_uring_wait_cqe(&ring, &completion_queue_entry);
    if (wait_return) {
      std::cerr << "Completion queue wait error. " << std::endl;
      exit(2);
    }

    std::cout << "user data " << completion_queue_entry->user_data << " entry ptr " << completion_queue_entry << " ret " << completion_queue_entry->res << std::endl;
    std::cout << "size " << statx_info.stx_size << std::endl;
    io_uring_cqe_seen(&ring, completion_queue_entry);
    if (completion_queue_entry->res > 0) {
      file_fd = completion_queue_entry->res;
    }
    responses++;
  }


  submission_queue_entry = io_uring_get_sqe(&ring);
  submission_queue_entry->user_data = 66666;
  char buf[1024 * 4];
  io_uring_prep_read(submission_queue_entry, file_fd, buf,  1024 * 4,  0);
  io_uring_submit(&ring);
  struct io_uring_cqe* read_entry = 0;
  auto read_wait_rv = io_uring_wait_cqe(&ring, &read_entry);
  if (read_wait_rv) {
    std::cerr << "Error waiting for read to complete." << std::endl;
    exit(2);
  }
  std::cout << "Read user data " << read_entry->user_data << " completed with " << read_entry->res << std::endl;
  if (read_entry->res < 0) {
    std::cout << "Read error " << strerror(-read_entry->res) << std::endl;
  }
}

输出

I/O 初始化成功。
用户数据 0 条目 ptr 0x7f4e3158c140 ret 5
大小 1048576
用户数据 0 条目 ptr 0x7f4e3158c150 ret 0
大小 1048576
读取用户数据 0 完成 4096

4

1 回答 1

3

如果您user_data在调用io_uring_prep_openat()/后尝试设置会发生什么io_uring_prep_statx()

我问这个是因为谷歌搜索io_uring_prep_statx表明它来自liburing library

在 liburing 源中搜索io_uring_prep_openat会导致我们在 liburing.h 中定义io_uring_prep_openat()

static inline void io_uring_prep_openat(struct io_uring_sqe *sqe, int dfd,
                    const char *path, int flags, mode_t mode)
{
    io_uring_prep_rw(IORING_OP_OPENAT, sqe, dfd, path, mode, 0);
    sqe->open_flags = flags;
}

搜索 liburing 源io_uring_prep_statx会导致定义io_uring_prep_statx()

static inline void io_uring_prep_statx(struct io_uring_sqe *sqe, int dfd,
                const char *path, int flags, unsigned mask,
                struct statx *statxbuf)
{
    io_uring_prep_rw(IORING_OP_STATX, sqe, dfd, path, mask,
                (__u64) (unsigned long) statxbuf);
    sqe->statx_flags = flags;
}

追逐电话让我们了解io_uring_prep_rw 的定义

static inline void io_uring_prep_rw(int op, struct io_uring_sqe *sqe, int fd,
                    const void *addr, unsigned len,
                    __u64 offset)
{
    sqe->opcode = op;
    sqe->flags = 0;
    sqe->ioprio = 0;
    sqe->fd = fd;
    sqe->off = offset;
    sqe->addr = (unsigned long) addr;
    sqe->len = len;
    sqe->rw_flags = 0;
    sqe->user_data = 0;
    sqe->__pad2[0] = sqe->__pad2[1] = sqe->__pad2[2] = 0;
}

PS:我注意到你有一条评论说

  //TODO: what does this actually return?
  auto submit_error = io_uring_submit(&ring);

好吧,如果我们在 liburing repo 中搜索“int io_uring_submit”,我们会遇到以下内容src/queue.c

/*
 * Submit sqes acquired from io_uring_get_sqe() to the kernel.
 *
 * Returns number of sqes submitted
 */
int io_uring_submit(struct io_uring *ring)

这最终将调用链接到io_uring_enter()系统调用(原始手册页),因此您可以阅读以了解更多详细信息。

更新:提问者说移动作业解决了他们的问题,所以我花了一些时间思考他们引用的文字。进一步阅读后,我发现了一个微妙之处(添加了重点):

user_data在操作码中很常见,并且不受内核的影响。当为此请求发布完成事件时,它只是简单地复制到完成事件 cqe。

文档前面有类似的声明(再次强调):

cqe 包含一个user_data字段。该字段从初始请求提交中携带,并且可以包含应用程序识别所述请求所需的任何信息。一种常见的用例是让它成为原始请求的指针。内核不会触及这个字段,它只是直接从提交到完成事件。

该语句适用于io_uring 内核系统调用,但io_uring_prep_openat()/io_uring_prep_statx()liburing函数。liburing是一个用户空间帮助程序库,因此上述关于user_data不必适用于所有liburing函数的语句。

如果该字段始终为零,那么如何使用它?

该字段被某些liburing准备辅助函数归零。在这种情况下,只能在调用这些辅助函数后设置(并保留新值)。io_uring内核系统调用按照报价运行。

于 2021-04-11T07:01:58.243 回答