liburing和Linux io_uring源码阅读

liburing
版本

系统内核版本: Linux 5.15.0-107-generic #117~20.04.1-Ubuntu x86_64 GNU/Linux

源代码版本: git@github.com:torvalds/linux.git v5.15

阅读入口

从最简单的代码看起,即阅读入口:examples/io_uring-test.c
此源文件内部调用liburing相关函数包含
io_uring_queue_initio_uring_get_sqeio_uring_prep_readvio_uring_submitio_uring_wait_cqeio_uring_cqe_seenio_uring_queue_exit
内核提供的函数仅有三个:io_uring_setup@425io_uring_enter@426io_uring_register@427
其中io_uring_queue_init调用的内核函数为io_uring_setup
以此为起点,阅读内核代码。

liburing调用栈
// 示例程序调用代码
struct io_uring_params p;
memset(&p, 0, sizeof(p));
p.flags = 0;
struct io_uring ring;
io_uring_queue_init_params(64, &ring, &p);
int io_uring_queue_init_params(unsigned entries, struct io_uring *ring,
                               struct io_uring_params *p)
{
    int ret;

    ret = io_uring_queue_init_try_nosqarr(entries, ring, p, NULL, 0);
    return ret >= 0 ? 0 : ret;
}
static int io_uring_queue_init_try_nosqarr(unsigned entries, struct io_uring *ring,
                                           struct io_uring_params *p, void *buf,
                                           size_t buf_size)
{
    unsigned flags = p->flags;
    int ret;

    // 重点在__io_uring_queue_init_params函数
    p->flags |= IORING_SETUP_NO_SQARRAY;
    ret = __io_uring_queue_init_params(entries, ring, p, buf, buf_size);

    // 5.15内核不支持IORING_SETUP_NO_SQARRAY, 故会返回 -EINVAL
    // 所以真正执行的是下面的__io_uring_queue_init_params
    if (ret != -EINVAL || (flags & IORING_SETUP_NO_SQARRAY))
        return ret;

    p->flags = flags;
    return __io_uring_queue_init_params(entries, ring, p, buf, buf_size);
}
int __io_uring_queue_init_params(unsigned entries, struct io_uring *ring,
                                 struct io_uring_params *p, void *buf,
                                 size_t buf_size)
{
    int fd, ret = 0;
    unsigned *sq_array;
    unsigned sq_entries, index;

    memset(ring, 0, sizeof(*ring));

    // 省去一些无法走到的代码
    fd = __sys_io_uring_setup(entries, p);
    if (fd < 0) {
        return fd;
    }

    return fd
}

__sys_io_uring_setup进入内核代码

// 根据调用号425找到函数地址 io_uring_setup
// grep -rn "SYSCALL_DEFINE" $(find ./ -name '*.c')
static long io_uring_setup(u32 entries, struct io_uring_params __user *params)
{
    struct io_uring_params p;
    int i;

    // 将数据从用户空间拷贝到内核空间
    if (copy_from_user(&p, params, sizeof(p)))
        return -EFAULT;
    for (i = 0; i < ARRAY_SIZE(p.resv); i++) {
        if (p.resv[i])
            return -EINVAL;
    }

    if (p.flags & ~(IORING_SETUP_IOPOLL | IORING_SETUP_SQPOLL |
                    IORING_SETUP_SQ_AFF | IORING_SETUP_CQSIZE |
                    IORING_SETUP_CLAMP | IORING_SETUP_ATTACH_WQ |
                    IORING_SETUP_R_DISABLED))
        return -EINVAL;

    return io_uring_create(entries, &p, params);
}

由于io_uring_create篇幅过长,走不到的地方将会对其进行裁剪

static int io_uring_create(unsigned entries, struct io_uring_params *p,
                           struct io_uring_params *params)
{
    // p为内核空间地址数据
    // params为用户空间地址数据

    struct io_ring_ctx *ctx;
    struct file *file;
    int ret;

    // 此处检查提交队列SQ大小是否会超过最大限制IORING_MAX_ENTRIES(0x8000)
    if (entries > IORING_MAX_ENTRIES) {
        if (!(p->flags & IORING_SETUP_CLAMP))
            return -EINVAL;
        entries = IORING_MAX_ENTRIES;
    }

    // 保证队列容量是2的幂次方
    p->sq_entries = roundup_pow_of_two(entries);
    // 完成队列CQ是提交队列容量的两倍
    p->cq_entries = 2 * p->sq_entries;

