Android内核网络栈调试与驱动数据通路优化 - 第2节

2.1 struct net_device 结构体详解

struct net_device 是 Linux 内核网络子系统中最重要的数据结构之一,它抽象了一个网络设备的所有属性和操作。无论是物理网卡、虚拟网桥还是 VLAN 接口,在内核中都以 struct net_device 实例表示。理解其内部布局是驱动开发和网络栈调试的基础。

2.1.1 核心字段分类

struct net_device 包含数百个字段,按功能可划分为以下几类:

分类关键字段说明
标识name, dev_id, ifindex设备名称(如 eth0)、设备 ID、全局唯一接口索引
配置mtu, flags, features最大传输单元、IFF_UP 等标志位、设备特性位图
地址dev_addr, broadcast, perm_addrMAC 地址、广播地址、永久硬件地址
统计stats, tstats, lstats收发统计计数(包数、字节数、错误数)
操作函数netdev_ops, ethtool_ops设备打开/关闭/发送等操作函数表
NAPInapi_list, poll_controllerNAPI 轮询列表、轮询控制函数
队列rx_queue, tx_queue, ingress_queue接收/发送/入口队列管理结构
链路层type, hard_header_len, addr_len链路类型(ARPHRD_ETHER)、头部长度、地址长度

调试要点: 在 Android 内核调试中,通过 /proc/net/dev 可查看所有 net_device 的统计信息;使用 cat /sys/class/net/<ifname>/ 可访问设备属性。驱动开发时最常操作的是 netdev_ops 和 features 字段。

2.1.2 netdev_ops 操作函数表

每个网络设备驱动必须实现 struct net_device_ops 中的核心回调函数:

struct net_device_ops {
    int (*ndo_open)(struct net_device *dev);
    int (*ndo_stop)(struct net_device *dev);
    netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev);
    void (*ndo_get_stats64)(struct net_device *dev, struct rtnl_link_stats64 *stats);
    int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
    int (*ndo_change_mtu)(struct net_device *dev, int new_mtu);
    int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
    void (*ndo_poll_controller)(struct net_device *dev);
    int (*ndo_set_features)(struct net_device *dev, netdev_features_t features);
};

驱动开发提示: ndo_start_xmit 是数据发送的核心入口,必须在原子上下文中快速返回。Android 设备中,Wi-Fi 和 Cellular 驱动的 ndo_start_xmit 常需要处理 skb 的 DMA 映射和硬件队列管理。

2.1.3 设备特性位图 (features)

net_device->features 使用位图表示设备硬件卸载能力,直接影响网络栈的数据处理路径:

特性标志含义影响
NETIF_F_SG支持分散/聚集 I/O允许 skb 使用多个片段
NETIF_F_IP_CSUMIPv4 TCP/UDP 校验和卸载减少 CPU 计算开销
NETIF_F_HW_VLAN_CTAG_RX硬件 VLAN 剥离接收时自动去除 VLAN 标签
NETIF_F_TSOTCP 分段卸载大包直接交给网卡分段
NETIF_F_LRO大接收卸载合并接收包减少中断
NETIF_F_RXCSUM接收校验和卸载硬件验证校验和

调试陷阱: Android 内核中,某些虚拟设备(如 tun、veth)不支持硬件卸载特性。若驱动错误声明 features,会导致 skb 校验和错误或数据损坏。使用 ethtool -k <ifname> 可查看当前启用的特性。

2.1.4 struct net_device 内部结构示意

下图展示了 struct net_device 的核心字段组织关系:

struct net_device 核心字段结构 struct net_device 标识字段 name (char[16]) ifindex (int) dev_id (int) 配置字段 mtu (unsigned int) flags (unsigned short) features (netdev_features_t) 操作函数表 netdev_ops ethtool_ops header_ops 统计字段 stats (net_device_stats) tstats / lstats pcpu_stat 队列管理 rx_queue (netdev_queue) tx_queue (netdev_queue) ingress_queue NAPI 相关 napi_list (struct list_head) poll_controller napi_weight (int) 调试与开发关注点 • 驱动注册时需正确初始化 netdev_ops 和 features • 统计字段用于排查丢包和错误,通过 /proc/net/dev 查看 • NAPI 相关字段影响中断与轮询的切换阈值 • 队列管理字段在流控和 QoS 调试中至关重要

2.2 网络设备注册与注销流程

网络设备从创建到销毁经历完整的生命周期管理。驱动必须遵循内核提供的 API 进行注册和注销,否则会导致设备不可用或内核崩溃。

2.2.1 设备注册流程

标准注册流程包含以下步骤:

  1. 分配 net_device:调用 alloc_netdev() 或 alloc_etherdev() 分配设备结构体
  2. 初始化私有数据:驱动填充 netdev_ops、ethtool_ops、features 等字段
  3. 设置硬件参数:配置 MAC 地址、MTU、队列数等
  4. 注册设备:调用 register_netdev() 将设备加入内核网络子系统
  5. 启动设备:通过 netif_carrier_on() 和 netif_start_queue() 激活链路和发送队列
// 典型驱动注册代码片段
struct net_device *ndev;
struct my_priv *priv;

// 1. 分配设备(以太网设备,额外私有数据大小 sizeof(*priv))
ndev = alloc_etherdev(sizeof(struct my_priv));
if (!ndev)
    return -ENOMEM;

// 2. 设置操作函数表
ndev->netdev_ops = &my_netdev_ops;
ndev->ethtool_ops = &my_ethtool_ops;

