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 网络设备驱动模型与字符/块设备不同,它不通过 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 核数。这些线索都直接对应本节四个抽象,而不是硬件手册里的孤立寄存器。