    // 初始化io_ring_ctx对象
    ctx = io_ring_ctx_alloc(p);
    if (!ctx)
        return -ENOMEM;
    ctx->compat = in_compat_syscall();
    if (!capable(CAP_IPC_LOCK))
        ctx->user = get_uid(current_user());

    mmgrab(current->mm);
    ctx->mm_account = current->mm;

    // 申请ctx->rings和ctx->sq_sqes内存
    ret = io_allocate_scq_urings(ctx, p);
    if (ret)
        goto err;

    // 设置了轮询后创建轮询线程函数, 由于参数未设置, 不在展开
    ret = io_sq_offload_create(ctx, p);
    if (ret)
        goto err;

    ret = io_rsrc_node_switch_start(ctx);
    if (ret)
        goto err;
    io_rsrc_node_switch(ctx, NULL);

    // 保存结构体成员偏移地址
    // 用于用户空间创建内存映射时获取到偏移地址, 将用户空间结构体内部变量和内核空间结构体内部变量一一对应
    // liburing io_uring_setup_ring_pointers
    memset(&p->sq_off, 0, sizeof(p->sq_off));
    /**
     * 由于代码中设置了按照cacheline字节对齐
     * 所以p->sq_off.tail = 64, 即虽然head是4字节, 但为了防止`错误共享`, 让其占据一个cacheline大小
     * cacheline一般为64, 也可能是128, 博主PC是64的
     * 错误共享知识可查看
     * https://github.com/TonyBeen/study/blob/master/false_sharing/false_sharing.cc
     * 
     * struct io_uring {
     *     u32 head __attribute__((__aligned__(64)));
     *     u32 tail __attribute__((__aligned__(64)));
     * };
     */
    p->sq_off.head = offsetof(struct io_rings, sq.head);                    // 0
    p->sq_off.tail = offsetof(struct io_rings, sq.tail);                    // 64
    p->sq_off.ring_mask = offsetof(struct io_rings, sq_ring_mask);          // 256
    p->sq_off.ring_entries = offsetof(struct io_rings, sq_ring_entries);    // 264
    p->sq_off.flags = offsetof(struct io_rings, sq_flags);                  // 276
    p->sq_off.dropped = offsetof(struct io_rings, sq_dropped);              // 272
    /**
     * 具体细节在函数io_allocate_scq_urings内部, 如下
     * 下面介绍如何计算得到2368的(代码在rings_size中)
     *
     * sizeof(struct io_rings) = 320 (64字节对齐结果)
     * sizeof(struct io_uring_cqe) = 16
     * cq_entries = 128
     * 
     * p->sq_off.array = cq_entries * sizeof(struct io_uring_cqe) + sizeof(struct io_rings)
     * 得到结果满足在cacheline字节边界上, 不满足的情况下不会是2368
     * 
     */
    p->sq_off.array = (char *)ctx->sq_array - (char *)ctx->rings;           // 2368

    memset(&p->cq_off, 0, sizeof(p->cq_off));
    p->cq_off.head = offsetof(struct io_rings, cq.head);
    p->cq_off.tail = offsetof(struct io_rings, cq.tail);
    p->cq_off.ring_mask = offsetof(struct io_rings, cq_ring_mask);
    p->cq_off.ring_entries = offsetof(struct io_rings, cq_ring_entries);
    p->cq_off.overflow = offsetof(struct io_rings, cq_overflow);
    p->cq_off.cqes = offsetof(struct io_rings, cqes);
    p->cq_off.flags = offsetof(struct io_rings, cq_flags);

    p->features = IORING_FEAT_SINGLE_MMAP | IORING_FEAT_NODROP |
                  IORING_FEAT_SUBMIT_STABLE | IORING_FEAT_RW_CUR_POS |
                  IORING_FEAT_CUR_PERSONALITY | IORING_FEAT_FAST_POLL |
                  IORING_FEAT_POLL_32BITS | IORING_FEAT_SQPOLL_NONFIXED |
                  IORING_FEAT_EXT_ARG | IORING_FEAT_NATIVE_WORKERS |
                  IORING_FEAT_RSRC_TAGS;

    // 拷贝到用户空间
    if (copy_to_user(params, p, sizeof(*p))) {
        ret = -EFAULT;
        goto err;
    }

