📌 引言
Linux 内核网络协议栈是操作系统最核心的子系统之一,负责处理所有网络通信。理解其工作原理,对于网络性能优化、故障排查、以及深入理解操作系统至关重要。本文作为系列第一篇,将聚焦数据包接收的入口路径,从网卡驱动到 netif_receive_skb() 函数,带您领略内核协议栈的精妙设计。🔍 第一节:数据包接收全景图
1.1 整体处理流程
当一个网络数据包到达网卡时,会经历以下处理路径:
📦 数据包到达网卡 ↓ 💾 DMA 写入 Ring Buffer ↓ ⚡ 硬件中断 / NAPI 轮询 ↓ 🔧 网卡驱动处理(igb/e1000e/mlx5 等) ↓ 📤 netif_receive_skb() ← 本节重点 ↓ 🌐 协议栈处理(IP → TCP/UDP → Socket) ↓ 📱 应用层 recv() 系统调用返回。
💡 重要提示:本文基于 Linux 内核 5.x/6.x 版本分析,不同版本间函数名可能略有差异,但核心逻辑保持一致。
🧠 核心数据结构:sk_buff
在深入代码之前,必须先理解 Linux 网络协议栈的核心数据结构——sk_buff(Socket Buffer),简称 skb。
2.1 sk_buff 结构体关键字段
// include/linux/skbuff.h (简化版) struct sk_buff { /* 数据包数据区指针 */ unsigned char *head; /* 缓冲区起始地址 */ unsigned char *data; /* 当前协议层数据起始 */ unsigned char *tail; /* 当前协议层数据结束 */ unsigned char *end; /* 缓冲区结束地址 */ /* 协议层信息 */ __be16 protocol; /* 以太网类型 (0x0800=IPv4) */ __u16 transport_header; /* 传输层头部偏移 */ __u16 network_header; /* 网络层头部偏移 */ __u16 mac_header; /* 链路层头部偏移 */ /* 设备与套接字 */ struct net_device *dev; /* 接收/发送设备 */ struct sock *sk; /* 关联的套接字 */ /* 校验和与标记 */ __u16 checksum; /* 校验和 */ __u32 priority; /* QoS 优先级 */ __u32 mark; /* 防火墙标记 */ /* 链表指针 */ struct sk_buff *next; struct sk_buff *prev; };
2.2 数据结构关系图
⚙️ 网卡驱动与 NAPI
3.1 传统中断模式 vs NAPI
早期 Linux 使用传统中断模式:每个数据包到达都触发一次中断,高流量下会导致中断风暴,CPU 被大量中断占据。
NAPI(New API) 混合了中断和轮询:
- 软中断(SOFTIRQ)周期性轮询网卡队列,批量取走数据包
// drivers/net/ethernet/intel/igb/igb_main.c staticirqreturn_tigb_msix_ring(int irq, void *data){ struct igb_q_vector *q_vector = data; /* 禁用中断 */ igb_irq_disable_queues(q_vector); /* 调度 NAPI 轮询 */ napi_schedule(&q_vector->napi); return IRQ_HANDLED; }
3.2 NAPI 轮询函数
以 Intel igb 驱动为例,NAPI 轮询函数为 igb_poll():// drivers/net/ethernet/intel/igb/igb_main.c static int igb_poll(struct napi_struct *napi, int budget) { struct igb_q_vector *q_vector = container_of(napi, struct igb_q_vector, napi); int work_done = 0; /* 清理 Tx 队列 */ if (q_vector->tx_ring) igb_clean_tx_irq(q_vector); /* 接收数据包 */ if (q_vector->rx_ring) work_done = igb_clean_rx_irq(q_vector, budget); /* 如果没用完 budget,重新启用中断 */ if (work_done < budget) { napi_complete_done(napi, work_done); igb_irq_enable_queues(q_vector); } return work_done; }
🚪 关键入口:netif_receive_skb()
4.1 函数作用
netif_receive_skb() 是数据包从驱动层进入内核协议栈的关键入口函数。它负责:
- 根据协议类型(Ethernet Type)分发到对应的协议处理函数
4.2 源码分析-》代码示例
// net/core/dev.c intnetif_receive_skb(struct sk_buff *skb) { return netif_receive_skb_internal(skb, 0); } EXPORT_SYMBOL(netif_receive_skb); staticintnetif_receive_skb_internal(struct sk_buff *skb, int pfmemalloc) { struct netdev_queue *rxq; int ret; /* 记录接收时间戳 */ net_timestamp_check(0, skb); /* RPS (Receive Packet Steering) 处理 */ if (static_branch_unlikely(&rps_needed)) { struct rps_dev_flow void_flow; int cpu = get_rps_cpu(skb->dev, skb, &void_flow); if (cpu >= 0) { ret = enqueue_to_backlog(skb, cpu, &rps_dev_flow_table[cpu]); goto done; } } /* 核心处理:调用 __netif_receive_skb */ ret = __netif_receive_skb(skb); done: return ret; }
4.3 __netif_receive_skb() 核心逻辑-》代码示例
// net/core/dev.c static int __netif_receive_skb(struct sk_buff *skb) { struct packet_type *ptype, *pt_prev; struct net_device *orig_dev; int ret = NET_RX_SUCCESS; __be16 type; /* 保存原始设备 */ orig_dev = skb->dev; /* 处理桥接 */ if (skb->dev->priv_flags & IFF_BRIDGE_PORT) { /* 交给桥接子系统处理 */ ret = br_handle_frame(skb); if (ret != RX_HANDLER_PASS) goto out; } /* 处理 VLAN */ if (skb_vlan_tag_present(skb)) { /* VLAN 标签处理 */ skb = vlan_untag(skb); if (!skb) goto out; } /* 获取协议类型 */ type = skb->protocol; /* 遍历协议处理链表,找到匹配的协议处理函数 */ list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) { if (ptype->type != type) continue; if (pt_prev) { ret = deliver_skb(pt_prev, skb, orig_dev); pt_prev = NULL; } /* 调用协议处理函数(如 ip_rcv) */ ret = ptype->func(skb, skb->dev, ptype, orig_dev); } out: return ret; }
📊 协议注册机制
5.1 如何注册协议处理函数?
协议处理函数通过 dev_add_pack() 注册到内核:
// net/core/dev.c voiddev_add_pack(struct packet_type *pt){ struct list_head *head = &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK]; /* 添加到哈希表 */ list_add_rcu(&pt->list, head); } // include/linux/netdevice.h struct packet_type { __be16 type; /* 协议类型 */ struct net_device *dev; /* 绑定设备(NULL=所有) */ int (*func)(struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *); void *af_packet_priv; struct list_head list; };
5.2 IPv4 协议注册示例
// net/ipv4/af_inet.c static struct packet_type ip_packet_type __read_mostly = { .type = cpu_to_be16(ETH_P_IP), /* 0x0800 */ .func = ip_rcv, /* IPv4 处理函数 */ }; static int __initinet_init(void) { /* 注册 IPv4 协议处理函数 */ dev_add_pack(&ip_packet_type); /* 注册 TCP、UDP、ICMP 等传输层协议 */ /* ... */ }
🎯 实战:数据包接收路径追踪
6.1 使用 ftrace 追踪函数调用
我们可以通过 ftrace 动态追踪内核函数调用,验证上述流程:# 启用函数追踪 echo 1 > /sys/kernel/debug/tracing/events/net/netif_receive_skb/enable echo 1 > /sys/kernel/debug/tracing/events/net/ip_rcv/enable # 发送测试数据包 ping -c 1 192.168.1.1 # 查看追踪日志 cat /sys/kernel/debug/tracing/trace # 输出示例: # ping-1234 [002] ..s1 12345.678901: netif_receive_skb: dev=eth0 skb=ffff88003c8c8c00 len=84 # ping-1234 [002] ..s1 12345.678902: ip_rcv: skb=ffff88003c8c8c00 len=84
6.2 使用 dropwatch 监控丢包
# 安装 dropwatch yum install dropwatch # 启动监控 dropwatch -l kas > start # 观察内核丢包点
📝 小结与预告
本文详细分析了 Linux 内核协议栈的数据包接收入口:
- NAPI 机制
- sk_buff 结构
- netif_receive_skb()
- 协议注册机制
📚 参考资料
- Linux Kernel Source Code (v5.x/v6.x)
- 《Understanding Linux Network Internals》Christian Benvenuti
- Kernel Documentation: networking/napi.rst