使用内核侧轮询时,liburing与内核如何避免唤醒丢失问题?
io_uring内核侧轮询唤醒丢失问题解答
结论
你描述的场景确实会触发唤醒丢失,这是典型的跨CPU内存观测顺序不一致导致的竞态问题,也是io_uring实现中必须靠内存屏障规避的核心风险点。
场景分析
先明确两边的操作语义:
- 用户线程操作:
- A:更新提交队列(SQ)的尾指针,对外发布新的SQE
- B:读取SQ的
IORING_SQ_NEED_WAKEUP标志,判断是否需要唤醒内核轮询线程
- 内核轮询线程操作:
- C:设置
IORING_SQ_NEED_WAKEUP标志,告知用户态后续提交新请求时需要唤醒自己 - D:读取SQ尾指针,判断是否有未处理的新请求
- C:设置
你提到的观测顺序等价于:
- 用户线程执行完A、B,且执行B时还未观测到C对标志位的修改,因此认为不需要唤醒内核
- 内核线程执行C、D,且执行D时还未观测到A对尾指针的修改,因此认为没有新请求需要处理,进入休眠状态
最终两边都没有后续动作,新提交的SQE永远不会被内核处理,即发生唤醒丢失。
现有实现的规避逻辑
io_uring的原生实现中已经通过强制内存屏障避免了该问题:
- 内核侧在执行完C(设置NEED_WAKEUP标志)之后、执行D(读取SQ尾指针)之前,会插入全内存屏障
smp_mb(),保证标志位的修改对所有CPU可见之后,才会去读尾指针 - 用户态标准库在执行完A(更新SQ尾指针)之后、执行B(读取NEED_WAKEUP标志)之前,也需要插入对应的内存屏障,保证尾指针的修改对外发布之后,才会去判断是否需要唤醒
只要两边严格遵循屏障约束,就不会出现你描述的观测顺序不一致的情况。
内容的提问来源于stack exchange,提问作者Zhipeng Teng
相关产品推荐
相关产品推荐