    // 以下是创建一个匿名 inode, 并将文件描述符返回
    // 可通过ll /proc/{pid}/fd/ 查看到io_uring的文件描述 3 -> 'anon_inode:[io_uring]'
    // 用户可通过返回文件描述符进行mmap, 以获取对SQ/CQ的内存访问
    file = io_uring_get_file(ctx);
    if (IS_ERR(file)) {
        ret = PTR_ERR(file);
        goto err;
    }

    /*
     * Install ring fd as the very last thing, so we don't risk someone
     * having closed it before we finish setup
     */
    ret = io_uring_install_fd(ctx, file);
    if (ret < 0) {
        /* fput will clean it up */
        fput(file);
        return ret;
    }

    trace_io_uring_create(ret, ctx, p->sq_entries, p->cq_entries, p->flags);
    return ret;
err:
    io_ring_ctx_wait_and_kill(ctx);
    return ret;
}
static int io_allocate_scq_urings(struct io_ring_ctx *ctx,
                                  struct io_uring_params *p)
{
    struct io_rings *rings;
    size_t size, sq_array_offset;

    /* make sure these are sane, as we already accounted them */
    ctx->sq_entries = p->sq_entries;
    ctx->cq_entries = p->cq_entries;

    // rings_size计算的是一块存储struct io_rings + struct io_uring_cqe + sq_array的连续内存
    // sq_array是ctx->sq_sqes的索引数组, 内存大小 = p->sq_entries * sizeof(u32) = 256
    // sq_array_offset = 2368
    // size = 2368 + 256 = 2624
    size = rings_size(p->sq_entries, p->cq_entries, &sq_array_offset);
    if (size == SIZE_MAX)
        return -EOVERFLOW;

    rings = io_mem_alloc(size);
    if (!rings)
        return -ENOMEM;

    ctx->rings = rings;
    ctx->sq_array = (u32 *)((char *)rings + sq_array_offset);
    rings->sq_ring_mask = p->sq_entries - 1;
    rings->cq_ring_mask = p->cq_entries - 1;
    rings->sq_ring_entries = p->sq_entries;
    rings->cq_ring_entries = p->cq_entries;

    size = array_size(sizeof(struct io_uring_sqe), p->sq_entries);
    if (size == SIZE_MAX) {
        io_mem_free(ctx->rings);
        ctx->rings = NULL;
        return -EOVERFLOW;
    }

    ctx->sq_sqes = io_mem_alloc(size);
    if (!ctx->sq_sqes) {
        io_mem_free(ctx->rings);
        ctx->rings = NULL;
        return -ENOMEM;
    }

    return 0;
}

返回用户空间

int __io_uring_queue_init_params(unsigned entries, struct io_uring *ring,
                                 struct io_uring_params *p, void *buf,
                                 size_t buf_size)
{
    int fd, ret = 0;
    unsigned *sq_array;
    unsigned sq_entries, index;

    memset(ring, 0, sizeof(*ring));

    // 省去一些无法走到的代码
    fd = __sys_io_uring_setup(entries, p);
    if (fd < 0) {
        return fd;
    }

    // ------> 从此处执行 <------
    // 未设置IORING_SETUP_NO_MMAP(不使用内存映射)标志
    if (!(p->flags & IORING_SETUP_NO_MMAP)) {
        // io_uring_queue_mmap透传, 直接看io_uring_mmap
        ret = io_uring_queue_mmap(fd, p, ring);
        if (ret) {
            __sys_close(fd);
            return ret;
        }
    }

    return ret;
}
static int io_uring_mmap(int fd, struct io_uring_params *p,
                         struct io_uring_sq *sq, struct io_uring_cq *cq)
{
    size_t size;
    int ret;

    size = sizeof(struct io_uring_cqe); // 16

    // 通过gdb查看
    /**
     * p->sq_off.array = 2368
     * p->sq_entries = 64
     * 
     * p->cq_off.cqes = 320 // 固定320
     * p->cq_entries = 128
     * 
     * 计算出结果
     * sq->ring_sz = 2624
     * cq->ring_sz = 2368
     */
    sq->ring_sz = p->sq_off.array + p->sq_entries * sizeof(unsigned);
    cq->ring_sz = p->cq_off.cqes + p->cq_entries * size;

