Skip to the content.

iouring 实现分析

tracepoint 的获取基本的

io_uring_cqe_overflow
io_uring_cqring_wait
io_uring_defer
io_uring_fail_link
io_uring_file_get
io_uring_link
io_uring_poll_arm
io_uring_req_failed
io_uring_short_write

# 不用说了,最重要的
io_uring_submit_req
io_uring_complete

# 也是重要的,但是不是高速路径
io_uring_create
io_uring_register

# io wq 相关
io_uring_queue_async_work
io_uring_task_add
io_uring_task_work_run
io_uring_local_work_run

关键源码

io_req_flags_t io_file_get_flags(struct file *file)
{
	io_req_flags_t res = 0;

	BUILD_BUG_ON(REQ_F_ISREG_BIT != REQ_F_SUPPORT_NOWAIT_BIT + 1);

	if (S_ISREG(file_inode(file)->i_mode))
		res |= REQ_F_ISREG;
	if ((file->f_flags & O_NONBLOCK) || (file->f_mode & FMODE_NOWAIT))
		res |= REQ_F_SUPPORT_NOWAIT;
	return res;
}
static bool io_file_supports_nowait(struct io_kiocb *req, __poll_t mask)
{
	/* If FMODE_NOWAIT is set for a file, we're golden */
	if (req->flags & REQ_F_SUPPORT_NOWAIT)
		return true;
	/* No FMODE_NOWAIT, if we can poll, check the status */
	if (io_file_can_poll(req)) {
		struct poll_table_struct pt = { ._key = mask };

		return vfs_poll(req->file, &pt) & mask;
	}
	/* No FMODE_NOWAIT support, and file isn't pollable. Tough luck. */
	return false;
}

trace_io_uring_complete 的调用路径分析

使用 code/src/c/iouring/op-read.c 测试, 也就是使用最简单的 io_uring_prep_read ,然后

/dev/zero

不支持 O_DIRECT ,如果使用 O_DIRECT 打开,会有报错我

@[
        __io_submit_flush_completions+357
        io_submit_sqes+332
        __do_sys_io_uring_enter+520
        do_syscall_64+132
        entry_SYSCALL_64_after_hwframe+118
]: 1

这种,提交就把事情干完了:

# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 14)               |  io_issue_sqe() {
 14)               |    arch_irq_work_raise() {
 14)               |      x2apic_send_IPI_self() {
 14)   0.737 us    |        irq_enter_rcu();
 14)   0.251 us    |        sched_core_idle_cpu();
 14)   0.197 us    |        raw_irqentry_exit_cond_resched();
 14)   7.014 us    |      }
 14)   7.868 us    |    }
 14)               |    io_file_get_normal() {
 14)               |      fget() {
 14)   0.194 us    |        __rcu_read_lock();
 14)   0.182 us    |        __rcu_read_unlock();
 14)   0.972 us    |      }
 14)   1.396 us    |    }
 14)               |    io_read() {
 14)               |      __io_read() {
 14)               |        io_rw_init_file() {
 14)   0.147 us    |          io_file_get_flags();
 14)   0.729 us    |        }
 14)   0.160 us    |        io_file_supports_nowait();
 14)               |        rw_verify_area() {
 14)   0.428 us    |          security_file_permission();
 14)   0.774 us    |        }
 14)               |        read_iter_zero() {
 14)   0.593 us    |          lock_mm_and_find_vma();
 14)   5.185 us    |          handle_mm_fault();
 14)   0.163 us    |          up_read();
 14)   0.142 us    |          raw_irqentry_exit_cond_resched();
 14)   8.045 us    |        }
 14)   2.530 us    |          arch_irq_work_raise();
 14) + 13.935 us   |      }
 14)               |      kiocb_done() {
 14)               |        io_req_io_end() {
 14)   0.211 us    |          __fsnotify_parent();
 14)   0.960 us    |        }
 14)   0.137 us    |        io_rw_recycle();
 14)   1.699 us    |      }
 14) + 16.250 us   |    }
 14) + 28.356 us   |  }

例如这种,所有的核心逻辑都是在一个函数中,根本不支持提交 + 异步完成的能力:

static ssize_t read_iter_zero(struct kiocb *iocb, struct iov_iter *iter)
{
	size_t written = 0;

	while (iov_iter_count(iter)) {
		size_t chunk = iov_iter_count(iter), n;

		if (chunk > PAGE_SIZE)
			chunk = PAGE_SIZE;	/* Just for latency reasons */
		n = iov_iter_zero(chunk, iter);
		if (!n && iov_iter_count(iter))
			return written ? written : -EFAULT;
		written += n;
		if (signal_pending(current))
			return written ? written : -ERESTARTSYS;
		if (!need_resched())
			continue;
		if (iocb->ki_flags & IOCB_NOWAIT)
			return written ? written : -EAGAIN;
		cond_resched();
	}
	return written;
}

所以,这种设备,就是在 io_uring_enter 的时候就会完成所有的功能:

@[
        read_iter_zero+5
        __io_read+188
        io_read+21
        __io_issue_sqe+58
        io_issue_sqe+57
        io_submit_sqes+274
        __do_sys_io_uring_enter+520
        do_syscall_64+132
        entry_SYSCALL_64_after_hwframe+118
]: 1

shmem

无论是否 O_DIRECT ,都是这个结果,因为 shmem 本质不支持 O_DIRECT , 所以使用 io-wq 来异步化。

@[
        io_req_complete_post+300
        io_issue_sqe+374
        io_wq_submit_work+188
        io_worker_handle_work+331
        io_wq_worker+222
        ret_from_fork+242
        ret_from_fork_asm+26
]: 1

通过 tracepoint 可以找到:

1498273.295 op-read.out/57888 io_uring:io_uring_queue_async_work(ctx: 0xffff88812e738800, req: 0xffff888106761500, opcode: 22, flags: 550834274304, work: 0xffff8881067615d8, op_str: "READ")

@[
        io_queue_iowq+193
        io_submit_sqes+711
        __do_sys_io_uring_enter+520
        do_syscall_64+132
        entry_SYSCALL_64_after_hwframe+118
]: 1

这个行为和 fsync 差不多了,可以自动转换

问题的本质在这里:

static inline void io_queue_sqe(struct io_kiocb *req, unsigned int extra_flags)
	__must_hold(&req->ctx->uring_lock)
{
	unsigned int issue_flags = IO_URING_F_NONBLOCK |
				   IO_URING_F_COMPLETE_DEFER | extra_flags;
	int ret;

	ret = io_issue_sqe(req, issue_flags);

	/*
	 * We async punt it if the file wasn't marked NOWAIT, or if the file
	 * doesn't support non-blocking read/write attempts
	 */
	if (unlikely(ret))
		io_queue_async(req, issue_flags, ret);
}
@[
        __io_read+1
        io_read+21
        __io_issue_sqe+58
        io_issue_sqe+57
        io_submit_sqes+274
        __do_sys_io_uring_enter+520
        do_syscall_64+132
        entry_SYSCALL_64_after_hwframe+118
]: 1
@[
        __io_read+1
        io_read+21
        __io_issue_sqe+58
        io_issue_sqe+57
        io_wq_submit_work+188
        io_worker_handle_work+331
        io_wq_worker+222
        ret_from_fork+242
        ret_from_fork_asm+26
]: 1
+ sudo bpftrace -e 'kretprobe:__io_read { printf("returned: %lx\n", retval); } interval:s:1000 { exit(); }'
Attaching 2 probes...
returned: fffffff5 (-11 也就是 EAGAIN)
returned: 26

第一次提交得到的是 EAGAIN,然后放到 io-wq 中执行:

 31)               |    __io_read() {
 31)               |      io_rw_init_file() {
 31)   0.183 us    |        io_file_get_flags();
 31)   0.521 us    |      }
 31)               |      rw_verify_area() {
 31)               |        security_file_permission() {
 31)   0.315 us    |          selinux_file_permission();
 31)   0.089 us    |          bpf_lsm_file_permission();
 31)   0.725 us    |        }
 31)   0.952 us    |      }
 31)               |      shmem_file_read_iter() {
 31)               |        shmem_get_folio_gfp() {
 31)   0.416 us    |          filemap_get_entry();
 31)   0.833 us    |        }
 31)   0.090 us    |        folio_unlock();
 31)   0.106 us    |        folio_mark_accessed();
 31)               |        lock_mm_and_find_vma() {
 31)   0.306 us    |          down_read_trylock();
 31)   0.501 us    |          find_vma();
 31)   1.432 us    |        }
 31)               |        handle_mm_fault() {
 31)   5.508 us    |          __handle_mm_fault();
 31)   0.089 us    |          __rcu_read_lock();
 31)   0.095 us    |          mem_cgroup_from_task();
 31)   0.130 us    |          count_memcg_events();
 31)   0.274 us    |          __rcu_read_unlock();
 31)   7.369 us    |        }
 31)   0.099 us    |        up_read();
 31)   0.324 us    |        raw_irqentry_exit_cond_resched();
 31)               |        touch_atime() {
 31)   0.536 us    |          atime_needs_update();
 31)   0.193 us    |          mnt_get_write_access();
 31)   1.121 us    |          generic_update_time();
 31)   0.160 us    |          mnt_put_write_access();
 31)   3.107 us    |        }
 31) + 15.782 us   |      }
 31) + 17.978 us   |    }
 31)               |    kiocb_done() {
 31)   0.117 us    |      io_req_io_end();
 31)   0.600 us    |    }
 31) + 25.544 us   |  }

是如何被唤醒的?

sudo bpftrace -e 'kprobe:io_worker_handle_work { @[kstack(bpftrace)] = count(); } interval:s:1000 { exit(); }'

@[
        io_worker_handle_work+1
        io_wq_worker+222
        ret_from_fork+242
        ret_from_fork_asm+26
]: 83

