Rust in Everything
基于 embassy-executor 0.10.0

时间

Timer::after_millis(100).await 要回答三个问题:现在几点、100 ms 是多少个单位、到点了谁来叫醒任务。embassy 把这三件事拆进四个小 crate:

crate版本(tag).rs 行数回答什么
embassy-time0.5.1(embassy-time-v0.5.1)1615(含 270 行日志宏 fmt.rs)用户 API:Instant、Duration、Timer、Ticker、with_timeout、Delay;另带 std、mock、wasm 三个驱动
embassy-time-driver0.2.2(embassy-time-driver-v0.2.2)667(其中 488 行是脚本生成的 tick.rs)驱动接口:now、schedule_wake 两个函数,TICK_HZ 常量
embassy-time-queue-utils0.3.2(embassy-time-queue-utils-v0.3.2)351给驱动用的定时器队列:集成队列和通用队列
embassy-executor-timer-queue0.1.0(无 tag,commit a416aac9c)104执行器在每个任务头里留的那块队列项内存

硬件那一半在 HAL 里,本卷读 embassy-stm32 0.6.0 的 src/time_driver/(739 行)。

  1. Instant 与 Duration:两个类型都只是一个 u64 tick;TICK_HZ 由 161 个 tick-hz-* feature 之一选出,不选就是 1 MHz,选两个编不过;换算先用 GCD 约分;Duration::from_* 向上取整,Instant::from_* 和所有 as_* 向下取整;as_nanos 在 32768 Hz 下 9 年就溢出,release 里悄悄算错。
  2. 时间驱动:驱动接口在 0.2.0 收成两个函数,靠链接期符号 _embassy_time_now 找到实现,没有驱动是链接错误,两个驱动在 rust-lld 下报重复符号,macOS 的 ld 却可能悄悄选一个;std 驱动用一个闹钟线程,mock 驱动手动推进时间;stm32 的 TIM 驱动用 32 位周期数加 16 位计数器拼时间,QEMU 里 TIM 没有模型,时间停在 0;模板里的 SysTick 驱动每毫秒中断一次,关中断期间丢 tick。
  3. 定时器队列:集成队列把链表节点放进任务头,从 waker 的 vtable 认出 embassy 任务;通用队列是一个定长 heapless::Vec,满了就提前叫醒一个,std 上 9 个定时器挤 8 个槽时,半秒内任务被 poll 近 400 万次;一个永不到期的定时器曾让同一任务之后的定时器全部失效。
  4. Timer、Ticker 与超时:Timer 第一次 poll 一定返回 Pending;with_timeout 在调用时就定下截止时刻;Ticker 错过的 tick 一次性补上,std 和 QEMU 上都是 3 个 tick 同时到;block_for 是忙等。
flowchart TB
  user["Timer Ticker"] --> time["embassy-time"]
  time --> drv["embassy-time-driver"]
  drv -.->|链接期符号| hal["HAL 时间驱动"]
  hal --> qu["queue-utils Queue"]
  qu --> etq["executor-timer-queue"]
  exec["embassy-executor"] --> etq
  etq -.->|链接期符号| exec

图里两条虚线都是链接期符号:embassy-time 调 _embassy_time_now / _embassy_time_schedule_wake,由 HAL 用 time_driver_impl! 定义;集成队列调 __embassy_time_queue_item_from_waker,由 embassy-executor 定义。所以 embassy-time 不依赖执行器,执行器也不依赖 embassy-time,两边经两个小 crate 接上。执行器在任务头里怎么摆这块内存、waker 怎么指回任务,见任务存储和状态与运行队列;一次 Timer::after 从头到尾在 QEMU 上的打点,见一次 Timer::after。和 tokio 对照:tokio 的定时器是运行时里一个分层时间轮,跟 IO 驱动一起 park(见定时器);embassy 没有运行时,时间轮的位置换成了 HAL 驱动里的一条链表和一个硬件比较通道。

💬 关联讨论

发起讨论
加载中...