    // fs/io_uring.c line:10328 会设置IORING_FEAT_SINGLE_MMAP标志
    if (p->features & IORING_FEAT_SINGLE_MMAP) {
        if (cq->ring_sz > sq->ring_sz)
            sq->ring_sz = cq->ring_sz;
        cq->ring_sz = sq->ring_sz;
    }
    /**
     * sq->ring_sz = 2624
     * cq->ring_sz = 2624
     */

    // 创建与内核结构体io_rings的内存映射
    sq->ring_ptr = __sys_mmap(0, sq->ring_sz, PROT_READ | PROT_WRITE,
                  MAP_SHARED | MAP_POPULATE, fd,
                  IORING_OFF_SQ_RING);
    if (IS_ERR(sq->ring_ptr))
        return PTR_ERR(sq->ring_ptr);

    // 共用一份数据
    if (p->features & IORING_FEAT_SINGLE_MMAP) {
        cq->ring_ptr = sq->ring_ptr;
    }

    size = sizeof(struct io_uring_sqe); // 64
    // 创建提交队列的内存映射
    // 此结构体数据用于给用户填充数据, 如通过io_uring_get_sqe获取一个结构体
    // 并通过io_uring_prep_read填充此结构体, 最后通过io_uring_submit提交
    // NOTE 需要注意的是, 通过io_uring_prep_read设置的数据必须保证内存生命周期在完成后
    sq->sqes = __sys_mmap(0, size * p->sq_entries, PROT_READ | PROT_WRITE,
                  MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQES);

