1. Zephyr TCP/IP 协议栈概览:架构、协议族与 Linux 对比

做嵌入式网络开发这些年,我接触过不少协议栈。从早期的 lwIP 到 uC/TCP-IP,再到后来全面转向 Zephyr。说实话,Zephyr 的网络子系统给我留下的第一印象就是——它不像一个“嵌入式”协议栈。

为什么这么说?你想想看,大多数嵌入式协议栈为了省资源,往往把代码写得很“拧巴”。宏定义套宏定义,回调函数满天飞。但 Zephyr 不一样。它的代码组织方式,更像是一个微缩版的 Linux 网络栈。分层清晰,接口规范,甚至直接借鉴了 Linux 的 sk_buff 思想。

这一章,我们就从整体上把 Zephyr 网络协议栈看透。我会结合我实际移植和调优的经验,帮你建立起一个清晰的地图。

1.1 网络子系统整体架构

Zephyr 的网络子系统,核心是分层的。从底到顶,大致分为四层:

  • 物理层/MAC 层:驱动层,处理具体的硬件收发。比如以太网 MAC、Wi-Fi、BLE、6LoWPAN 等。
  • 网络层:核心是 IP 协议。支持 IPv4 和 IPv6。这里有个关键模块——网络缓冲区管理(net_buf),它负责所有数据包的内存分配和回收。
  • 传输层:TCP 和 UDP。Zephyr 的 TCP 实现是完整的,支持拥塞控制、重传、窗口管理等。
  • 套接字层:给应用层提供 BSD 风格的 Socket API。这是我最喜欢的一点——你几乎可以把 Linux 上的 socket 代码直接拿过来用。

我个人习惯把 Zephyr 的网络栈画成下面这张图,每次给团队新人讲架构时都会拿出来:

核心要点:Zephyr 的网络栈不是“拼凑”出来的。它是一个从设计之初就考虑了模块化和可移植性的原生系统。每个层之间通过明确定义的接口通信,而不是靠全局变量和回调地狱。

Zephyr 网络子系统分层架构 应用层 (Application) HTTP / MQTT / CoAP / 自定义应用 套接字层 (Socket API) BSD Socket / Zephyr Socket Offload 传输层 (Transport Layer) TCP / UDP 网络层 (Network Layer) IPv4 / IPv6 / ICMP / ARP / 6LoWPAN 驱动层 (Driver / MAC / PHY) net_buf 贯穿全栈

1.2 支持的协议族

Zephyr 支持的协议族,比很多人想象的要丰富。我最初以为它只支持 TCP/IP over Ethernet,后来发现它其实是一个“多协议汇聚”的架构。

协议族 支持情况 典型应用场景
IPv4 完整支持 传统以太网、Wi-Fi 产品
IPv6 完整支持,含 SLAAC、DHCPv6 物联网、Thread、6LoWPAN
TCP 完整实现,支持拥塞控制 HTTP、MQTT、WebSocket
UDP 完整支持 CoAP、DNS、SNTP
6LoWPAN 原生支持 802.15.4、BLE 物联网
Bluetooth IPSS 支持 BLE 上的 IP 通信
CANBUS 支持 车载网络、工业控制

我的经验:在 Zephyr 中启用协议族是通过 Kconfig 配置的。比如你要用 IPv6,就打开 CONFIG_NET_IPV6=y。但要注意,IPv6 的缓冲区开销比 IPv4 大不少。我在一个资源受限的 Cortex-M4 项目上,就因为没算好 net_buf 的大小,导致 IPv6 数据包频繁丢包。后来把 CONFIG_NET_BUF_DATA_SIZE 从 128 调整到 256 才解决。

1.3 与 Linux 网络栈的对比

很多从 Linux 转过来的开发者,第一次看 Zephyr 网络代码时会觉得眼熟。没错,Zephyr 确实借鉴了 Linux 的设计思路,但两者有本质区别。

我列一个对比表,这样更直观:

对比维度 Linux 网络栈 Zephyr 网络栈
内核态/用户态 严格分离,系统调用开销大 单地址空间,无系统调用开销
缓冲区管理 sk_buff,复杂但强大 net_buf,轻量级,支持静态池
Socket 实现 VFS 层 + inet socket 直接实现,无 VFS 层
并发模型 多进程/多线程 + 软中断 单线程 + 协作式或抢占式线程
内存开销 通常 > 64MB 可低至 32KB
实时性 非实时(PREEMPT_RT 除外) 原生实时内核
协议扩展 通过内核模块加载 编译时静态配置

这里我想重点说一下 net_buf 和 sk_buff 的区别。Linux 的 sk_buff 是一个“万能”的数据结构,它通过指针操作来避免数据拷贝。但这也导致代码非常复杂,一个 sk_buff 结构体就有上百行。

Zephyr 的 net_buf 则走了另一条路。它把缓冲区分成固定大小的“块”,通过链表串联起来。这样做的好处是:

  • 内存分配是确定性的,不会碎片化
  • 支持静态池预分配,适合实时系统
  • 代码简单,容易调试

避坑指南:我曾经在一个项目中,直接把 Linux 上的 socket 编程经验照搬到 Zephyr 上。结果发现 Zephyr 的 send() 在缓冲区满时会返回 -ENOMEM,而不是像 Linux 那样阻塞等待。这是因为 Zephyr 的 socket 默认是非阻塞模式。后来我加上了 CONFIG_NET_SOCKETS_POLL_MAX 和 poll() 机制才搞定。记住:Zephyr 不是 Linux,它的 API 行为有差异。

1.4 配置体系与裁剪

Zephyr 的网络栈是高度可裁剪的。你可以通过 Kconfig 选项,只编译你需要的功能。比如:

# 最小化 TCP/IP 配置示例
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=n
CONFIG_NET_TCP=y
CONFIG_NET_UDP=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_BUF_RX_COUNT=8
CONFIG_NET_BUF_TX_COUNT=4
CONFIG_NET_BUF_DATA_SIZE=256

这个配置只启用了 IPv4 + TCP/UDP,关闭了 IPv6。缓冲区数量也压到了最低。对于资源紧张的 MCU,这种裁剪是必须的。

关键指标:Zephyr 网络栈的最小 RAM 开销可以做到 16KB 左右(仅 IPv4 + UDP)。如果加上 TCP,大约需要 32KB。这个数据是我在 STM32F4 上实测的,不同平台会有差异。

1.5 小结

Zephyr 的网络协议栈,说白了就是一个为嵌入式场景量身定做的“迷你 Linux 网络栈”。它保留了分层架构和 BSD Socket API 的易用性,但在内存管理、实时性、可裁剪性上做了大量优化。

我个人觉得,理解 Zephyr 网络栈的关键在于三点:

  1. 分层架构:每一层职责清晰,接口明确
  2. net_buf 机制:这是整个协议栈的内存基石
  3. 配置裁剪:根据硬件资源,精确控制功能开关

嗯,这一章的内容就到这里。后面的章节,我们会深入到 TCP 的发送窗口调优、丢包重传分析、以及如何用 net-shell 和 wireshark 做故障定位。这些都是实战中真正会用到的东西。