4. VxWorks网络协议栈结构剖析
VxWorks 的网络协议栈并不是“socket 直接调用网卡”的简单结构,而是在 socket API、协议层(TCP/IP)、MUX 层与网络驱动之间形成了一条分层、可挂接、可替换的调用链。理解这条链的关键,是分清“谁调用谁”和“数据往哪个方向走”这两件事。前者是控制路径,后者是数据路径,二者方向相反、职责不同。
一、四层结构的分工
从上层往下看,VxWorks 网络栈大致分为四层:
- socket API 层:提供
socket()、bind()、connect()、send()、recv()等标准 BSD 接口,是应用代码唯一直接接触的层。它不关心底层是以太网、串口还是共享内存。 - 协议层:由 TCP、UDP、IP、ICMP、ARP 等协议模块组成。TCP 负责连接管理、确认重传、滑动窗口;IP 负责寻址与路由;ARP 负责 IP 到 MAC 的解析。
- MUX 层:即网络多路复用层(Network Multiplexer),是协议层与驱动层之间的“中间件”。它向上为协议提供统一的发送入口,向下管理多个网络接口,并负责协议与驱动之间的绑定、解绑和接口切换。
- 网络驱动层:即 END(Enhanced Network Driver)驱动,直接操作网卡寄存器、DMA 描述符和收发缓冲区,完成帧的实际收发。
这四层之间不是简单的函数调用堆叠,而是通过 MUX 提供的标准接口(如 muxBind、muxSend、muxDevStart)和回调函数(如 stackRcvRtn、stackTxRestartRtn)连接起来的。协议层通过 MUX 注册自己的接收回调,驱动层通过 MUX 把收到的帧交给协议层。
二、控制路径:从 socket 到驱动的调用关系
当应用调用 socket(AF_INET, SOCK_STREAM, 0) 时,控制流从 socket API 进入协议层,创建一个 TCP 控制块(TCB),并返回一个文件描述符。随后 bind() 把本地 IP 和端口写入 TCB,connect() 触发 TCP 三次握手,最终通过 IP 层查路由、ARP 解析下一跳 MAC,再经 MUX 找到对应网络接口,调用驱动的发送函数把 SYN 帧发出去。
这条控制路径的核心是“逐层向下传递参数”:socket 层传的是描述符和地址结构,协议层传的是协议控制块和报文,MUX 层传的是网络接口指针和 M_BLK 数据块,驱动层传的是 DMA 描述符和物理地址。每一层只关心自己需要的参数,不越界操作。
三、数据路径:从网卡到应用的反向流动
数据接收方向与控制方向相反。网卡收到帧后触发中断,驱动在中断服务程序或轮询任务中把帧从 DMA 缓冲区取出,封装成 M_BLK 链,然后调用 MUX 注册的接收回调 stackRcvRtn。MUX 根据帧类型(以太网类型字段)判断是 IP、ARP 还是其他协议,再分发给对应的协议层处理函数。
IP 层收到后检查目的地址,若是本机则继续向上交给 TCP 或 UDP;TCP 层根据四元组找到对应连接,把数据放入接收缓冲区,并唤醒阻塞在 recv() 上的应用任务。应用最终从 socket 缓冲区读到数据。整个过程是“驱动→MUX→协议→socket”的自下而上流动。
四、MUX 层的关键作用
MUX 层是理解整个栈的枢纽。它解决两个问题:一是“协议如何找到驱动”,二是“驱动如何找到协议”。前者通过 muxBind 完成——协议层调用 muxBind 把协议号、接收回调、发送回调注册到 MUX,MUX 再根据接口名或接口号绑定到具体 END 驱动。后者通过 stackRcvRtn 完成——驱动收到帧后调用 MUX 提供的统一接收入口,MUX 再根据帧类型分发给已注册的协议。
这种设计带来的直接好处是:更换网卡时,只需替换 END 驱动并重新 muxBind,协议层和 socket 层代码完全不用改。同样,增加一个新协议(如自定义 EtherType)也只需向 MUX 注册新的回调,不影响已有驱动。
五、一次 send 调用的完整数据流
以 TCP 发送为例,假设应用调用 send(sock, buf, 1024, 0),数据流路径如下:
- socket 层把用户缓冲区数据拷贝到内核
M_BLK链,并调用 TCP 发送函数。 - TCP 层根据连接状态决定是否立即发送、是否等待窗口,给数据加上 TCP 头,计算校验和,然后交给 IP 层。
- IP 层查路由表确定下一跳和出接口,加上 IP 头,若下一跳 MAC 未知则先触发 ARP 请求,把数据包暂存队列。
- IP 层调用
muxSend,把M_BLK和出接口信息交给 MUX。 - MUX 根据接口找到对应 END 驱动的发送函数,调用
endSend。 - 驱动把
M_BLK数据映射到 DMA 缓冲区,填写发送描述符,启动网卡发送。 - 发送完成后,驱动释放
M_BLK,并通过stackTxRestartRtn通知协议层可以继续发送。
接收方向则相反:网卡中断→驱动组装 M_BLK→stackRcvRtn→MUX 按类型分发→IP 层校验→TCP 层查找连接→放入接收队列→唤醒 recv()。整个过程中,M_BLK 是贯穿始终的数据载体,MUX 是贯穿始终的“分发器”。
六、常见误区与排查线索
实际调试中,很多网络不通的问题出在 MUX 绑定环节。例如:muxBind 返回 NULL 说明协议号或接口名不匹配;muxSend 返回 ERROR 说明接口未启动或驱动发送队列满;stackRcvRtn 未被调用说明驱动中断未使能或 DMA 描述符未正确初始化。排查时可以先确认 ifShow 或 muxShow 输出的接口状态,再检查协议注册回调是否与帧类型一致。
另一个常见问题是缓冲区不足。VxWorks 的 M_BLK 来自网络内存池(netPool),如果池太小,高流量下会出现丢包,表现为 send() 阻塞或 recv() 收不到数据。此时应检查 netPoolShow 的剩余块数,并适当增大 END_MEM_SIZE 或调整 M_BLK 数量。
掌握这四层结构和两条路径的方向,就掌握了 VxWorks 网络栈的骨架。后续无论是移植新网卡、增加自定义协议,还是定位丢包和延迟问题,都可以沿着这条链逐层排查。