// 3. 设置特性
ndev->features |= NETIF_F_IP_CSUM | NETIF_F_SG;

// 4. 设置 MAC 地址(从硬件读取)
dev_addr_set(ndev, hw_mac);

// 5. 注册设备
ret = register_netdev(ndev);
if (ret) {
    free_netdev(ndev);
    return ret;
}

// 6. 激活链路
netif_carrier_on(ndev);
netif_start_queue(ndev);

Android 特有场景: 在 Android 内核中,register_netdev() 会触发 NETDEV_REGISTER 通知链,netd 守护进程通过 netlink 监听此事件并配置 IP 地址。驱动开发者需确保注册时机正确,避免在设备未就绪时暴露给用户空间。

2.2.2 设备注销流程

注销流程是注册的逆操作,需确保所有引用被释放:

  1. 停止队列:调用 netif_stop_queue() 停止发送
  2. 关闭链路:调用 netif_carrier_off() 通知链路断开
  3. 注销设备:调用 unregister_netdev() 从内核移除
  4. 释放设备:调用 free_netdev() 释放内存
// 典型驱动注销代码片段
void my_remove(struct platform_device *pdev)
{
    struct net_device *ndev = platform_get_drvdata(pdev);

    // 1. 停止发送队列
    netif_stop_queue(ndev);

    // 2. 关闭链路
    netif_carrier_off(ndev);

    // 3. 注销设备(触发 NETDEV_UNREGISTER 通知链)
    unregister_netdev(ndev);

    // 4. 释放私有数据和设备结构体
    free_netdev(ndev);
}

调试陷阱: 注销时若存在未释放的 skb 或未完成的 DMA 操作,会导致内存泄漏或硬件异常。建议在 ndo_stop 回调中清空所有待处理数据包,并等待 DMA 完成后再调用 unregister_netdev()。

2.2.3 注册与注销流程示意

网络设备注册与注销流程 注册流程 alloc_netdev() 初始化 netdev_ops 设置 features / MAC register_netdev() netif_carrier_on() 注销流程 netif_stop_queue() netif_carrier_off() unregister_netdev() free_netdev() 关键通知链事件 register_netdev() → NETDEV_REGISTER → 用户空间 netlink 事件 unregister_netdev() → NETDEV_UNREGISTER → 清理路由和地址

2.3 NAPI 轮询机制原理

NAPI(New API)是 Linux 内核为高性能网络设备设计的混合中断-轮询机制。它解决了传统中断驱动模型在高吞吐量场景下的“中断风暴”问题,是 Android 设备中 Wi-Fi、以太网和 Cellular 驱动的标准数据接收方式。

2.3.1 传统中断 vs NAPI 对比

特性传统中断模式NAPI 模式
数据接收方式每个包触发一次中断中断后切换为轮询
高吞吐性能中断风暴导致 CPU 100%轮询减少中断开销
低延迟场景延迟低(立即响应)需等待轮询周期
CPU 占用中断上下文频繁切换绑定到特定 CPU 核心
适用场景低速设备(10Mbps 以下)高速设备(100Mbps 以上)

2.3.2 NAPI 工作流程

NAPI 的核心思想是:在数据流量低时使用中断,流量高时自动切换为轮询。具体流程如下:

  1. 中断触发:网卡收到数据包,触发硬件中断
  2. 中断处理:ISR 调用 napi_schedule() 将设备的 NAPI 结构加入轮询列表
  3. 关闭中断:NAPI 框架自动屏蔽该设备的接收中断
  4. 轮询回调:内核在软中断上下文中调用驱动的 poll 函数
  5. 批量处理:poll 函数从硬件读取多个包,通过 netif_receive_skb() 送入协议栈
  6. 退出条件:当处理完所有包或达到预算(budget)时,重新开启中断
// NAPI poll 函数典型实现
static int my_poll(struct napi_struct *napi, int budget)
{
    struct my_priv *priv = container_of(napi, struct my_priv, napi);
    int work_done = 0;
    struct sk_buff *skb;

    while (work_done < budget) {
        skb = my_hw_read_packet(priv);  // 从硬件读取数据包
        if (!skb)
            break;

        // 送入网络协议栈
        netif_receive_skb(skb);
        work_done++;
    }

    // 如果处理完所有包,重新开启中断
    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        my_hw_enable_interrupt(priv);  // 重新使能硬件中断
    }

    return work_done;
}

// 中断处理函数
irqreturn_t my_interrupt(int irq, void *dev_id)
{
    struct net_device *ndev = dev_id;
    struct my_priv *priv = netdev_priv(ndev);

    // 关闭硬件中断(NAPI 接管)
    my_hw_disable_interrupt(priv);

    // 调度 NAPI 轮询
    napi_schedule(&priv->napi);

    return IRQ_HANDLED;
}

性能优化要点: budget 参数控制单次轮询处理的最大包数,默认值为 64。在 Android 设备中,Wi-Fi 驱动的 budget 可调整至 128-256 以提升吞吐量,但需注意软中断执行时间不能过长(建议 < 1ms)。

2.3.3 NAPI 状态机与调度流程

NAPI 状态机与调度流程 NAPI_STATE_SCHED 未调度(中断使能) 硬件中断触发 ISR 调用 napi_schedule() NAPI 轮询中 poll() 回调执行 数据包到达 napi_schedule() 处理完成 work_done < budget 重新使能中断 napi_complete_done() 继续轮询 work_done == budget ← 上一章 📖 返回目录 下一章 →