第2章:网络协议栈源码结构分析

好,咱们直接切入正题。上一章我聊了VxWorks网络协议栈的整体架构,这一章咱们把袖子撸起来,看看Wind River官方协议栈的源码到底长什么样。说白了,就是带你熟悉一下目录结构、关键文件,以及那个非常重要的MUX层接口。

2.1 源码目录结构:别被文件夹吓到

我第一次打开VxWorks网络协议栈源码时,说实话,有点懵。目录层级不少,文件也很多。但别怕,其实核心就那么几个地方。

典型的Wind River协议栈源码,通常位于target/src/usrNetwork/或者target/src/net/下。我习惯先看顶层目录,心里有个谱。

目录/文件 说明
net/ 核心网络协议栈实现,TCP/IP协议族都在这里
netinet/ Internet协议族,比如IP、ICMP、UDP、TCP的具体实现
mux/ MUX层代码,这是咱们移植工作的关键接口
socklib/ Socket库,用户态接口的底层支撑
iflib/ 网络接口库,驱动相关的抽象层

嗯,这里要注意:不同版本的VxWorks,目录名可能略有差异。比如VxWorks 6.x和7.x,有些文件挪了位置。但核心逻辑没变。

2.2 关键文件说明:你得知道这些文件是干嘛的

我个人习惯,拿到一个协议栈源码,先找几个关键文件。它们就像地图上的地标,能帮你快速定位。

2.2.1 net/tcpip.cnet/tcpip_init.c

这是协议栈的初始化入口。说白了,系统启动时网络协议栈怎么起来的,就看这里。我记得有一次调试一个网络启动挂死的问题,最后发现是初始化顺序不对,就是在这个文件里找到的线索。

2.2.2 netinet/ip_input.cnetinet/ip_output.c

IP层的输入输出处理。你想想看,一个数据包从网卡收上来,怎么被解析成IP包,然后往上送?就是ip_input.c干的。反过来,要发送数据,ip_output.c负责封装和路由选择。

2.2.3 netinet/tcp_input.cnetinet/tcp_output.c

TCP协议的核心。这两个文件,我建议做协议栈优化的同学重点看。为什么?因为TCP的拥塞控制、重传机制、滑动窗口,全在这里。我曾经为了优化一个嵌入式设备的TCP吞吐量,在这两个文件里泡了整整一周。

2.2.4 mux/muxLib.c

MUX库的实现。这个文件太重要了,咱们后面会详细讲。它负责把网络协议栈和底层驱动解耦。说白了,你换网卡驱动,只要MUX接口不变,协议栈代码一行都不用改。

核心观点:MUX层是VxWorks网络协议栈移植的「桥头堡」。你只要搞懂了MUX接口,移植任何网卡驱动都不在话下。

2.3 MUX层接口:协议栈与驱动的「翻译官」

好,咱们重点聊聊MUX层。MUX的全称是Multiplexer,多路复用器。名字听着高大上,其实它的作用很简单:让上层协议栈和下层网卡驱动能互相通信。

2.3.1 MUX层的位置

你想想看,协议栈是通用的,但网卡驱动是千奇百怪的。有的网卡是PCIe接口,有的是SPI接口,有的甚至是通过内存映射的。如果没有MUX层,那协议栈得为每种网卡写一套适配代码,这谁受得了?

MUX层就在中间,它定义了一套标准接口。驱动只要实现这套接口,协议栈就能用。反过来,协议栈也通过MUX层把数据交给驱动。

2.3.2 核心数据结构:MUX_BLK

MUX层最核心的数据结构是MUX_BLK。你可以把它理解成一个「数据信封」。里面装着网络数据包,还有一些元信息,比如数据长度、协议类型、发送方向等。

typedef struct mux_blk {
    struct netif      *mblk_ifp;    /* 网络接口指针 */
    pBuf_t            *mblk_pBuf;   /* 数据缓冲区 */
    int               mblk_len;     /* 数据长度 */
    int               mblk_flags;   /* 标志位 */
    /* ... 还有其他字段 */
} MUX_BLK;

我在项目中遇到过一个问题:数据包在MUX层传递时,mblk_pBuf指向的缓冲区被意外释放了。排查了好久,最后发现是驱动在发送完成后没有正确通知MUX层。嗯,从那以后,我对MUX_BLK的生命周期管理就格外小心。

2.3.3 关键接口函数

MUX层提供了一组标准接口,驱动开发者需要实现它们。我挑几个最重要的说说:

  • muxBind():驱动注册到MUX层。说白了,就是告诉协议栈「嘿,我这儿有个网卡,你可以用了」。
  • muxUnbind():驱动从MUX层注销。网卡被拔掉或者驱动卸载时调用。
  • muxSend():协议栈通过这个接口把数据包交给驱动发送。驱动拿到MUX_BLK后,负责把数据写到硬件里。
  • muxReceive():驱动收到数据后,通过这个接口把数据包交给协议栈。注意,这个函数通常是在中断上下文调用的。
  • muxIoctl():控制命令接口。比如设置MAC地址、调整MTU、查询链路状态等。

个人经验:写驱动时,muxSend()muxReceive()的实现要特别注意并发问题。我曾经在muxSend()里忘了加锁,结果多核环境下数据包乱序,查了三天才找到原因。

2.3.4 MUX层的初始化流程

咱们捋一下MUX层是怎么启动的:

  1. 系统启动时,网络协议栈初始化,调用muxInit()
  2. 网卡驱动加载,调用muxBind()注册自己。
  3. 协议栈通过muxIoctl()查询网卡能力,比如支持哪些协议、MTU多大。
  4. 一切就绪后,数据就可以通过muxSend()/muxReceive()流动了。

这个流程看起来简单,但实际移植时,每一步都可能出问题。我建议你先把muxBind()调通,能注册成功,后面就顺了。

2.4 避坑指南:我踩过的几个坑

最后,分享几个我在MUX层移植中遇到的典型问题,希望能帮你少走弯路。

坑1:MUX_BLK的内存管理

我曾经在驱动里直接释放了MUX_BLK指向的缓冲区,结果协议栈那边还在用,直接崩溃。记住:MUX_BLK的所有权是协议栈的,驱动用完要调用muxFreeBlk()归还,而不是自己释放。

坑2:中断上下文中的muxReceive()

muxReceive()通常是在中断里调用的。但有些协议栈操作可能会引起调度,比如分配内存。如果你的系统在中断里不允许调度,那就得用任务级处理。我当时的做法是:中断里只把数据放到一个队列里,然后唤醒一个任务去调用muxReceive()

坑3:MTU不匹配

有一次我移植一个千兆网卡,发现大包总是发不出去。查了半天,原来是驱动报告的MTU是1500,但硬件实际支持9000字节的巨型帧。协议栈按1500分包,结果驱动那边处理不了。所以,muxIoctl()里返回的MTU一定要和硬件能力匹配。

2.5 小结

这一章咱们把VxWorks网络协议栈的源码结构捋了一遍。核心就三点:

  • 知道关键文件在哪,尤其是mux/muxLib.cnetinet/下的TCP/IP实现。
  • 理解MUX层的定位,它是协议栈和驱动的桥梁。
  • 掌握MUX_BLK和几个核心接口的用法。

下一章,咱们会深入MUX层的实现细节,手把手教你如何写一个网卡驱动的MUX适配层。到时候,我会拿一个真实的网卡驱动做例子,咱们一起把代码跑起来。