4. Linux网络子系统基础

在 Layerscape 平台上开发或移植以太网驱动,如果只盯着寄存器手册和 DPAA/ENETC 的硬件描述,很容易写出“能收发但接不进内核协议栈”的代码。真正决定驱动能否被 Linux 网络子系统接纳的,是四个基础抽象:net_device 描述一个网络接口,sk_buff 承载一个报文,NAPI 决定收包在中断与轮询之间如何切换,网络设备驱动模型规定驱动必须向谁注册、暴露哪些回调。本节先把这四者的职责和交互讲清楚,再落到 Layerscape 驱动与它们的对接点上。

net_device:接口的身份与能力清单

net_device 是内核对“一块网卡”的软件表示。驱动在 probe 阶段通过 alloc_etherdev_mqs() 或 alloc_netdev_mqs() 分配它,随后填写关键字段:netdev_ops 指向一组回调(ndo_open、ndo_stop、ndo_start_xmit、ndo_set_rx_mode、ndo_change_mtu 等),ethtool_ops 提供链路与统计查询,dev->dev_addr 保存 MAC,features 声明校验和卸载、TSO、SG 等能力。最后用 register_netdev() 把接口挂进内核,用户空间才会看到 eth0/enetc0 之类的名字。

对 Layerscape 而言,net_device 的对接点集中在三处:一是 ndo_start_xmit,它把 sk_buff 交给 ENETC 的发送环或 DPAA 的 TX FQ;二是 ndo_open,负责申请中断、初始化 BD 环、启动 MAC;三是 ndo_set_rx_mode,把多播/混杂模式写入硬件过滤表。能力声明要与硬件一致,例如 ENETC 支持校验和卸载,就应在 features 中置位 NETIF_F_HW_CSUM,否则协议栈会做多余的软件校验。

sk_buff:一个报文在内核中的载体

sk_buff(简称 skb)不是“数据缓冲区”,而是描述一段报文及其元数据的结构体。它用 head/data/tail/end 四个指针划分出头部空间、已填充数据和尾部空间,用 len 表示线性区长度,用 frags 和 frag_list 支持非线性数据。收发路径上最常用的操作是:收包时 alloc_skb() 分配、用 skb_put() 扩展数据区;发包时用 skb_push()/skb_pull() 在头部增删协议头;跨层传递时用 skb_reserve() 预留头部空间。

Layerscape 驱动的对接点在于 DMA 与 skb 的绑定方式。ENETC 的 RX BD 指向的是 DMA 缓冲区,驱动通常预先分配一批 skb 并把 skb->data 的 DMA 地址写入 BD;收到报文后,用新的 skb 替换旧 skb,把旧 skb 交给 napi_gro_receive() 或 netif_receive_skb() 上送。DPAA 则通过 FMan 的 BM(Buffer Manager)管理缓冲区,驱动从 BM 池取 buffer 构造 skb。无论哪种方式,都要保证 DMA 方向、cache 一致性和 skb 生命周期三者匹配,否则会出现“报文内容错乱”或“skb 被提前释放”的典型故障。

NAPI:中断与轮询的折中

纯中断收包在高速率下会导致中断风暴,纯轮询又浪费 CPU。NAPI 的做法是:第一个报文到达时触发中断,中断处理函数只做最少的工作——关闭该队列中断、调用 napi_schedule() 把 NAPI 实例挂到软中断;随后由软中断上下文调用驱动的 poll 回调,在一次 poll 中批量收包,直到预算(budget)用完或队列为空,再重新打开中断。

Layerscape 的 ENETC 和 DPAA 都天然适合 NAPI:ENETC 每个 RX 环可对应一个 NAPI 实例,DPAA 的每个 RX FQ 也可独立调度。对接要点是:netif_napi_add() 注册 poll 函数并指定权重(常用 64),poll 中循环处理 BD 或 FQ 中的帧,返回本次实际处理的报文数;当返回数小于权重时表示队列已空,应调用 napi_complete_done() 并重新使能中断。多队列场景下,每个队列独立 NAPI,配合 RPS/RSS 可以把不同流分散到不同 CPU。

Linux 网络子系统与 Layerscape 驱动的对接关系 用户空间 socket / send / recv 协议栈 IP / TCP / 路由 / 邻居 net_device netdev_ops / features Layerscape 驱动 ENETC / DPAA sk_buff 生命周期(以 ENETC 收包为例) 预分配 skb DMA 地址写入 RX BD skb_reserve(headroom) 硬件收包 BD 写回,产生中断 DMA 数据已就绪 NAPI poll skb_put 设置 len 替换新 skb 回 BD 上送协议栈 napi_gro_receive() netif_receive_skb() NAPI 中断 / 轮询状态切换 中断使能 首包到达 → 触发 IRQ napi_schedule() 软中断轮询 poll(budget=64) 批量处理 BD / FQ 重新使能中断 napi_complete_done() 队列空 → 退出轮询 预算未用完,继续轮询 注:budget=64 为常见示例值,实际由驱动 netif_napi_add() 指定

网络设备驱动模型:注册、回调与生命周期

Linux 网络设备驱动模型与字符/块设备不同,它不通过 file_operations 暴露接口,而是通过 net_device + netdev_ops 把“接口”交给协议栈。驱动的生命周期大致是:模块加载 → probe 匹配设备树或 PCI ID → 分配 net_device → 初始化硬件与 DMA → register_netdev → 接口 up 时调用 ndo_open → 收发 → ndo_stop → unregister_netdev → 释放资源。Layerscape 的 ENETC 是 PCI 设备,DPAA 的 FMan/MAC 多通过设备树描述,两者都遵循这一模型,区别只在资源获取方式:ENETC 用 pci_iomap 映射 BAR,DPAA 用 of_iomap 映射寄存器并解析 phandle 找到 MAC 节点。

对接点小结与常见排障线索

把四者串起来看,Layerscape 驱动的对接点可以归纳为一张“责任表”:net_device 负责身份与能力,sk_buff 负责数据与元数据,NAPI 负责收包节奏,驱动模型负责注册与生命周期。实际调试时,如果 ifconfig 看不到接口,先查 register_netdev 是否成功;如果接口 up 但收不到包,查 ndo_open 是否申请了中断、NAPI 是否注册;如果收到包但内容错乱,查 DMA 映射与 cache 一致性;如果高负载下丢包,查 NAPI budget 与队列数量是否匹配 CPU 核数。这些线索都直接对应本节四个抽象,而不是硬件手册里的孤立寄存器。

建议在驱动 probe 中打印 net_device 的 name、features 和 NAPI 权重,在 poll 中统计每次处理的报文数,用 ethtool -S 观察 rx_packets/rx_dropped 的变化,能快速定位是“没收到”还是“收到了没上送”。