快速理解 net_device

4 阅读5分钟

快速理解 net_device (by Opus 4.6)

一句话定义

net_device 是 Linux 内核中所有网络接口的统一抽象结构体,无论是物理网卡还是虚拟设备,内核协议栈都通过同一个 net_device 接口与之交互。


类比理解

┌────────────────────────────────────────────────────────┐
│                                                        │
│   net_device 就像一个 "标准插座面板"                      │
│                                                        │
│   ┌──────────────┐                                     │
│   │              │  ← 上层协议栈只认识这个 "插座面板"     │
│   │  net_device  │     不关心背后接的是什么               │
│   │  (统一接口)   │                                     │
│   │              │                                     │
│   └──────┬───────┘                                     │
│          │                                             │
│    ┌─────┼──────┬──────────┬──────────┬──────────┐     │
│    │     │      │          │          │          │     │
│    ▼     ▼      ▼          ▼          ▼          ▼     │
│  物理   tap   veth      bridge     virtio    macvlan   │
│  网卡   设备   pair      端口       net       设备      │
│                                                        │
│  背后的 "电器" 各不相同,但插座接口是标准的                │
│                                                        │
└────────────────────────────────────────────────────────┘

核心组成

struct net_device {

    // ═══════════ 身份信息 ═══════════
    char            name[IFNAMSIZ];    // 接口名: "eth0", "tap0", "veth123"
    int             ifindex;           // 接口索引号: 1, 2, 3...
    unsigned char   dev_addr[ETH_ALEN];// MAC 地址
    unsigned int    mtu;               // 最大传输单元 (如 1500)
    unsigned int    flags;             // 状态标志 (IFF_UP, IFF_RUNNING...)
    unsigned short  type;              // 设备类型 (ARPHRD_ETHER, ARPHRD_LOOPBACK...)

    // ═══════════ 核心:操作函数表 ═══════════
    const struct net_device_ops  *netdev_ops;   // ⭐ 最重要!设备操作函数指针
    const struct ethtool_ops     *ethtool_ops;  // ethtool 操作

    // ═══════════ 收发包相关 ═══════════
    struct netdev_queue  *_tx;         // 发送队列
    struct netdev_rx_queue *_rx;       // 接收队列
    unsigned int    num_tx_queues;     // 发送队列数量
    unsigned int    num_rx_queues;     // 接收队列数量

    // ═══════════ 流量控制 ═══════════
    struct Qdisc    *qdisc;            // 排队规则 (TC 相关)

    // ═══════════ 统计信息 ═══════════
    struct net_device_stats stats;     // 收发包计数、错误计数等

    // ═══════════ 所属信息 ═══════════
    struct net      *nd_net;           // 所属的 Network Namespace
    
    // ... 还有很多其他字段
};

最重要的部分:net_device_ops

这是 net_device灵魂,定义了"这个网络接口具体怎么工作":

struct net_device_ops {

    // ═══════════ 生命周期 ═══════════
    int  (*ndo_open)(struct net_device *dev);       // ip link set up
    int  (*ndo_stop)(struct net_device *dev);       // ip link set down

    // ═══════════ 发送数据 ═══════════
    netdev_tx_t (*ndo_start_xmit)(                  // ⭐ 发送数据包
        struct sk_buff *skb,                        //    这是最核心的函数
        struct net_device *dev);

    // ═══════════ 配置管理 ═══════════
    int  (*ndo_set_mac_address)(...);               // 设置 MAC 地址
    int  (*ndo_change_mtu)(...);                    // 修改 MTU
    void (*ndo_set_rx_mode)(...);                   // 设置混杂模式等
    int  (*ndo_vlan_rx_add_vid)(...);               // 添加 VLAN

    // ═══════════ 统计信息 ═══════════
    void (*ndo_get_stats64)(...);                   // 获取统计
    
    // ... 还有几十个函数指针
};

不同设备的 ndo_start_xmit 实现

当协议栈调用 dev_queue_xmit(skb) 时,最终会调到 ndo_start_xmit()
不同的设备,这个函数的实现完全不同:

┌─────────────┬──────────────────────┬───────────────────────────┐
│ 设备类型     │ ndo_start_xmit 实现  │ 具体做了什么               │
├─────────────┼──────────────────────┼───────────────────────────┤
│ 物理网卡     │ ixgbe_xmit_frame()  │ 填充 DMA 描述符,通知硬件   │
│ (Intel 10G) │                      │ 网卡 DMA 读取并发送        │
├─────────────┼──────────────────────┼───────────────────────────┤
│ tap 设备     │ tun_net_xmit()      │ 将 skb 放入 socket 队列    │
│             │                      │ 唤醒 read() 等待的进程     │
├─────────────┼──────────────────────┼───────────────────────────┤
│ veth        │ veth_xmit()         │ 直接调用对端的 netif_rx()   │
│             │                      │ 数据立即出现在对端          │
├─────────────┼──────────────────────┼───────────────────────────┤
│ bridge 端口  │ br_dev_xmit()       │ 查 FDB 表,转发到目的端口   │
│             │                      │                           │
├─────────────┼──────────────────────┼───────────────────────────┤
│ OVS 端口    │ internal_dev_xmit() │ 进入 OVS 流表匹配流程      │
│             │                      │                           │
├─────────────┼──────────────────────┼───────────────────────────┤
│ loopback    │ loopback_xmit()     │ 直接调用 netif_rx() 回给自己│
│ (lo)        │                      │ 发送 = 接收               │
└─────────────┴──────────────────────┴───────────────────────────┘

