- 操作系统
- 嵌入式
- 嵌入式OS
【免费下载链接】tock
A secure embedded operating system for microcontrollers
本文基于 Tock Network WG Meeting Notes(2025-02-24) 编写,并结合 Tock 内核仓库中 Ethernet HIL、EthernetTap 用户态驱动、LiteEth/VirtIO 硬件适配层及 StreamingProcessSlice 流式缓冲机制的源码,还原 2025 年 2 月 Tock 以太网能力从「实验分支」走向「正式对外发布」的关键决策过程。读者可从中掌握 Tock 以太网栈的当前支持范围、内核态到用户态的帧收发路径,以及社区在「子模块 + 构建系统」上的取舍。
会议概览
- 日期:2025 年 2 月 24 日
- 参与者:Branden Ghena、Leon Schuermann、Alex Radovici、Tyler Potyondy
- 议程:
- 各方向进展更新(Updates)
- 以太网(Ethernet)
- 核心议题:将 Tock 的以太网支持从实验状态推进到可对外宣传的正式能力,涉及 HIL 稳定化、驱动合入、用户态协议栈(LwIP)以及构建系统对第三方子模块的处理。
本次会议的结论可以概括为一句话:在 EthernetTap 驱动 PR 合入 staging 分支之后,Tock 就具备了正式对外宣传以太网支持的资格。
进展更新:RP2040 的 PIO SPI 与 WiFi 探索
会议开头,Branden Ghena 汇报了 RP2040 平台上的网络相关进展:
- RP2040 PIO SPI 已取得突破:学生团队已经让中断(interrupts)工作起来,计划整理代码后向 Tock 提交 PR。RP2040 的 PIO(Programmable I/O)是其特色外设,可用于实现灵活的时序协议,SPI 是其中应用场景之一。
- RP2040 WiFi 通信方式特殊:RP2040 通过单根导线与 Infineon WiFi 芯片通信,本质上是一种半双工(half-duplex)SPI 形态,实现上比较特殊,需要进一步研究。
从仓库看,RP2040 的无线外设驱动位于 capsules/extra/src/cyw4343/driver.rs,该驱动目前仅出现在 capsules 的 extra 集合中,尚未与EthernetAdapterDatapathHIL 打通,与会议中「WiFi 仍需更多工作」的判断一致。
以太网:Tock 从「实验」走向「正式支持」
现场演示:LwIP 协议栈跑通 QEMU
Leon Schuermann 在会议上做了现场演示(live demo):在 QEMU 模拟器上运行 LwIP(Lightweight IP)应用栈,以太网链路工作正常,能够通过 HTTP 提供(serve)一个基础的着陆页(landing page)。
该演示对应的用户在用户态(userspace)侧的成果是 LwIP Ethernet Libtock-C App(libtock-c 的 PR #494)。这意味着完整的 TCP/IP 协议栈运行在用户态,而 Tock 内核只负责把裸以太网帧(raw IEEE 802.3 frames)在内核与用户态之间搬运。
当前以太网硬件支持范围
针对 Branden 的提问「我们目前有哪些硬件支持以太网?」,Leon 给出了清单:
| 平台 | 说明 |
|---|---|
| QEMU | 通过 VirtIO 网络设备仿真,配合qemu_rv32_virt/qemu_rv64_virt板级支持 |
| LiteX | 基于 LiteEth MAC 的软核以太网(FPGA SoC) |
| 部分 STM32F4 | 带片上以太网 MAC 的型号 |
| 任意带 USB 的板子 | 通过USB-CDC ECM(Ethernet Control Model)虚拟网卡,理论上任何有 USB 的板子都能用,且能与任何主流桌面操作系统对接 |
这些声明在仓库源码中都有对应实现:
- LiteX LiteEth:chips/litex/src/liteeth.rs 实现了
EthernetAdapterDatapathHIL,并通过ETHMAC_SRAM_WRITER_*(接收)与ETHMAC_SRAM_READER_*(发送)寄存器组驱动 LiteEth MAC;boards/litex/arty/src/main.rs 与 boards/litex/sim/src/main.rs 已将其接入板级。 - VirtIO 网络卡:chips/virtio/src/devices/virtio_net.rs 实现了 VirtIO Net 设备;boards/qemu_rv32_virt/src/lib.rs 在探测到 VirtIO NetworkCard 时实例化
EthernetTapDriver并桥接到VirtIONet,探测不到则禁用(对应 README 中 "VirtIO NetworkCard device not found, disabling EthernetTapDriver" 的提示,见 boards/qemu_rv32_virt/README.md)。
合入路线:staging → master
会议的合入策略非常明确,按以下顺序推进:
- 先把现有 PR 合入staging 分支;
- 合并EthernetTap PR(即 capsules/extra/src/ethernet_tap.rs 对应的用户态 TAP 驱动);
- 从 staging 向 master 提 PR。
Leon 特别指出,staging → master 的那个 PR 将同时包含三件套:
- HIL(硬件抽象层接口,即
EthernetAdapterDatapath); - HIL 的一个硬件实现(如 LiteEth、VirtIONet);
- HIL 的一个使用者(即 EthernetTap 用户态驱动)。
这正是 Tock 社区典型的「接口 + 实现 + 使用者」三位一体合入模式,保证 HIL 不是空头支票——既有实现验证,又有真实消费方。
后续事项:文档化支持矩阵
Branden 提出一个重要诉求:要把「哪些平台支持、哪些不支持」明确记录下来,用一个表格说明各芯片对各功能(feature)的支持情况。这对应 Tock 文档体系中的板级 README(例如 boards/qemu_rv32_virt/README.md 明确列出 VirtIO 网络卡存在/缺失时的行为),避免用户踩坑。
LiteX 驱动需要随 HIL 更新到最新接口,但这项工作可以在合入 master 之后再做。
远期计划:SmolTCP 与 libtock-c 用户态栈
会议明确了两条后续主线:
- 内核态 TCP/IP 栈:更新 Ionut 的 SmolTCP 方案,作为 Tock 内核内的网络栈方向;
- 用户态网络栈:优先完成 libtock-c 用户态侧的工作,即streaming process slice API与LwIP 栈 + 应用两个 PR。
这两个 PR 都已经接近完成,其中streaming process slice API 应先行合入。
深入源码:Tock 以太网的内核实现路径
原始以太网帧 HIL:EthernetAdapterDatapath
kernel/src/hil/ethernet.rs 定义了 Tock 内核的以太网抽象层,其设计要点如下:
- 只覆盖裸数据通路(raw datapath):传输 IEEE 802.3 以太网帧,要求帧已包含带源/目的地址的以太网头,不含 FCS(帧校验序列)尾部。
- 接口声明:该 HIL 目前不稳定,会被后续贡献扩展为更完整的内核内网络栈;但已足以把以太网 MAC / 适配器桥接到用户态,由用户态运行网络栈。
核心 traitEthernetAdapterDatapath提供四个方法:
set_client:注册EthernetAdapterDatapathClient回调;enable_receive/disable_receive:启用/禁用帧接收;HIL 明确要求 MAC 不得在启用前或禁用后触发received_frame;transmit_frame(frame_buffer, len, transmission_identifier):发送一帧。帧必须位于缓冲区偏移 0 处、含以太网头、不含 FCS;长度以u16表示(最大帧尺寸上限为u16::MAX字节,覆盖含各种 Jumbo Frame 选项在内的所有常见 MTU)。
异步完成通过客户端 traitEthernetAdapterDatapathClient的两个回调体现:
transmit_frame_done(err, frame_buffer, len, transmission_identifier, timestamp):发送完成或异步出错,错误码包括FAIL(内部错误)、BUSY(MAC 忙)、OFF(未初始化)、NODEVICE(无活动链路);timestamp为可选发送时间戳(面向 IEEE 1588 PTP 等场景预留);received_frame(frame, timestamp):收到一帧,同样携带可选接收时间戳。
EthernetTap:面向用户态的 TAP 式网卡驱动
capsules/extra/src/ethernet_tap.rs 实现了会议中反复提及的EthernetTap 驱动——一个「用户态应用 TAP 式网络驱动」,模拟 Linux/FreeBSD 的 TAP 虚拟网络设备语义,让用户态应用直接操作裸以太网帧。这是本次会议合入路线图中的关键 PR。
其核心机制包括:
多应用复用:驱动在所有应用之间复用底层网络接口。所有应用都
allow一个接收缓冲区,每帧入站帧都会被放入所有应用的接收缓冲区(类似 VEPA 模式下的 MacVTap:应用都能收发链路帧,但一般无法彼此直接通信,除非对端支持 hairpin 模式;若需应用间互通,可在内核实例化 hairpinning 桥或为每个进程提供独立 TAP)。基于
StreamingProcessSlice的无损流式接收:驱动把收到的帧连同每帧驱动头一起写入StreamingProcessSlice。每帧头格式为:- Bytes 0–1(
u16,本机字节序):flags,bit 0 表示「接收时间戳有效」; - Bytes 2–3(
u16,本机字节序):帧长度(不含本头); - Bytes 4–11(
u64,本机字节序):接收时间戳; - Bytes 12 起:帧内容(含以太网头、不含 FCS)。
该头让用户态无需解析帧内容即可高效定位帧边界。驱动仅在成功追加数据后调度 upcall;应用须遵循
StreamingProcessSlice语义,通过原子allow换缓冲实现无损接收。- Bytes 0–1(
发送路径:同时最多传输一帧。把待发帧拷贝进内核内存是同步操作,实际发送可能异步,因此发送开始后通过 upcall 通知应用,用户态不得在前一次发送未被 upcall 确认前再次发送。
MAX_MTU硬编码为1518字节(802.1q VLAN 标签 + 1500 字节负载 MTU,不含 4 字节 FCS)。驱动号:
capsules_core::driver::NUM::EthernetTap。
其系统调用接口完整定义如下(源码impl SyscallDriver for EthernetTapDriver中command实现可查证):
Command 系统调用
| 命令号 | 功能 | 返回 |
|---|---|---|
0 | 检测驱动是否安装 | 成功,或ENOSUPPORT |
1 | 查询进程本地 RX 统计 | success_u32_u32_u32:(rx_frames, rx_bytes, rx_frames_dropped),计数在u32::MAX处回绕 |
2 | 查询进程本地 TX 统计 | success_u32_u32:(tx_frames, tx_bytes) |
3 | 发送allow_ro::TX_FRAME缓冲区中的帧 | 参数 1 为最多发送字节数(自偏移 0 起,受MAX_MTU限制,最多u16::MAX),参数 2 为帧发送标识符(原样带回 upcall 2);失败返回BUSY(有帧正在发送)或SIZE(缓冲区过小或超 MTU) |
Subscribe 系统调用
- upcall
0:暂不支持(驱动被其他进程释放时的通知); - upcall
1:接收帧写入流式切片后触发; - upcall
2:帧发送完成时触发。参数 1 为 statuscode(0 或 ErrorCode);参数 2 为 flags 与长度打包的u32(bits 16–30 为 flags,bit 16 表示 TX 时间戳有效;bits 0–15 为已发送字节数);参数 3 为帧发送标识符。
Allow 系统调用
- rw-allow
0:接收帧用的StreamingProcessSlice; - rw-allow
1:发送帧元数据缓冲区(至少 8 字节,bytes 0–7 为发送时间戳,big endian); - ro-allow
0:待发送帧缓冲区(仍需通过 command 3 触发发送)。
StreamingProcessSlice:无损内核→用户态流式传输的基石
kernel/src/utilities/streaming_process_slice.rs 实现了会议中提到的streaming process slice抽象。它解决的核心问题是:ADC 采样、网络栈等场景需要内核向进程持续、无损地输送不受进程速率控制的数据流,且不想依赖内核侧缓冲。
其协议要点(版本 0):
- 进程分配两个缓冲区;
- 按格式初始化第一个缓冲区:
version字段置 0,清空exceeded标志,可自行设置/清除halt标志,保留位必须为 0,offset置 0; - 进程将缓冲区
allow给内核驱动; - 内核从
data字段 +offset处写入数据,每次写入后递增offset。只有完整写入一个 chunk 才递增offset;若空间不足则置位exceeded标志(bit 0)。若halt与exceeded同时置位,内核停止写入;若exceeded置位而halt清除,内核仍尝试写入更小的后续 chunk(可能丢弃部分包); - 内核调度 upcall 通知进程(进程不得依赖 upcall 数量,须以缓冲区头中的
offset与 flags 为准); - 进程准备第二个缓冲区,通过
allow原子换入; - 进程处理第一个缓冲区中的 chunk,同时内核向新缓冲区持续写入——这就是双缓冲乒乓(ping-pong)实现无损流式传输。
缓冲区头布局(8 字节):
0 2 4 6 8 +-----------+-----------+-----------------------+----------... | version | flags | write offset (32 bit) | data +-----------+-----------+-----------------------+----------... | 000...000 | x{14},H,E | <native endian u32> |version:u16,目前仅支持版本0;flags:u16 位域,E(bit 0)为exceeded,H(bit 1)为halt,其余 14 位保留(进程必须清零,内核遇到未知保留位会拒绝操作);offset:u32,本机字节序的写入偏移。
对应实现细节可在 kernel/src/utilities/streaming_process_slice.rs(append_chunk)与 kernel/src/utilities/streaming_process_slice.rs(append_chunk_from_iter)中查看,且文件底部附有完整的单元测试(test_empty_process_slice、test_header_only_process_slice)验证头部解析、exceeded置位与空 chunk 行为。
会议中提到 API「接受两个缓冲区」的讨论,正是指这一双缓冲协议:用户如果愿意,仍可使用单一连续缓冲区,但双缓冲是推荐形态。EthernetTap 的received_frame实现正是通过StreamingProcessSlice::append_chunk_from_iter(frame_header.iter().chain(frame.iter()))把「驱动头 + 帧」整体追加进进程缓冲,并只在首次追加成功时调度 upcall。
构建系统之争:git 子模块的「自动更新」与「文件清单」取舍
会议花了相当篇幅讨论一个工程化痛点:Tock 的 Makefile 体系会自动初始化/更新 git 子模块(包括 LwIP 这类第三方代码库),但子模块目录在 Make 首次运行时可能还是空目录,导致「不能依赖子模块目录中任何文件在 Make 首跑时就存在」。
Leon 遇到的具体问题是:
- 无法阻止 Uncrustify(代码格式化工具)对 LwIP 子模块尝试格式化;
- 无法在 Makefile 初始化阶段引入 LwIP 内的 Filelists.mk(该文件以变量形式列出 LwIP 的全部 C 源文件)——因为子模块此时可能尚未就绪;
- Brad 建议增加
Makefile.setup来初始化和更新子模块,Tock 现有 Makefile 体系会在发现该文件时「自动」处理。
会上提出的候选方案包括:
- 两阶段构建系统:先发现 setup 问题、再重新调用同一条 make 命令(Leon 认为「很 messy」);
- 自己 vendoring(内置)文件清单:直接把 LwIP 的
Filelists.mk内容硬编码进仓库(Branden 倾向此方案:「我认为最简单的方式就是把文件清单自己 vendor 进来」); - 独立编译库:用一个独立 make 步骤先编译库(不依赖 Tock),再在构建 Tock 时引入(Tyler 提到 OpenThread 曾用「单独编译库」的做法,且 OpenThread 子模块固定在某 release,
make时自动初始化并拉取)。
Tyler 还补充了一个现实经验:给网络栈新增功能(比如 MQTT 示例)往往「很难 just work」——栈应可扩展,但新增一个子示例可能破坏已有移植,因为新特性总是需要在多个层面(库、移植、示例)同时工作。
最终 Branden 的结论是:vendoring 文件清单仍是最省事的方案,代价是子模块更新时可能因未同步文件清单而出现未定义符号(undefined symbols)之类的问题——这是需要接受的成本。同时应在 README 里说明Filelists.mk是怎么来的、以及如何随版本更新,降低维护门槛。
结论与行动清单
本次会议收敛出的可执行结论如下:
- 合入顺序:先合入 streaming process slice API 相关 PR(双缓冲接口),再合入 LwIP 栈 + 应用 PR;
- staging 分支:合入 EthernetTap PR 后,staging → master 的 PR 将携带「HIL + HIL 硬件实现 + HIL 使用者」三件套,标志着 Tock 以太网支持正式对外宣传;
- 文档化:需要建立「哪些芯片支持哪些网络功能」的支持矩阵表,明确标注工作/不工作的部分;
- LiteX 驱动:随后在 master 上把 LiteX 驱动更新到最新 HIL 接口;
- 远期:推进内核态 SmolTCP 方案,以及 libtock-c 用户态栈的更多 PR。
对开发者而言,若想在 Tock 上跑通以太网,当前可操作的路径是:使用带 LiteEth 的 LiteX 板或带 VirtIO 网卡的 QEMU 目标(boards/qemu_rv32_virt),在内核侧依赖 kernel/src/hil/ethernet.rs 定义的 HIL 与 capsules/extra/src/ethernet_tap.rs 驱动,在用户态通过 libtock-c 的 LwIP 应用(PR #494 对应成果)完成 TCP/IP 栈接入;USB-CDC ECM 则为任意带 USB 的板子提供了虚拟以太网通道。
- 操作系统
- 嵌入式
- 嵌入式OS
【免费下载链接】tock
A secure embedded operating system for microcontrollers
相关推荐
Tock 网络工作组纪要(2026-02-02):DMASlice DMA 缓冲区安全共享机制深度解析
Tock 网络工作组纪要(2026 02 02):DMASlice DMA 缓冲区安全共享机制深度解析 本文基于 Tock 操作系统网络工作组(Network
操作系统嵌入式嵌入式OSTock 网络工作组 2025-11-10 会议解读:CYW4343 WiFi 驱动落地、WiFi Device Trait 与 tensile Thread 硬件 CI
Tock 网络工作组 2025 11 10 会议解读:CYW4343 WiFi 驱动落地、WiFi Device Trait 与 tensile Thread
操作系统嵌入式嵌入式OSTock 网络工作组会议纪要深度解读:WiFi 驱动 PR 评审、IPC RFC 推进与 Thread 硬件 CI 实践
Tock 网络工作组会议纪要深度解读:WiFi 驱动 PR 评审、IPC RFC 推进与 Thread 硬件 CI 实践 本文基于 Tock 网络工作组(Net
操作系统嵌入式嵌入式OS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考