IO 驱动
调度器那一部分讲了任务怎么被取出来 poll。这一部分讲任务在 IO 上返回 Pending 之后,是谁、在什么时候把它叫醒的。
源码主要在 runtime/io/,加上 io-uring 部分和测试约 2000 行:driver.rs 是反应器本体,scheduled_io.rs 是每个 socket 一份的就绪状态,registration.rs 和 io/poll_evented.rs 是 TcpStream 这类类型和反应器之间的胶水。底下的系统调用全交给 mio:Linux 上是 epoll,macOS 上是 kqueue,Windows 上是 IOCP 加 AFD。
- 反应器:
Driver和Handle各拿着什么;turn()一次做哪几步;token 怎么变回ScheduledIo的指针;注销的ScheduledIo为什么要等到下一次turn()才释放;忙的 worker 多久查一次 IO。 - ScheduledIo 与就绪状态:一个
usize里装着 16 位就绪位、15 位 tick 和 1 位关闭标志;tick 防的是哪一种竞争;读、写各一个 waker 槽,其余等待者放进侵入式链表。 - 从 TcpStream::read 到挂起:顺着真实代码走一遍 IO 主线,配一个能看到
Pending、wake、再次 poll 顺序的实验。 - (unstable)io-uring:它要解决什么,1.54.1 做到了哪一步。
整条 IO 主线:
flowchart TB
read["TcpStream::poll_read"] --> ready{"readiness 里有可读位?"}
ready -->|有| sys["read 系统调用"]
sys -->|读到数据| done[返回 Ready]
sys -->|WouldBlock| clear["clear_readiness"]
clear --> ready
ready -->|没有| store["waker 存进 reader 槽"]
store --> pending[返回 Pending]
pending --> park[worker 空闲,park 在驱动上]
park --> poll["mio poll 返回事件"]
poll --> set["set_readiness:tick 加 1"]
set --> wake["wake:任务进队列"]
wake --> read
图里上半部分在任务的线程上,属于任务自己的 poll;下半部分在拿到驱动的那个 worker 上,属于驱动的 turn()。两边只通过 ScheduledIo 里的一个原子变量和一把小锁交换信息。
前置阅读:任务被唤醒后进哪个队列,见 worker 主循环;worker 怎么抢到驱动、睡在 epoll_wait 上,见 任务窃取与空闲 和 驱动栈与 park。
💬 关联讨论
发起讨论加载中...