Skip to the content.

buffered write

为什么 iouring buffered write 没有对称的回调机制

io_async_buf_func 是 read 专用的回调机制,用于处理异步缓冲区读取(buffered read)时的页缓存未命中。 对于 write,没有对称的回调机制。我认为根本原因在于,write 的写是内核中的 page wirteback 机制来做的, 想做回调也可以,但是太复杂了。

io_async_buf_func 的作用(仅用于 Read)

static int io_async_buf_func(struct wait_queue_entry *wait, unsigned mode,
                             int sync, void *arg)
{
    struct wait_page_queue *wpq;
    struct io_kiocb *req = wait->private;
    struct wait_page_key *key = arg;

    wpq = container_of(wait, struct wait_page_queue, wait);

    if (!wake_page_match(wpq, key))      // 检查是否是目标 page
        return 0;

    rw->kiocb.ki_flags &= ~IOCB_WAITQ;   // 清除等待标志
    list_del_init(&wait->entry);         // 从 waitqueue 移除
    io_req_task_queue(req);              // ← 调度 task_work 重试读操作
    return 1;
}

触发条件(io_rw_should_retry):

static bool io_rw_should_retry(struct io_kiocb *req)
{
    // 1. 仅限 buffered IO(非 DIRECT)
    if (kiocb->ki_flags & (IOCB_DIRECT | IOCB_HIPRI))
        return false;

    // 2. 文件系统必须支持 FOP_BUFFER_RASYNC
    if (!(req->file->f_op->fop_flags & FOP_BUFFER_RASYNC))
        return false;

    // 3. 设置回调并启用 IOCB_WAITQ 标志
    wait->wait.func = io_async_buf_func;
    kiocb->ki_flags |= IOCB_WAITQ;
    kiocb->ki_waitq = wait;
}

要记住,buffer write 的写回在这里:

@[
        nvme_queue_rqs+5
        blk_mq_dispatch_queue_requests+359
        blk_mq_flush_plug_list+120
        blk_add_rq_to_plug+170
        blk_mq_submit_bio+1556
        __submit_bio+116
        __submit_bio_noacct+144
        iomap_ioend_writeback_submit+92
        iomap_add_to_ioend+306
        xfs_writeback_range+93
        iomap_writeback_folio+503
        iomap_writepages+87
        xfs_vm_writepages+145
        do_writepages+211
        __writeback_single_inode+65
        writeback_sb_inodes+535
        __writeback_inodes_wb+76
        wb_writeback+691
        wb_workfn+691
        process_one_work+402
        worker_thread+602
        kthread+252
        ret_from_fork+244
        ret_from_fork_asm+26
]: 699

io_uring 的 buffer write 的 worker 上限

核心问题:Hash 序列化

if (req->file && (req->flags & REQ_F_ISREG)) {
    bool should_hash = def->hash_reg_file;  // ← 普通文件写需要 hash

    if (should_hash || (ctx->flags & IORING_SETUP_IOPOLL))
        io_wq_hash_work(&req->work, file_inode(req->file));  // ← 按 inode has
h
}

void io_wq_hash_work(struct io_wq_work *work, void *val)
{
    bit = hash_ptr(val, IO_WQ_HASH_ORDER);  // 同一文件的所有请求 hash 相同
    atomic_or(IO_WQ_WORK_HASHED | (bit << IO_WQ_HASH_SHIFT), &work->flags);
}

为什么只有 2 个 Worker

hash = __io_get_work_hash(work_flags);
/* hashed, can run if not already running */
if (!test_and_set_bit(hash, &wq->hash->map)) {  // ← 原子操作检查
    // 可以执行(第一个 worker)
} else {
    // hash 冲突,跳过(其他 worker 无法并行处理同一文件)
    stall_hash = hash;
}

关键限制: • 同一 inode 的多个写请求被 hash 到 同一个 bucket • test_and_set_bit 确保同一时间只能有一个 worker处理该文件的请求 • 其他 worker 即使空闲,看到 bit 被设置也会跳过(IO_ACCT_STALLED_BIT)

实际测试,没那么简单,即便这个测试,也不是稳定的有 iou-wrk 出现:

fio --name=test --ioengine=io_uring --direct=0 --iodepth=1024 --rw=randwrite --bs=4k --directory=$HOME/hack --nrfiles=16 --size=40G --runtime=100 --time_based

pstree -t -p  $(pgrep fio | tail -1)

FOP_BUFFER_WASYNC 的影响

FOP_BUFFER_WASYNC 是文件系统向 io_uring 声明:"我支持非阻塞(NOWAIT)的 buffered write"。

核心作用

// io_uring/rw.c:1150-1156
if (!(kiocb->ki_flags & IOCB_DIRECT) &&           // buffered IO
    !(req->file->f_op->fop_flags & FOP_BUFFER_WASYNC) &&  // 不支持 WASYNC
    (req->flags & REQ_F_ISREG))                   // 普通文件
    goto ret_eagain;                              // 直接进 io-wq,不尝试非
阻塞

 文件系统     支持 WASYNC   行为差异
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 XFS, Btrfs   ✓             尝试 IOCB_NOWAIT 写入,失败才进 io-wq
 ext4, f2fs   ✗             直接进 io-wq,不尝试非阻塞

为什么需要这个标志

Buffered write 通常需要:

