当前位置:首页>Linux>Linux 内核协议栈源码分析(一)

Linux 内核协议栈源码分析(一)

  • 2026-10-11 08:03:06
Linux 内核协议栈源码分析(一)

📌 引言

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) 混合了中断和轮询:

  1. 数据包到达时,网卡触发一次中断
  2. 驱动在中断处理中禁用中断,启动 NAPI 轮询
  3. 软中断(SOFTIRQ)周期性轮询网卡队列,批量取走数据包
  4. 队列为空时,重新启用中断
// 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() 是数据包从驱动层进入内核协议栈的关键入口函数。它负责:

  1. 将 sk_buff 传递给协议栈
  2. 根据协议类型(Ethernet Type)分发到对应的协议处理函数
  3. 处理桥接、VLAN、链路聚合等二层特性

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 内核协议栈的数据包接收入口:

  1. NAPI 机制
    :混合中断与轮询,避免中断风暴
  2. sk_buff 结构
    :零拷贝数据包处理的核心
  3. netif_receive_skb()
    :驱动层到协议栈的关键入口
  4. 协议注册机制
    :通过 ptype_base 哈希表分发数据包

📚 参考资料

  • Linux Kernel Source Code (v5.x/v6.x)
  • 《Understanding Linux Network Internals》Christian Benvenuti
  • 《Linux 内核源码剖析:TCP/IP 实现》
  • Kernel Documentation: networking/napi.rst

最新文章

随机文章