跳转至

异步网卡架构探索

日期:2026-08-08 标签:rust, async, network, nic, virtio, smoltcp, architecture, driver

来源:StarryOS net-k3 分支的分析文档、项目规范和已有实现。 当前处于异步网卡开发前期——同步轮询基线与测试矩阵已完成,异步 RX 队列计划待启动。

异步网卡长什么样

异步网卡驱动按四层分离,每层有独立执行上下文和所有权边界:

应用层 socket syscall(poll / select / read / write)
        │
  socket readiness 层  ← 多 waiter、overflow、close、error
        │                 smoltcp 单槽 waker → axpoll::PollSet
        │
  stack runner 层      ← 独立推进 smoltcp ingress/egress/maintenance/timer
        │                 device、software、timer 三类唤醒源
        │
  queue task 层        ← 有界 budget,RX reap/refill、TX submit/reclaim
        │                 packet slot、occupancy、drop reason
        │
  ISR 层               ← 读 cause → ack/mask → AtomicWaker::wake() → 返回
        │                 不分配内存、不阻塞、不 await、不碰 descriptor
        │
  ┌─────────────────────────────────────────┐
  │  平台层:probe、IRQ、DMA、MMIO          │
  │  QEMU VirtIO-MMIO / VF2 DWMAC           │
  └─────────────────────────────────────────┘

关键:平台层更换(MMIO → PCI → DWMAC)时,队列所有权、背压和完成语义不变。transport 适配只发生在 probe、IRQ 和 DMA 边界。

对比 UART:UART 是字节 ring + 单一 copier 任务。网卡是 descriptor ring + per-queue service task,以 DMA descriptor 为所有权单位。

用到的库

库 版本 角色
axtask::future 0.3.0-preview.2 异步调度器,block_on + poll_io
embassy-sync::AtomicWaker 0.6.2 ISR 中安全唤醒任务,O(1) 原子操作
smoltcp 0.13.1(本地 fork) TCP/IP 协议栈,本地化在 crates/smoltcp/
axnet 本地化 网络设备抽象,本地化在 crates/axnet/
axpoll 0.1.2 socket poll/select 事件通知

不引入 embassy-executor(与 axtask 调度器冲突)。不引入完整 embassy-net(替换代价过大且首版 MVP 不需要)。

AtomicWaker 是唯一已批准的 embassy 依赖。ISR 中 WAKER.wake(),任务上下文中 WAKER.register(cx.waker())。单 waiter 场景够用;多 waiter 场景由 socket readiness 层的 axpoll 管理。

各层职责

ISR 层

只做四件事:读 cause 寄存器 → 分类 → ack/mask → AtomicWaker::wake()。不读取 descriptor、不搬运数据、不调用协议栈。

中断诊断阶段已验证 VirtIO-MMIO 中断可重复投递且独立于 UART IRQ。IRQ 7 诊断 handler 成功分类 used-buffer / config-change 并正确 ack/rearm。

queue task 层

唯一 task 推进一个 RX queue 或 TX queue。以固定 budget 回收 TX、处理 RX、补 descriptor。budget 耗尽且有剩余工作时主动重排自身,不立即 unmask IRQ。

RX 状态机:Empty → PostedToDevice → DeviceOwned → Completed → CpuSynchronized → StackToken → Recycled

TX 状态机:Free → StackToken → ReadyToSubmit → DeviceOwned → Completed → Reclaimed → Free

packet slot 有明确容量上界。满载时产生背压(WouldBlock),不通过无界队列隐藏压力。

stack runner 层

在任务上下文调用 smoltcp poll。响应三类唤醒源:

  • device wake:queue task 完成 RX/TX 后
  • software wake:socket 有新数据或空间
  • timer wake:smoltcp 协议定时器(TCP 重传、ARP 刷新)

空闲时不轮询(与当前 10ms polling fallback 不同)。持续流量不饥饿任一方向。

socket readiness 层

smoltcp 单个 socket set 只有一个 waker 槽。需要多个 socket 各自等待时,由该层做桥接:

smoltcp socket events → axpoll::PollSet → 多 waiter poll/select

覆盖多 waiter、overflow、close、error 场景。poll/select 返回的 readiness 与实际 I/O 一致。

UART 经验迁移

UART 异步驱动已验证的经验可迁移到 NIC:

可迁移 迁移方式
ISR 极简原则 读 cause → ack → wake,不变
register-recheck IRQ/descriptor rearm 后重检
backpressure ring full → WouldBlock → 注册 waker
completion 分层 区分 submit、doorbell、DMA 完成、descriptor 回收
QEMU/真板分证据 QEMU 单 hart 不能替代真板 SMP/DMA 证据

不可迁移:字节 ring 布局、单一 copier 任务模型。NIC 以 DMA descriptor 和 packet buffer ownership 为基本单位。

NIC 新增的 UART 不具备的问题:DMA cache coherence、scatter-gather、MTU、checksum offload、link state、burst traffic、multiqueue、interrupt moderation。

执行上下文契约

每层有明确的所有权边界:

对象 唯一修改者 可观察者 回收点
IRQ mask/cause ISR / control path queue task queue drain/rearm
RX descriptor queue task / device stack token token consume/recycle
TX descriptor queue task / device stack runner completion + DMA sync
stack poll state stack runner socket / VFS socket operation
reset generation control path IRQ、queue、token old completion discarded

已完成的基线

阶段 内容 状态
协议栈本地化 smoltcp/axnet 本地化,14/14 QEMU PASS ✅ 完成
轮询网络 VirtIO-MMIO 轮询端到端,10ms polling fallback ✅ 完成
中断诊断 MMIO IRQ 诊断,12/12 gates PASS ✅ 完成
测试矩阵 测试矩阵与资格操作手册收口 ✅ 完成

下一条路径:异步 RX 队列

下一个阶段覆盖 IRQ 唤醒原语 + QEMU 异步 RX。目标:MMIO RX 由最小 ISR 唤醒唯一 queue task,以有界 budget 推进。

具体引入 NetQueueControl、AtomicWaker、register-recheck 竞争防护。验证 event-before-register、register-during-event、spurious IRQ 无 lost wakeup。

验证边界:单向 RX burst 无 busy loop、饿死或 descriptor 泄漏。budget exhausted 可观测。

不含:异步 TX、最终 packet slot、stack runner、socket readiness。

参考