    // 初始化提交队列和完成队列
    io_uring_setup_ring_pointers(p, sq, cq);
    return 0;
}
<think> 我们遇到了一个编译错误,提示在编译liburing时,io_uring_register.c文件中错误:'MAP_HUGE_2MB'未声明(首次在该函数中使用);你是想说'MAP_HUGETLB'吗? 这个错误通常是因为在OpenWrt环境中,编译时使用的内核头文件可能不包含定义`MAP_HUGE_2MB`的内核版本。`MAP_HUGE_2MB`是一个宏,用于在mmap调用中请求2MB的大页映射。这个宏定义在Linux内核头文件中(通常是<linux/mman.h>或<sys/mman.h>),但在较旧的内核版本中可能没有定义。 解决方案通常有以下几种: 1. **升级OpenWrt的内核版本**:如果你的OpenWrt版本较旧,可能内核不支持`MAP_HUGE_2MB`宏。尝试更新到更新的OpenWrt版本,该版本可能使用了支持该宏的内核。 2. **修改liburing源码**:如果升级内核不可行,可以尝试修改liburing源码,将`MAP_HUGE_2MB`替换为对应的数值。根据Linux内核源码,`MAP_HUGE_2MB`实际上是`(21 << MAP_HUGE_SHIFT)`,而`MAP_HUGE_SHIFT`通常是26。因此,数值为`0x4000000`(即21<<26)。但是,注意这个数值可能因架构内核版本而异。 在io_uring_register.c中,找到报错的行(329行),将`MAP_HUGE_2MB`替换为数值`(21 << 26)`。但是,请注意,这种方法不够通用,因为不同的架构可能映射不同。更好的方法是检查系统中是否定义了`MAP_HUGE_2MB`,如果没有,则用数值替代。但是,由于是编译错误,我们可以在条件编译中处理。 3. **在OpenWrt中打补丁**:为liburing包创建一个补丁,在编译前应用。补丁内容可以是检查是否定义了`MAP_HUGE_2MB`,如果没有,则定义它(或者使用数值替代)。或者,直接修改源码文件。 4. **禁用相关的测试**:如果上述方法都不可行,可以尝试禁用这个测试。在liburing的编译配置中,可能可以跳过这个测试。但是,这可能会影响io_uring功能的完整性。 具体步骤: - 打开文件`io_uring_register.c`,找到报错的代码行(大约是329行),内容类似于: ```c MAP_PRIVATE | MAP_HUGETLB | MAP_HUGE_2MB | MAP_ANONYMOUS, ``` - 修改为: ```c MAP_PRIVATE | MAP_HUGETLB | (21 << 26) | MAP_ANONYMOUS, ``` 注意:`21<<26`是`MAP_HUGE_2MB`的标准值,但在某些架构上可能不同。建议查看当前编译环境的内核头文件(如果有)中`MAP_HUGE_2MB`的定义。如果OpenWrt的内核头文件中有类似的定义,也可以直接引用正确的宏。 另外,我们可以通过检查宏定义是否存在来条件编译。例如: ```c #ifndef MAP_HUGE_2MB #define MAP_HUGE_2MB (21 << 26) #endif ``` 然后将这个定义放在文件开头,或者放在包含头文件之后。 但是,注意这个测试文件(io_uring_register.c)可能只是用于测试,而不是核心库的一部分。因此,临时修改这个文件以通过编译可能是安全的。 **操作建议**: 1. 尝试在liburing的Makefile中查找是否有选项可以跳过这个测试。有时测试代码并不需要编译进库,可能只是用于单元测试。 2. 如果上述替换数值的方法不可行,也可以尝试将`MAP_HUGE_2MB`替换为0,因为`MAP_HUGE_2MB`实际上是大页的标志,如果系统不支持,可能测试会失败,但库本身可能还能编译。但是,这可能会导致测试失败,从而编译终止(因为make报告了错误)。所以,为了避免编译终止,可以尝试禁用这个测试。 3. 如果liburing的版本较新,而OpenWrt提供的是较旧的版本,可以考虑更新liburing的版本,因为新版本可能已经修复了这个问题。 4. 在OpenWrt的编译环境中,可以通过修改liburing的包定义(在OpenWrt的package目录下)来添加补丁。具体步骤如下: - 找到OpenWrt中liburing的包目录(例如:package/libs/liburing)。 - 在patches目录下创建一个补丁文件,比如001-fix-MAP_HUGE_2MB-undeclared.patch。 - 补丁内容示例: ```diff --- a/test/io_uring_register.c +++ b/test/io_uring_register.c @@ -326,7 +326,7 @@ static int test_iovec_size(struct io_uring *ring) void *buf; buf = mmap(0, 2*1024*1024, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_HUGETLB | MAP_HUGE_2MB | MAP_ANONYMOUS, + MAP_PRIVATE | MAP_HUGETLB | (21 << 26) | MAP_ANONYMOUS, -1, 0); if (buf == MAP_FAILED) return 1; ``` 然后重新编译。 总结:推荐使用补丁的方式修改源码,将`MAP_HUGE_2MB`替换为数值`(21 << 26)`,以便在缺少宏定义的情况下也能编译通过。 §§相关问题§§ 1. 如何在OpenWrt中为软件包创建应用补丁? 2. 除了修改源码,还有其他方法解决OpenWrt编译中遇到的宏未定义错误吗? 3. 为什么OpenWrt编译环境中会缺少MAP_HUGE_2MB宏定义?如何检查内核是否支持大页映射? 4. 在liburing的编译过程中,如何跳过测试代码的编译? 5. 升级OpenWrt的内核版本有哪些步骤注意事项? </think> 在OpenWrt编译liburing时遇到`MAP_HUGE_2MB`未定义错误,根本原因是当前内核头文件缺少对该宏的支持(通常出现在较旧内核版本中)。以下是解决方案: ### **解决步骤** 1. **检查内核版本** 运行 `uname -r` 确认内核版本。`MAP_HUGE_2MB` 需要 **Linux 4.12+** 内核支持。若版本过低,需升级OpenWrt或内核。 2. **修改源码(推荐临时方案)** 编辑报错文件 `io_uring_register.c`: - 定位到329行(或附近): ```c MAP_PRIVATE | MAP_HUGETLB | MAP_HUGE_2MB | MAP_ANONYMOUS, ``` - 替换为数值定义(`21 << 26` 对应2MB大页): ```c MAP_PRIVATE | MAP_HUGETLB | (21 << 26) | MAP_ANONYMOUS, ``` 或添加条件编译(文件开头插入): ```c #ifndef MAP_HUGE_2MB #define MAP_HUGE_2MB (21 << 26) #endif ``` 3. **更新内核头文件(长期方案)** - 升级OpenWrt至最新版(支持Linux 5.10+): ```bash ./scripts/feeds update ./scripts/feeds install liburing make menuconfig # 启用liburing make -j$(nproc) ``` - 或单独升级内核: 修改OpenWrt的`target/linux/`目录下对应架构的内核配置。 4. **跳过测试用例(应急方案)** 若无需测试,修改Makefile移除测试编译(查找并注释类似`$(MAKE) -C test`的行)。 --- ### **根本原因** - `MAP_HUGE_2MB` 是Linux内核的**大页内存映射宏**,定义在`<linux/mman.h>`。 - OpenWrt旧版本的内核头文件(如Linux 4.4)未包含此宏,需升级至Linux 4.12+。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值