简介:IEEE 802.3-2022 以太网标准正式版全文面向网络设备研发工程师、协议栈开发者、测试认证人员及高校网络方向师生,用于查阅从 1Mb/s 到 400Gb/s 各速率的 MAC、MII、PHY 与 CSMA/CD 规范,支持产品设计选型、协议实现对照与标准符合性验证。资源包内为1个PDF文件,共7025页,体量约93.79MB,相比2018版新增1400余页,主要补充25G至100G相关条款并修订10G内容,同时整合此前各项修订;需注意802.3df-2024、802.3ck-2022、802.3db-2022等后续修订未纳入,涉及400/800Gb/s新进展时需另行查阅。文档按总则、MAC、各速率PHY、管理信息库与术语索引分层组织,便于按章节定位。已有287人学习下载,适合作为案头标准手册长期备查。
1. 7000 页不是冗余:以太网标准真正的野心是让 1 Mb/s 和 400 Gb/s 共用一套 MAC
翻开这份 802.3-2022 行业标准 IEEE802.3 文档,第一反应通常是:7025 页,是不是注水了?反直觉的地方在于,页数暴涨的那 1400 多页几乎全砸在 25G 到 100G 的 PMD、FEC 和接口细节上,而管帧格式的那几页,和上世纪八十年代几乎一模一样。同一套 MAC 既要伺候 1 Mb/s 的同轴电缆,也要伺候 400 Gb/s 的背板,靠的不是把旧东西删掉重写,而是在中间插了一层又一层适配壳。搞懂这层壳怎么长出来的,你才知道为什么换个光模块不用改驱动、为什么 100G 链路起不来时第一步该看协商而不是看光功率。
2. 分层与解耦:为什么换了物理层,MAC 一行都不用改
MAC 这一层做的事情少得可怜:组帧、填源和目的地址、算 32 位 CRC、按规则决定什么时候能发。它完全不知道对端是 10BASE-T 的双绞线还是 400GBASE-DR4 的并行光。真正让这个谎言成立的是夹在 MAC 和物理介质之间的一串子层——调和子层(RS)、介质无关接口(MII)、物理编码子层(PCS)、物理介质接入子层(PMA)、物理介质相关子层(PMD)。标准把这串东西写成一条流水线,每一层只跟上下相邻的层打交道,速率和介质的所有脏活都被关在 PCS 以下。
2.1 RS 与 MII:把 PHY 差异挡在 MAC 之外的薄壳
RS 的职责听上去微不足道:把 MAC 送来的串行位流按 MII 的位宽切开,发送时把前导码和 SFD 交出去,接收时把载波侦听和冲突检测的信号翻译成 MAC 认识的 PLS 原语。但正是这层“翻译”把 MAC 和 PHY 彻底解耦了。MAC 可以做成一份 RTL 或者一个软核,在 FPGA 里跑十年不动;PHY 从 100BASE-TX 换到 1000BASE-X 再到 25GBASE-R,换的是 MII 之上的握手方式和 MII 本身的形态,MAC 侧看到的仍然是“有载波 / 无载波 / 收到一个字节”。
常见实现里,MII 并不一定有对应的引脚。芯片内部模块之间直接按 MII 的时序约定传信号,甚至只在逻辑上遵守位宽和时钟关系;只有跨芯片、跨板卡时才落到真实的并行总线上。理解这一点,读标准时就不会纠结某个接口为什么既描述了引脚又描述了帧结构——它们描述的其实是同一个逻辑契约的两种落地形态。
2.2 MII 家族的退化史:并行位宽撞墙之后转向串行
这条演进线索值得单独拎出来,因为它解释了大量“为什么这个接口长这样”的疑问。早期的 MII 用 4 位并行、25 MHz 时钟凑出 100 Mb/s;千兆的 GMII 把位宽加到 8 位、时钟提到 125 MHz;万兆的 XGMII 直接把位宽拉到 32 位并改用双沿采样。位宽越加越宽,时钟越来越高,板级时序收敛越来越难,于是方向掉头:SGMII 之类改成串行,把时钟嵌进数据里,用 8B/10B 编码保证足够的跳变让接收端恢复时钟。
这个权衡可以用一行算式看清楚。
def mii_clock(rate_gbps: float, width_bits: int, ddr: bool = False) -> float: """ rate_gbps: 单向线速率,单位 Gb/s width_bits: 每个方向的并行位宽 ddr: 是否双沿采样(上下沿都传数据) 返回:该接口需要的时钟频率,单位 MHz """ edges_per_cycle = 2 if ddr else 1 return rate_gbps * 1000 / (width_bits * edges_per_cycle) for name, rate, width, ddr in [ ("MII", 0.1, 4, False), # 25 MHz,板级轻松 ("GMII", 1.0, 8, False), # 125 MHz,开始要关注走线 ("XGMII", 10.0, 32, True), # 156.25 MHz,32 位并行走线已经很难 ("CGMII", 100.0, 64, True), # 781.25 MHz,单总线已不现实 ]: print(f"{name:6s} {mii_clock(rate, width, ddr):8.2f} MHz")跑出来的 781.25 MHz 是个信号:到了 100G,靠加宽总线换低时钟这条路走到头了。所以 40G/100G 之后的 MII 在实现上被切成多条 lane,每条 lane 跑一个可以接受的速率,再在 PCS 里做 lane 分发和对齐。这也解释了标准里为什么反复出现“PCS lane marker”“lane 对齐”这类词——它们不是可选的优化,而是并行接口撑不住之后的必然产物。
提示:读某条 clause 里的位宽和时钟时,先算一遍这个式子。算出来超过 200 MHz 的并行总线,基本可以断定标准里会有配套的多 lane 方案。
2.3 帧格式里那个“一个字段两种含义”的历史包袱
以太网帧第 13、14 字节是一个被复用过的字段:小于等于 1500(0x05DC)时它表示后面 LLC 数据的长度,大于等于 1536(0x0600)时它表示上层协议类型。1501 到 1535 这 35 个值在标准里是未定义的缝隙。这个设计是历史妥协的化石——早期以太网需要一个长度字段让接收端知道有效载荷到哪结束,后来上层协议需要一个类型字段来做多路分解,标准干脆让同一个位置承担两种语义,用数值范围区分。
写解析代码时这个坑非常具体。
def decode_ethernet_frame(raw: bytes) -> dict: """ raw: 从目的 MAC 首字节开始、到 FCS 之前结束的字节串。 7 字节前导码 0xAA 和 1 字节 SFD 0xAB 已被 PHY/RS 剥掉。 """ if len(raw) < 14: raise ValueError("不足 14 字节,连 DA + SA + Type/Length 都不够") dst = ":".join(f"{b:02x}" for b in raw[0:6]) src = ":".join(f"{b:02x}" for b in raw[6:12]) tlv = int.from_bytes(raw[12:14], "big") if tlv >= 0x0600: # 1536 起按 EtherType 解释 mode, body = "ethertype", raw[14:] elif tlv <= 1500: # 1500 及以下按长度解释 mode, body = "length", raw[14:14 + tlv] else: # 1501~1535 是标准里留的缝隙 raise ValueError(f"0x{tlv:04x} 落在未定义区间,帧大概率被截断") # 最小帧 64 字节含 4 字节 FCS,不足的由发送端补零,接收端要靠长度判断 padded = len(raw) + 4 < 64 return {"dst": dst, "src": src, "tlv": tlv, "mode": mode, "payload_len": len(body), "padded": padded}几个参数要拎清楚。raw不包含 FCS,因为 CRC 一般在 MAC 里校验完就丢了;padded判断的是补零,46 字节的最小载荷是为了让帧从目的地址到 FCS 凑满 64 字节,这个数字直接来自下一章的冲突检测时间预算,不是随便定的;长度模式下body只取到长度字段声明的字节数,后面的补零必须丢掉,否则上层会读到垃圾。
2.4 MDI 上的另一件事:PoE 的检测与分级
介质相关接口(MDI)是以太网和电缆的物理分界,供电也发生在这里。标准把直流电压叠加在双绞线的中心抽头上,数据变压器对直流是隔离的,所以供电和数据互不干扰。关键在于上电之前的那几步:PSE 先输出一个 2.8 V 到 10 V 之间的探测电压,测签名电阻,标称 25 kΩ 左右;确认是对端设备之后才做分级,用电流或电压签名区分功率档位,最后才把完整电压加上去。
为什么探测电压压得这么低?因为任何合规 PHY 的输入端都能扛住 10 V,而一个不带 PoE 功能的普通网卡大概率没有那个 25 kΩ 签名,于是 PSE 就停在探测阶段不动。这是用极小的代价换来了“插错口不烧设备”的安全性。
| 类型 | PSE 输出上限 | PD 端可用功率 | 典型分级方式 |
|---|---|---|---|
| Type 1 | 15.4 W | 12.95 W | 单事件,Class 0~3 |
| Type 2 | 30 W | 25.5 W | 单事件 + 两级探测,Class 4 |
| Type 3 | 60 W | 51 W | 双事件分级,Class 5~6 |
| Type 4 | 90 W | 71.3 W | 双事件分级 + Autoclass,Class 7~8 |
表里 PSE 输出和 PD 可用之间那一截损耗是通道损耗,标准允许它就是这么大。单对线的场景另有 PoDL 的一套分级,原理相同但电压等级和类别数不同,别把两套参数混着用。
3. 从冲突检测到全双工:CSMA/CD 的时间预算为什么是 512 bit
半双工共享介质上,发送方必须在冲突信号传回来之前一直占着线,否则它会以为发送成功,冲突双方的数据全废。这条约束翻译成公式很短:最小帧的发送时间必须大于等于信号往返一圈的时间。10 Mb/s 时标准取了 512 bit 时间,也就是 51.2 微秒,配上同轴电缆的传播速度和最多四个中继器,就得到了那个著名的 2500 米网络直径。512 这个数字不是设计出来的,是被光速和当时的器件延迟逼出来的。
3.1 slot time 与网络直径:一个用光速算出来的数字
把这条约束写成一个可验证的预算,链路能不能在半双工下工作就一目了然。
def slot_time_us(speed_mbps: float, slot_bits: int = 512) -> float: """512 bit 时间在不同速率下折算成微秒,这就是往返预算的总盘子""" return slot_bits / speed_mbps def topology_delay_us(cable_len_m: float, repeaters: int, prop_us_per_km: float = 5.0, phy_delay_us: float = 0.5, repeater_delay_us: float = 0.5) -> float: """估一条拓扑占用多少往返时间:电缆按往返算,其余按单程延迟乘 2 或按个数算""" cable = 2 * (cable_len_m / 1000.0) * prop_us_per_km fixed = 2 * phy_delay_us + repeaters * repeater_delay_us return cable + fixed for speed, length, reps in [(10, 2500, 4), (100, 200, 2), (1000, 100, 0)]: budget = slot_time_us(speed) used = topology_delay_us(length, reps) flag = "够用" if used <= budget else "超预算" print(f"{speed:5d} Mb/s {length:5d} m {reps} 中继器 " f"预算 {budget:6.2f} µs 占用 {used:5.2f} µs {flag}")10 Mb/s 和 100 Mb/s 两行都还能过;到 1000 Mb/s 半双工,预算只剩 0.512 微秒,比一个 PHY 自身的延迟还小,物理上不可能满足。标准的解法是载波扩展:把短帧在物理层补齐到 512 字节再发,让占用介质的时间重新拉长到预算之内,再叠加帧突发把补齐的开销摊薄。这条补丁只在千兆半双工上存在过,属于典型的“为了兼容旧机制而付出的额外复杂度”。
参数上要留意prop_us_per_km取 5 是工程近似,实际电缆里电磁波速度大约是光速的 0.6 到 0.77 倍,折算下来每公里 4 到 5.5 微秒。phy_delay_us和repeater_delay_us才是真正吃掉预算的大头,这也是为什么速率每提一档,网络直径就断崖式下跌——不是电缆变差了,是固定延迟在越来越小的预算里占比越来越大。
3.2 全双工之后:冲突消失,但 Pause 帧和 buffer 顶了上来
全双工点对点链路里收发各走一对线,冲突在物理上不存在,CSMA/CD 的整套机制被整体旁路,最小帧长的约束也从“必须”退化成“保持格式兼容”。冲突这个矛盾消失之后,新的矛盾立刻补位:发送方比接收方快,接收端的缓冲填满就丢帧。
标准给的答案是 MAC 控制帧,也就是 Pause 帧。它用类型字段 0x8808 标记,操作码 0x0001,目的地址固定为 01-80-C2-00-00-01,这个地址被规定为“不转发”,所以暂停请求只影响相邻一跳。帧里带的参数以 512 bit 时间为单位,表示“请对方暂停多久”。设计上它是一条建议而不是命令:接收方可以完全忽略,也可以提前恢复发送。这个软约束在存储转发设备上带来的问题不小——上游停了,下游的缓冲还在往外吐,链路抖动会沿着路径累积。
真正踩过坑的人对这段历史都有血泪经验:后来在无损网络里演化出的按优先级流控,一旦形成环路就会出现暂停帧互相锁死,整片网络进入死锁,排查起来像黑匣子。这也从侧面说明,把流控做在 MAC 层、做成不带拓扑感知的单跳信号,本身就是个有局限的设计。
3.3 PLCA:把 CSMA/CD 的旧思路搬到单对线上做时分复用
单对双绞线的多点场景(一条线挂多个节点,没有交换机)重新把共享介质的问题带了回来。10 Mb/s 的速率下,往返预算又变得很紧,CSMA/CD 的退避算法在长线缆上效率极低。标准给出的方案是物理层冲突避免:在一个混合段里选一个协调者,由它周期性发出 BEACON 开启一轮,各节点按节点 ID 顺序依次拿到自己的发送机会,轮到谁谁发,没轮到的闭嘴。
这套机制的巧妙之处在于它把调度做在了 PHY 里而不是 MAC 里。MAC 看到的仍然是一个“可以发”的信号,完全不用改。轮转过程中还有一个提交阶段,用来让新上线的节点在不打乱现有顺序的前提下插进来。节点数被限制在很小的范围(混合段里最多 8 个),因为节点越多,一轮的时间越长,每个节点分到的带宽越少。
4. 物理层编码与协商:64B/66B、RS-FEC 和 AN 是怎么决定链路能不能起来的
一条链路起不来,九成的情况下问题不在光功率,而在两端没有对上同一套“语言”:编码方式、FEC 开关、lane 数量、甚至是否启用节能特性。这些参数全都在物理层协商阶段确定,而协商本身又必须在链路还没建立的时候完成,于是标准被迫设计了一套比正常速率慢得多、但极其鲁棒的通信方式。
4.1 编码谱系:4B/5B、8B/10B、64B/66B 与那段被浪费的带宽
线路编码要解决两件事:让接收端能从数据里恢复时钟(足够的电平跳变),以及保持直流平衡(不让变压器饱和)。4B/5B 和 8B/10B 都用 25% 的额外带宽换这两件事,在百兆和千兆时代这笔账划算。到了 100G,25% 意味着白扔 25 Gb/s 的线路带宽和相应的功耗,账算不过来了。
64B/66B 的做法是把 64 位载荷加 2 位同步头打成一块,开销降到 3.125%。跳变密度不再靠编码表保证,而是靠后面的自同步扰码器。同步头的两个比特只有 01(纯数据)和 10(含控制字符)两种合法取值,00 和 11 从未出现——这个性质让接收端可以在任意位置开一个 66 位的滑窗,靠非法组合快速找到块边界。
SYNC_LEGAL = {0b01: "data", 0b10: "control"} # 00 / 11 非法,可直接判错 def pack_block(block64: int) -> int: """64 位载荷 + 2 位同步头,拼成 66 位一块,开销 2/64 = 3.125%""" return (0b01 << 64) | (block64 & ((1 << 64) - 1)) def unpack_block(block66: int): """返回 (载荷, 类型);同步头非法时返回 (None, 错误说明)""" sync = (block66 >> 64) & 0b11 if sync not in SYNC_LEGAL: return None, "sync header 非法,块边界大概率丢了" return block66 & ((1 << 64) - 1), SYNC_LEGAL[sync]pack_block里那个掩码不能省,调用方传进来的高位如果没清干净,会污染同步头,接收端看到 11 就判定失锁。unpack_block的返回值设计成二元组而不是抛异常,是因为真实接收机在失锁后需要连续统计非法同步头的数量来决定是否重新对齐,单次失败不该中断整个接收流程。
4.2 FEC 与交织:为什么 100G 之后纠错成了必选项
PAM4 调制一个符号携带 2 比特,眼图从 NRZ 的一个变成三个,同样的噪声容限下信噪比要求陡增。速率的提升又把符号率推高,信道损耗加剧。结果就是原始误码率恶化到 1e-4 到 1e-5 这个量级,靠物理层的模拟均衡已经压不下去了,必须引入前向纠错。
100G 及以上的以太网用里德-所罗门码,作用在 GF(2^10) 上,一个符号 10 比特。两套参数值得记住:一套是 528 符号里 14 个校验符号,能纠 7 个符号错,开销约 2.7%;另一套是 544 符号里 30 个校验符号,能纠 15 个,开销约 5.8%。前者多用于电接口和背板,后者多用于光接口,因为光链路的信噪比更紧张。选哪套不是自由的,PMD 条款里写死了。
代价有三块。一是延迟,编解码本身加上交织深度,在 cut-through 转发设备上能明显测出来;二是功耗,FEC 逻辑在 400G 端口上是个不小的发热源;三是线路速率必须提高,因为要承载校验符号,实际波特率比标称速率高几个百分点。这三块代价换来的是误码率从 1e-4 量级降到 1e-15 以下,把“链路能通”变成了“链路能稳定跑满”。
交织是配合 FEC 的另一个手段。突发错误会连续打坏一串符号,超过纠错能力就整块报废;把编码后的符号按固定规则打散到多条 lane 上,突发错误就被摊成了分散的单符号错,落在纠错能力之内。代价同样是延迟,交织越深,延迟越大。
4.3 Auto-Negotiation 与 link training:两端怎么谈出共同语言
协商要解决一个鸡生蛋的问题:要谈速率,得先能通信;可通信本身依赖速率已经谈好。标准的解法是用一套独立于业务速率的低速信令。百兆和千兆用的是快速链路脉冲,一串 33 个脉冲,其中 16 个承载链路码字,17 个提供时钟信息,用最笨的曼彻斯特式跳变让对端即使频率对不准也能解出来。
链路码字的基页只有 16 位,塞不下多少东西。低位是 5 位选择符,标识这是以太网;中间一串是技术能力位,对端据此判断双方支持的交集;最高位是“还有下一页”标志,次高位是确认,再次是远程故障。能力不够就把后续的下一页翻出来,一页一页把 FEC、节能、通道参数这些扩展能力传过去。
| 字段(语义,位序简化) | 作用 |
|---|---|
| 选择符 | 标识使用的协议族,以太网是固定编码 |
| 技术能力位 | 声明支持的速率与双工组合,双方取交集 |
| 远程故障 | 告诉对端“我这边收不到有效信号” |
| 确认 | 握手应答,保证双方都读到了对方的页 |
| 下一页标志 | 能力太多时翻页承载扩展参数 |
背板和 10G 以上的铜缆场景用的是另一套基于差分曼彻斯特编码的协商,页更长,能塞进 FEC 意愿、lane 数量、通道损耗这些信息。协商完成之后还有一步 link training,两端用已知的训练序列反复调整发送端的均衡系数,把眼图调到最优,然后才切换到正常数据。这个过程对使用者是不可见的,但它是高速背板链路能起来的前提。
节能特性搭的是同一班车:支持低功耗闲置的一端在下一页里声明能力,双方都支持才启用。这也是排查时容易忽略的地方——链路在某些对端上起不来,原因可能是协商里某个可选能力位对不上,而不是物理层坏了。
5. 常见问题与排查:读这份 7025 页文档最容易翻车的五个地方
这份文档的体量和结构决定了它不好用。它不是一本从头读到尾的书,而是一份按条款号索引的契约集合,加上几十个可选特性矩阵。翻车通常不是因为技术难,而是因为找错了地方或者用错了版本假设。
5.1 现象:查不到 800G 的条款
现象很直接:按目录翻遍全篇,找不到 800 Gb/s 相关的内容,怀疑文档缺页或者下载不完整。
原因是版本边界。这份 2022 版是 2018 版的整体修订,
本文还有配套的精品资源,点击获取