Rust in Everything
基于 embassy-executor 0.10.0

网络

这一部分讲 embassy-net 0.9.1(tag embassy-net-v0.9.1,2026-04-16)和它的两个驱动接口 crate:embassy-net-driver 0.2.0(2023-10-18 发布,之后没有新版本)、embassy-net-driver-channel 0.4.0。embassy-net 的 src/ 一共 3927 行,TCP/IP 协议本身一行也没写:底层是 smoltcp 0.13.1(tag v0.13.1),一个不分配内存、不依赖 async 的协议栈。embassy-net 做的是把 smoltcp 「调用方每隔一段时间来 poll 我一次」的同步接口,接到 embassy 的任务、waker 和定时器上。

三个角色:

  • 驱动:实现 embassy-net-driver 的 Driver trait,只管「现在能不能收一帧、能不能发一帧」,能就交出一个令牌,不能就登记 waker。网卡中断到来时叫醒的就是这个 waker。
  • Stack 和 Runner:embassy_net::new 把 smoltcp 的 Interface、SocketSet 和几个 waker 槽装进一个 RefCell<Inner>。Stack 是指向它的 4 字节句柄,Runner 拿着驱动,在自己的任务里反复 poll 这个 Inner。
  • 套接字:TcpSocket、UdpSocket 只是「Stack + smoltcp 里的一个槽号」。读写时借出 RefCell,在 smoltcp 套接字里登记 waker,再叫醒 Runner。
  1. Stack 与 Runner:Runner::run 是一个永远 Pending 的 poll_fn;每次 poll 先收完所有帧、再发完所有能发的,最后把 smoltcp 的 poll_at 换成一个当场 poll、当场 drop 的 Timer;实测空闲时一次也不 poll,ARP 每秒一次,DHCP 重试 10 秒一次;0.9.1 修掉的「发送队列满时忙转」,复原旧代码后 100 ms 里 poll 两万多次;两个任务一起等 wait_config_up 时每秒互相叫醒上千万次。
  2. 驱动接口:receive 返回收、发两个令牌,transmit 必须在真有空间时才给令牌;stm32 以太网驱动的中断处理函数只做一件事:叫醒 Runner;Capabilities::default() 的 MTU 是 0,debug 下 panic、release 下帧长不受限;embassy-net-driver-channel 用两条 zerocopy_channel 把驱动放进另一个任务;tuntap 驱动在 macOS 上编不过。
  3. TCP 与 UDP 套接字:缓冲区由调用方给、生命周期被 transmute 成 'static;每个 with_mut 都叫醒 Runner;进程内两个栈之间的 TCP echo 和吞吐(缓冲区、MTU、驱动队列深度);drop 一个连着的 TcpSocket 不发任何包;UDP 报文超过 MTU 时 send_to 返回 Ok 却被丢掉;两个任务读同一个 UdpSocket,一个永远收不到。
flowchart TB
  irq["网卡中断"] --> dw["驱动的 waker"]
  dw --> run["Runner 任务"]
  run --> inner["RefCell Inner"]
  inner --> iface["smoltcp Interface"]
  inner --> set["SocketSet"]
  iface --> drv["Driver 令牌"]
  run --> tm["Timer::at poll_at"]
  app["应用任务"] --> sock["TcpSocket UdpSocket"]
  sock --> inner
  sock --> rw["Inner.waker"]
  rw --> run

网络栈里同时走着本卷的两条主线:网卡中断叫醒 Runner 是中断主线,Runner 按 poll_at 交给时间驱动的那个到期时刻是定时器主线。应用任务和 Runner 之间靠 waker 来回叫醒,形状和跨任务主线一样,只是中间的「队列」换成了 smoltcp 的套接字缓冲区。和 tokio 对照:tokio 的网络 I/O 由运行时里的反应器统一等 epoll / kqueue 事件(见 反应器),协议栈在内核里;embassy 没有内核,协议栈本身就是一个普通任务。

本部分的实验大多在 std 上跑:platform-std 执行器上建两个 embassy-net 栈,中间用一个手写的 Driver(或 embassy-net-driver-channel)接成一根「网线」,静态 IPv4 地址 10.0.0.1 和 10.0.0.2。少数实验在 QEMU 上跑同样的两栈结构,用来看 Cortex-M4 上的内存和代码体积。main 上 embassy-net 已经从 smoltcp 换成了自家的 xarxa 协议栈(2026-07-27 的提交 680b19296「net: migrate smoltcp -> xarxa」),还没发版,本部分只讲 0.9.1。

💬 关联讨论

发起讨论
加载中...