2. emNet源码包结构与关键文件解析

拿到 emNet 发布包后,第一件不该做的事是急着打开某个 .c 文件逐行阅读。emNet 是一个按协议分层、按功能分目录组织的嵌入式 TCP/IP 协议栈,如果缺少一张“源码地图”,很容易在几十个源文件之间迷路。本节的目标是:先看清发布包的目录骨架,再定位 IP、TCP、UDP、ICMP、ARP、DHCP、DNS 等模块各自落在哪个文件里,最后理解这些文件之间的调用与数据流关系,为后续的移植、配置和裁剪打下基础。

一、发布包的顶层目录组织

emNet 的发布包通常以 SEGGER/emNet/ 为根,下面按“通用源码 + 端口层 + 配置 + 文档 + 示例”几个维度展开。不同版本目录名可能略有差异,但结构逻辑基本一致。典型布局如下:

emNet/
├── IP/                 # 协议栈核心源码(与硬件无关)
│   ├── IP.c / IP.h
│   ├── TCP.c / TCP.h
│   ├── UDP.c / UDP.h
│   ├── ICMP.c / ICMP.h
│   ├── ARP.c / ARP.h
│   ├── DHCPc.c / DHCPc.h
│   ├── DNS.c / DNS.h
│   ├── IGMP.c / IGMP.h
│   ├── IP_Config.h
│   └── ...
├── Config/             # 用户配置与端口相关配置
│   ├── IP_Config.c
│   ├── IP_Config.h
│   └── ...
├── PORT/               # 硬件/RTOS 移植层
│   ├── IP_OS.c
│   ├── IP_NI_*.c       # 网络接口驱动适配
│   └── ...
├── Sample/             # 示例应用(如 WebServer、TCP 回显)
├── Doc/                # 手册与 API 文档
└── SEGGER/             # 依赖的 SEGGER 基础组件(如 RTOS、调试)

这里最关键的分界是:IP/ 目录下的文件与硬件无关,它们只依赖标准 C 和 emNet 内部接口;PORT/ 目录下的文件与硬件和 RTOS 相关,负责把协议栈“接”到具体的以太网控制器和操作系统上。理解这条分界线,是判断“某个文件该不该改”的第一依据。

二、协议模块与文件的对应关系

emNet 把每个协议族基本收敛到一对 .c/.h 文件中,命名直观。下表给出常见模块与文件的对应,以及各自的核心职责:

模块主要文件核心职责
IP 层IP.c / IP.hIP 首部处理、分片重组、路由选择、上层协议分发
TCPTCP.c / TCP.h连接状态机、滑动窗口、重传、序号确认、端口管理
UDPUDP.c / UDP.h无连接数据报收发、端口复用、校验和
ICMPICMP.c / ICMP.h回显请求/应答(ping)、差错报文生成
ARPARP.c / ARP.hIP→MAC 地址解析、ARP 缓存维护
DHCP 客户端DHCPc.c / DHCPc.h动态获取 IP、掩码、网关、DNS 地址
DNS 客户端DNS.c / DNS.h域名到 IP 的解析请求与响应处理
IGMPIGMP.c / IGMP.h组播组成员管理(可选模块)

需要特别注意的是 IP_Config.h 与 IP_Config.c。前者集中了大量编译期开关,例如是否启用 TCP、UDP、DHCP、DNS、IGMP,缓冲区数量、最大连接数、是否支持分片等;后者通常存放用户可修改的运行时配置,如本机 MAC、默认 IP、子网掩码、网关。裁剪协议栈时,第一步就是改这两个文件,而不是去删 .c 文件。

三、模块之间的调用与数据流

从接收方向看,数据从网络接口驱动进入,经 IP 层解析后按协议号分发:协议号 6 交给 TCP,17 交给 UDP,1 交给 ICMP。发送方向则相反,应用通过 socket 风格 API 调用 TCP/UDP,再经 IP 层封装,最后由网络接口发出。ARP 与 IP 是“旁路”关系:IP 在发送前需要目的 MAC,若 ARP 缓存未命中,会先挂起该包并发起 ARP 请求。DHCP 和 DNS 本身是应用层协议,但它们通过 UDP 承载,因此依赖 UDP 与 IP 正常工作。

emNet 源码地图:模块分层与数据流 应用层 / 示例 WebServer · TCP 回显 · DNS 查询 · DHCP 自动配置 TCP.c / TCP.h 连接状态机 · 重传 · 窗口 · 端口 UDP.c / UDP.h 数据报 · 端口复用 · 校验和 IP.c / IP.h — 网络层核心 首部处理 · 分片重组 · 路由 · 协议分发(6→TCP,17→UDP,1→ICMP) ICMP.c / ICMP.h ping · 差错报文 ARP.c / ARP.h IP→MAC · 缓存 DHCPc / DNS 动态地址 · 域名解析 PORT/ 目录负责网络接口驱动与 RTOS 适配;Config/ 目录负责编译期与运行期配置

四、移植层与配置文件的定位

PORT/ 目录是 emNet 与具体硬件之间的“适配器”。其中 IP_NI_*.c 系列文件对应不同的网络接口控制器(NI,Network Interface),例如内部 MAC、外部以太网芯片等。移植时通常需要实现或修改:网卡初始化、发送一帧、接收一帧、中断处理、以及可选的校验和卸载。另一个关键文件是 IP_OS.c,它把 emNet 内部的信号量、互斥量、任务延时等抽象映射到实际 RTOS(如 embOS、FreeRTOS 或裸机)。如果使用裸机,则这些函数通常退化为简单的标志位或空实现。

Config/ 目录下的 IP_Config.h 是裁剪协议栈的“总开关”。例如,若产品不需要 DNS,可将 IP_USE_DNS 置 0,链接时 DNS.c 的目标代码就不会被引用,从而节省 Flash 和 RAM。类似地,IP_USE_DHCP、IP_USE_TCP、IP_USE_UDP 等宏控制各模块的编译。需要强调的是:关闭某个宏后,应用层就不能再调用对应 API,否则会出现链接错误或运行时异常,因此裁剪必须与应用程序同步调整。

五、建立源码地图的实用方法

面对一个具体的 emNet 版本,建议按以下顺序建立自己的源码地图:

  1. 先列出 IP/ 目录下所有 .c/.h 文件,按协议名归类,确认哪些模块被当前配置启用。
  2. 打开 IP_Config.h,记录所有 IP_USE_* 宏的当前取值,形成一张“启用模块清单”。
  3. 在 PORT/ 目录中确认网络接口驱动文件名,找到与目标硬件匹配的那一个,并记录它依赖的寄存器或外设。
  4. 从应用入口(如 Sample/ 中的示例)出发,沿 API 调用向下追踪一次完整收发路径,验证自己对分层关系的理解。
  5. 把上述信息整理成一页纸的“源码地图”,标注每个文件的职责、是否可修改、依赖关系。
一个实用的判断原则:IP/ 目录下的文件原则上不修改,它们由 SEGGER 维护并随版本升级;Config/ 和 PORT/ 目录下的文件才是移植和裁剪时应该动手的地方。如果发现必须修改 IP/ 下的核心文件才能满足需求,通常说明配置方式或移植层实现有误,应优先检查这两处。