数据包如何流经 net_device

发送路径

应用层 send()
    │
    ▼
TCP/UDP 层
    │
    ▼
IP 层
    │ 路由查找,确定出接口 → 找到对应的 net_device
    ▼
dev_queue_xmit(skb)                    ← 内核统一发送入口
    │
    │ skb->dev = 目标 net_device
    │
    ▼
Traffic Control (qdisc)                ← 流量控制 / TC 规则
    │
    ▼
net_device->netdev_ops->ndo_start_xmit(skb, dev)   ← ⭐ 多态调用
    │
    │  根据 dev 的实际类型,执行不同的发送逻辑
    │
    ├── 物理网卡 → DMA 发送到线缆
    ├── tap     → 放入队列,等用户空间进程读取
    ├── veth    → 直接交给对端
    └── bridge  → FDB 查表转发

接收路径

数据包到达(硬件中断 / 软件注入)
    │
    ▼
netif_receive_skb(skb)                 ← 内核统一收包入口
    │
    │ skb->dev = 收到数据的 net_device
    │
    ▼
内核判断该 net_device 的角色
    │
    ├── 普通接口 → 直接上送协议栈 (ip_rcv → tcp_rcv → socket)
    ├── bridge port → br_handle_frame() → bridge 转发逻辑
    ├── OVS port → ovs_vport_receive() → OVS 流表匹配
    └── bonding → bond_handle_frame() → bonding 处理

用面向对象思维理解

                    ┌──────────────────────┐
                    │   net_device (基类)    │
                    │                      │
                    │   name               │
                    │   mac_address        │
                    │   mtu                │
                    │   flags              │
                    │                      │
                    │   netdev_ops ────────┼──▶ 虚函数表 (类似 vtable)
                    │     .ndo_open()      │
                    │     .ndo_stop()      │
                    │     .ndo_start_xmit()│    ← "纯虚函数",每个子类必须实现
                    │                      │
                    └──────────┬───────────┘
                               │
            ┌──────────────────┼──────────────────┐
            │                  │                  │
    ┌───────┴──────┐  ┌───────┴──────┐  ┌───────┴──────┐
    │  物理网卡     │  │   tap 设备   │  │  veth 设备   │
    │  "子类"       │  │  "子类"      │  │  "子类"      │
    │              │  │              │  │              │
    │ start_xmit:  │  │ start_xmit:  │  │ start_xmit:  │
    │  DMA发送     │  │  放入队列     │  │  交给对端     │
    │              │  │  等进程读取   │  │              │
    │ 额外字段:    │  │ 额外字段:    │  │ 额外字段:    │
    │  DMA地址     │  │  socket队列  │  │  peer指针    │
    │  中断号      │  │  文件描述符   │  │              │
    └──────────────┘  └──────────────┘  └──────────────┘

Linux 内核用 C 语言实现了面向对象的多态

  • net_device 是"基类"
  • net_device_ops 是"虚函数表"
  • 不同设备类型的 ops 实现就是"方法重写"
  • 协议栈调用 ndo_start_xmit() 时,自动调用正确的实现

生命周期

创建                  注册                启用              使用              销毁
─────                ─────              ─────            ─────            ─────

alloc_netdev()   register_netdevice()   dev_open()    dev_queue_xmit()   unregister_netdevice()
      │                 │                  │          netif_receive_skb()        │
      ▼                 ▼                  ▼               │                    ▼
 分配内存          出现在系统中         IFF_UP 置位        正常收发包          从系统移除
 初始化字段        分配 ifindex        调用 ndo_open()                     free_netdev()
 设置 ops         通知用户空间        启动队列
                  (udev/systemd)

对应用户空间命令:

ip link add ...   (内核自动注册)    ip link set up    正常通信         ip link del ...

常用查看命令与 net_device 字段的对应

$ ip link show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel
    state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:ab:cd:ef brd ff:ff:ff:ff:ff:ff
字段对应关系:

  "2"                    →  net_device.ifindex
  "eth0"                 →  net_device.name
  "<BROADCAST,...,UP>"   →  net_device.flags
  "mtu 1500"             →  net_device.mtu
  "qdisc fq_codel"       →  net_device.qdisc->ops->id
  "state UP"             →  net_device.operstate
  "qlen 1000"            →  net_device.tx_queue_len
  "52:54:00:ab:cd:ef"    →  net_device.dev_addr

一句话总结

net_device 是 Linux 内核对所有网络接口的统一抽象,它通过 net_device_ops 函数指针表实现多态——上层协议栈调用相同的 dev_queue_xmit() / netif_receive_skb() 接口,不同类型的设备(物理网卡、tap、veth、bridge...)在 ndo_start_xmit() 中各自实现自己的发送逻辑,对协议栈完全透明