MAC计数器工作原理:帧收发流程、错误计数、寄存器结构

咱们做网络丢包诊断,MAC计数器是第一个要啃透的东西。它就像设备的黑匣子,每一帧在MAC层怎么走的、在哪出错的,全记在这些寄存器里。下面我把自己多年排查的经验揉碎了讲清楚。

帧收发基本流程

先看看正常帧是怎么走的。发送方向很简单:上层协议栈把完整帧扔给MAC,MAC加前导码、帧起始定界符、CRC校验,再交给PHY发出去。接收方向反过来,MAC收帧,做CRC校验,检查长度、地址,没问题就往上送。

但这中间埋了好多计数器点。我个人习惯是把流程分成三个“检查哨”:

  1. 发送侧:MAC计数发送帧数、字节数、冲突计数(半双工时)。
  2. 接收侧:MAC计数接收帧数、CRC错帧数、对齐错帧数。
  3. 内部丢弃:比如接收FIFO溢出、发送下溢、资源不足。

你看,每个点都有对应的计数器。我曾碰到过一例:xx厂商设备收端CRC错误计数值总是缓慢增长,但上层TCP重传率却没变。最后查出来是PHY侧链路抖动导致少量CRC错,MAC的计数器真实记录,但应用层重传被上层确认掩盖了。所以计数器本身是没错的,关键是你得知道它在哪。

MAC帧收发流程与计数器监测点 上层协议栈 发送FIFO MAC发送(加CRC/前导) PHY发送 PHY接收 MAC接收 接收FIFO 上层协议栈 发送侧计数器 · TX帧总计数 · TX冲突计数 · TX下溢错 接收侧计数器 · RX帧总计数 · CRC错误计数 · 对齐错误计数 内部丢弃监视 · FIFO溢出 · 资源不可用

关键错误计数

错误计数器是丢包诊断的核心。我总结了三个最常见的,你排查时先看它们:

错误类型计数器名称含义典型原因
CRC错误rx_crc_err_cnt接收帧CRC校验失败的数量链路噪声、PHY芯片问题、接口松动
对齐错误rx_align_err_cnt帧未以字节边界结束(非8位对齐)物理层信号畸变、时钟抖动
过大帧rx_oversize_cnt帧长超过最大允许值(通常1522字节)软件bug、上层封包异常

个人经验:有一次排障,发现CRC错误计数器一直在涨但是链路误码率测试正常。后来我用示波器看MAC与PHY之间的MII总线,发现有一根信号线焊接虚连,导致偶尔采样错误。计数器是不会骗你的,但你需要读对寄存器。

寄存器结构

我不建议死记硬背每个寄存器地址,但得理解它的访问逻辑。标准MAC内核通常会把这堆计数器映射到一组连续的内存空间或PCIe BAR中。典型寄存器定义像这样:

/* 常见以太网MAC计数器寄存器偏移(假设基址0x1000) */
#define MAC_REG_TX_FRAME_CNT     0x1000   // 发送帧总数
#define MAC_REG_RX_FRAME_CNT     0x1004   // 接收帧总数
#define MAC_REG_RX_CRC_ERR_CNT   0x1008   // CRC错误计数
#define MAC_REG_RX_ALIGN_ERR_CNT 0x100C   // 对齐错误计数
#define MAC_REG_RX_OVERFLOW_CNT  0x1010   // FIFO溢出计数

读完寄存器后,我心里会算一个“丢包率”公式:

丢包率 = (RX_FRAME_CNT - RX_CRC_ERR_CNT - RX_ALIGN_ERR_CNT - RX_OVERFLOW_CNT) / RX_FRAME_CNT

这个值如果偏离1.0太多,说明链路或设备有硬伤。我曾经遇到过某个设备RX_FRAME_CNT居然比TX_FRAME_CNT还大——一定是芯片内部计数回环没清掉。那时候我直接调了PHY的loopback模式做自测,才锁定是MAC的统计路径问题。

寄存器访问注意事项

实际操作时,有几点容易踩坑:

  • 清零行为:很多MAC计数器是“读后清零”的。你读一次,数值就变成了0。所以在脚本采集时必须读完后马上保存,否则下一次就丢了。
  • 溢出翻转:32位计数器可能会溢出,需要记录上次值做差值计算。
  • 影子寄存器:有些芯片为了安全,会再备份一份内部复制,读到的可能不是实时值。这时要读两次取最新。

⚠️ 注意:我曾经被“读后清零”坑过。当时在调试脚本里连续读同一个计数器两次,第二次读永远是0。其实是MAC硬件自动清零了。解决方案是第一次读之前先让计数器保持,或者采集前软复位一次。所以老司机都会先写一个测试脚本验证清零行为。

嗯,等你用熟这些计数器,丢包诊断就会变得有章可循。下一轮我们可以继续聊怎么用这些计数器的组合来定位丢包源头。今天先到这,你回去把芯片datasheet翻出来,确认一下清零属性。