还是记错了,似乎在 io_worker_handle_work 中就是完成所有的工作的?

 12)               |  io_worker_handle_work() {
 12)   0.053 us    |    _raw_spin_lock();
 12)   0.048 us    |    _raw_spin_unlock();
 12)   0.048 us    |    _raw_spin_unlock();
 12)   0.052 us    |    _raw_spin_lock();
 12)   0.048 us    |    _raw_spin_unlock();
 12)   0.069 us    |    _raw_spin_lock();
 12)   0.048 us    |    _raw_spin_unlock();
 12)               |    io_wq_submit_work() {
 12)               |      io_issue_sqe() {
 12)               |        io_write() {
 12)   0.112 us    |          io_rw_init_file();
 12)   0.170 us    |          rw_verify_area();
 12)   7.905 us    |          ext4_file_write_iter();
 12)   0.202 us    |          kiocb_done();
 12)   9.669 us    |        }
 12)               |        io_req_complete_post() {
 12)   0.617 us    |          __io_req_task_work_add();
 12)   0.799 us    |        }
 12) + 10.760 us   |      }
 12) + 10.899 us   |    }
 12)   0.063 us    |    _raw_spin_lock();
 12)   0.057 us    |    _raw_spin_unlock();
 12)   0.286 us    |    io_wq_free_work();
 12)   0.063 us    |    _raw_spin_lock();
 12)   0.060 us    |    _raw_spin_unlock();
 12)   0.063 us    |    _raw_spin_lock_irq();
 12)   0.058 us    |    _raw_spin_unlock_irq();
 12) + 13.271 us   |  }

我靠,这个不是变成了纯串行的了

唤醒之后,还有什么工作要做?

也就是即便是被唤醒,也是有工作做的?

xfs

#define FILENAME “/home/martins3/data/testfile.txt”

如果是 O_DIRECT ,结果为:

@[
        __traceiter_io_uring_complete+58
        __io_submit_flush_completions+357
        ctx_flush_and_put+49
        io_handle_tw_list+177
        tctx_task_work_run+81
        tctx_task_work+58
        task_work_run+89
        io_run_task_work+78
        io_cqring_wait+923
        __do_sys_io_uring_enter+321
        do_syscall_64+132
        entry_SYSCALL_64_after_hwframe+118
]: 1

看上去,还是在同步等待啊 : IORING_ENTER_GETEVENTS (亲爹,这样的智商就别来看内核代码了,看看天线宝宝养养脑吧)

显然,是一个提交,然后一个等待

io_uring_enter(4, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(4, 0, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 0

如果不是 O_DIRECT 的,那么:

@[
        __traceiter_io_uring_complete+58
        __io_submit_flush_completions+357
        io_submit_sqes+332
        __do_sys_io_uring_enter+520
        do_syscall_64+132
        entry_SYSCALL_64_after_hwframe+118
]: 1

如果直接命中了 page cache ,那么:

io_uring_enter(4, 1, 0, 0, NULL, 8)     = 1

如果没有命中 page cache ,那么结果为,这个效果和

io_uring_enter(4, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(4, 0, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 0

O_DIRECT 的时候:

 16)               |    io_read() {
 16)               |      __io_read() {
 16)               |        io_rw_init_file() {
 16)   0.332 us    |          io_file_get_flags();
 16)   1.279 us    |        }
 16)   0.315 us    |        io_file_supports_nowait();
 16)               |        rw_verify_area() {
 16)   0.970 us    |          security_file_permission();
 16)   1.732 us    |        }
 16)               |        xfs_file_read_iter [xfs]() {
 16) + 81.150 us   |          xfs_file_dio_read [xfs]();
 16) + 87.432 us   |        }
 16) + 93.506 us   |      }
 16) + 94.402 us   |    }
 16) ! 115.179 us  |  }

不是 O_DIRECT 的时候:

  8)               |    io_read() {
  8)               |      __io_read() {
  8)               |        io_rw_init_file() {
  8)   0.447 us    |          io_file_get_flags();
  8)   1.868 us    |        }
  8)   0.502 us    |        io_file_supports_nowait();
  8)               |        rw_verify_area() {
  8)   1.304 us    |          security_file_permission();
  8)   2.418 us    |        }
  8)               |        xfs_file_read_iter [xfs]() {
  8) + 42.789 us   |          xfs_file_buffered_read [xfs]();
  8) + 47.868 us   |        }
  8)               |        xfs_file_read_iter [xfs]() {
  8)   2.844 us    |          xfs_file_buffered_read [xfs]();
  8)   3.840 us    |        }
  8) + 60.603 us   |      }
  8)               |      kiocb_done() {
  8)               |        io_req_io_end() {
  8)   0.565 us    |          __fsnotify_parent();
  8)   1.883 us    |        }
  8)   0.506 us    |        io_rw_recycle();
  8)   4.386 us    |      }
  8) + 67.107 us   |    }
  8) + 89.555 us   |  }

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