1. 查找/分配 page cache 页
2. 等待页面锁定
3. 可能触发写回(writeback)

这些操作都可能阻塞。如果文件系统不能保证在资源不可用时快速返回 -EAGAIN,就
不应该设置 FOP_BUFFER_WASYNC。

// fs/read_write.c:1750-1753
if ((iocb->ki_flags & IOCB_NOWAIT) &&
    !((iocb->ki_flags & IOCB_DIRECT) ||
      (file->f_op->fop_flags & FOP_BUFFER_WASYNC)))
    return -EINVAL;  // 如果不支持,直接拒绝 NOWAIT 请求

实际效果对比

支持 WASYNC (XFS/Btrfs)

用户提交 buffered write
        │
        ▼
┌───────────────┐
│ 尝试 NOWAIT   │  ← 在提交线程快速尝试
│ 写入          │
└───────┬───────┘
        │
   ┌────┴────┐
   ▼         ▼
 成功      -EAGAIN
   │         │
   ▼         ▼
 完成    进 io-wq

不支持 WASYNC (ext4/f2fs)

用户提交 buffered write
        │
        ▼
┌───────────────┐
│ 直接返回      │  ← 不尝试,避免不确定的阻塞
│ -EAGAIN       │
└───────┬───────┘
        │
        ▼
     进 io-wq
   (worker 线程处理)

关键区别

 特性           支持 WASYNC                不支持 WASYNC
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 延迟           可能更低(同步路径命中)   总是 io-wq 开销
 CPU 效率       提交线程可能自旋/重试      直接交给 worker
 文件系统要求   必须正确处理 IOCB_NOWAIT   无特殊要求

总结

FOP_BUFFER_WASYNC 是文件系统的能力声明:

▌ "我可以在 buffered write 时处理 IOCB_NOWAIT 标志,如果无法立即完成,我
▌ 会返回 -EAGAIN 而不会阻塞。"

这让 io_uring 有机会在提交线程中快速完成 buffered write,避免进入 io-wq 的
上下文切换开销。对于不支持此标志的文件系统,io_uring 保守地直接交给 io-wq
处理,确保不会阻塞提交路径。

如果我们使用 ext4 测试,就可以发现所以的 io 都是串行的

@retval[io_write, 0]: 10391822
@retval[io_write, -11]: 10391823

iouring buffered write 会产生 io-wq

对于 aio direct=0 测试结果 11k,而 io_uring direct=0 可以达到 180k 的性能。 很显然,__do_sys_io_uring_enter 只是提交,而存在另外一个线程负责等待硬件响应。

通过 syscall ,将任务放到 io workqueue 上:

   - 44.41% entry_SYSCALL_64_after_hwframe
      - 44.30% do_syscall_64
         - 23.83% __do_sys_io_uring_enter
            - 16.64% io_submit_sqes
               - 12.90% io_queue_async
                  - 12.59% io_queue_iowq
                     - 10.88% io_wq_enqueue
                        - 9.66% io_wq_activate_free_worker
                           - 9.26% try_to_wake_up
                              - 7.02% __task_rq_lock
                                 - 6.77% _raw_spin_lock
                                      6.58% native_queued_spin_lock_slowpath
                                0.65% ttwu_queue_wakelist
                                0.52% select_task_rq
                     - 1.59% io_prep_async_link
                          1.07% io_wq_hash_work
                          0.50% io_prep_async_work
               - 1.90% io_issue_sqe
                  - 0.94% io_file_get_normal
                       0.85% fget
                    0.74% io_write
               - 1.24% io_prep_rw
                    1.06% io_prep_rw_setup
            - 3.02% __io_run_local_work
               - 1.71% __io_submit_flush_completions
                  - 1.60% io_free_batch_list
                     - 0.72% io_clean_op
                          0.55% kfree
                       0.67% fput
                 0.58% llist_reverse_order
            - 1.51% io_cqring_wait
               - 0.81% schedule

然后让内核中的 io workqueue 来完成工作:

   - io_wq_worker
      - 36.46% io_worker_handle_work
         - 34.64% io_wq_submit_work
            - 34.45% io_issue_sqe
               - 31.33% io_write
                  - 29.08% ext4_buffered_write_iter
                     - 26.20% generic_perform_write
                        - 15.67% ext4_da_write_begin
                           - 11.04% __filemap_get_folio
                              - 6.25% filemap_add_folio
                                 - 2.62% __filemap_add_folio
                                 - 1.63% __folio_batch_add_and_move
                                 - 1.57% __mem_cgroup_charge
                              - 3.27% folio_alloc_noprof
                              - 0.94% filemap_get_entry
                                   0.77% xas_load
                           - 4.06% ext4_block_write_begin
                              - 2.36% create_empty_buffers
                                 - 1.99% folio_alloc_buffers
                              - 1.09% ext4_da_get_block_prep
                                 - 0.67% ext4_da_map_blocks.constprop.0
                        - 4.81% copy_page_from_iter_atomic
                             4.12% rep_movs_alternative
                        - 3.44% ext4_da_do_write_end
                           - 3.10% block_write_end
                              - 3.02% __block_commit_write
                          1.34% balance_dirty_pages_ratelimited_flags
                       0.81% file_modified
                    0.63% rw_verify_area
               - 2.37% __io_req_task_work_add
                  - 0.65% try_to_wake_up
                 0.52% kiocb_done

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。