快速理解 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()中各自实现自己的发送逻辑,对协议栈完全透明。