一、Linux 网卡驱动模型:总线 - 设备 - 驱动

1.1 经典范式:报名 → 匹配 → 干活 → 上线

Linux 的设备驱动模型遵循一个优雅的范式,可以类比为给新生儿上户口、做体检、办身份证、最后上学的流程:

┌─────────────────────────────────────────────────────────────┐
│           Linux 网卡驱动模型:四步走                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   阶段一:驱动注册(报名)                                   │
│   ┌─────────────────────────────────────────────────┐      │
│   │  pci_register_driver(&igb_driver)               │      │
│   │                                                 │      │
│   │  内核驱动链表 ←─────── igb_driver               │      │
│   │  (此时驱动只是"登记在册",还没开始干活)          │      │
│   └─────────────────────────────────────────────────┘      │
│                         │                                   │
│                         ▼                                   │
│   阶段二:设备匹配(相亲)                                   │
│   ┌─────────────────────────────────────────────────┐      │
│   │  内核扫描 PCI 总线                              │      │
│   │  比对:Vendor ID + Device ID vs id_table       │      │
│   │  匹配成功!→ 立即调用 probe 函数                │      │
│   └─────────────────────────────────────────────────┘      │
│                         │                                   │
│                         ▼                                   │
│   阶段三:Probe 初始化(干活)                               │
│   ┌─────────────────────────────────────────────────┐      │
│   │  ① 获取 MAC 地址                               │      │
│   │  ② DMA 初始化(Ring Buffer)                    │      │
│   │  ③ 注册 net_device_ops                          │      │
│   │  ④ NAPI 初始化,注册 poll 函数                  │      │
│   │  ⑤ 注册 ethtool 函数                           │      │
│   │  ⑥ 注册 net_device(上户口)                    │      │
│   └─────────────────────────────────────────────────┘      │
│                         │                                   │
│                         ▼                                   │
│   阶段四:启动网卡(上线)                                   │
│   ┌─────────────────────────────────────────────────┐      │
│   │  ifconfig eth0 up                               │      │
│   │  → igb_open()                                   │      │
│   │  → 分配队列内存 → 注册中断 → 启动 NAPI         │      │
│   │  → 网卡进入"待命"状态                           │      │
│   └─────────────────────────────────────────────────┘      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 为什么需要这套模型?

这套模型实现了驱动与设备的解耦

好处 说明
热插拔支持 设备插入时自动匹配驱动,无需重启
模块化 一个驱动可支持多种设备型号
统一接口 内核无需关心具体硬件,调用统一接口即可

二、Probe 函数:初始化七步曲

当驱动与设备匹配成功后,内核会调用驱动的 probe 函数。这是网卡初始化的核心阶段。

2.1 第一步:获取 MAC 地址

┌─────────────────────────────────────────────────────────────┐
│              获取 MAC 地址:网卡的身份证                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   驱动代码流程:                                            │
│                                                             │
│   1. ioremap() ──► 把硬件寄存器映射到内核虚拟地址           │
│                                                             │
│   2. 读取网卡 EEPROM 或特定寄存器                            │
│                                                             │
│   3. 获取 48 位 MAC 地址                                    │
│      ┌────┬────┬────┬────┬────┬────┐                       │
│      │ 00│ 1A│ 2B│ 3C│ 4D│ 5E │  ◄── 全球唯一              │
│      └────┴────┴────┴────┴────┴────┘                       │
│                                                             │
│   4. 保存到 net_device 结构体的 dev_addr 字段              │
│                                                             │
│   为什么重要?后续所有网络通信都基于这个地址进行             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.2 第二步:DMA 初始化

┌─────────────────────────────────────────────────────────────┐
│              DMA 初始化:高性能的基础                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   传统方式(CPU 搬运):                                    │
│   网卡 ──✗──► CPU ──► 内存                                  │
│              慢!CPU 被占用                                  │
│                                                             │
│   DMA 方式(直接内存访问):                                │
│   网卡 ──✓──► 内存(绕过 CPU)                              │
│              快!CPU 解放                                    │
│                                                             │
│   驱动操作:                                                │
│   1. 在内存中申请一块区域作为环形缓冲区(Ring Buffer)       │
│   2. 将这块内存的物理地址告诉网卡硬件                        │
│   3. 网卡收到数据后,直接写入这块内存                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.3 第三步:注册 net_device_ops

┌─────────────────────────────────────────────────────────────┐
│         net_device_ops:网卡的"能力说明书"                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   这是一个函数指针集合,驱动填充后注册到 net_device:        │
│                                                             │
│   ┌─────────────────────────────────────────┐               │
│   │  struct net_device_ops igb_netdev_ops = │               │
│   │  {                                      │               │
│   │      .ndo_open         = igb_open,      │  ← 启动网卡    │
│   │      .ndo_stop         = igb_stop,      │  ← 停止网卡    │
│   │      .ndo_start_xmit   = igb_xmit_frame,│  ← 发送数据包  │
│   │      .ndo_set_mac_address = ...,        │  ← 设置 MAC    │
│   │      .ndo_change_mtu   = ...,          │  ← 修改 MTU    │
│   │  };                                     │               │
│   └─────────────────────────────────────────┘               │
│                                                             │
│   为什么需要?                                              │
│   内核上层协议栈只需调用 dev->netdev_ops->ndo_start_xmit()  │
│   无需关心底层是 igb 还是 e1000 网卡 ──► 完美解耦           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.4 第四步:NAPI 初始化

┌─────────────────────────────────────────────────────────────┐
│         NAPI:解决"中断风暴"的利器                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   问题:高速流量下,每个包都触发中断 → CPU 不堪重负          │
│                                                             │
│   ┌─────────────────────────────────────────────────┐      │
│   │            NAPI:中断 + 轮询的混合机制            │      │
│   └─────────────────────────────────────────────────┘      │
│                                                             │
│   流程:                                                    │
│   ┌──────────┐    1 个中断    ┌──────────────┐               │
│   │ 网卡收到包 │ ──────────► │ 屏蔽后续中断  │               │
│   └──────────┘              └──────┬───────┘               │
│                                    │                        │
│                                    ▼                        │
│                           ┌──────────────┐                  │
│                           │ 内核主动轮询  │                  │
│                           │ 批量取数据包  │  ◄── igb_poll   │
│                           └──────┬───────┘                  │
│                                    │                        │
│                                    ▼                        │
│                           ┌──────────────┐                  │
│                           │ 处理完重新   │                  │
│                           │ 开启中断     │                  │
│                           └──────────────┘                  │
│                                                             │
│   优势:高流量时批量处理高效,低流量时中断响应及时           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.5 第五步:注册 ethtool 函数

┌─────────────────────────────────────────────────────────────┐
│         ethtool:网卡配置与诊断工具的接口                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ethtool 是 Linux 下强大的网卡管理工具:                    │
│                                                             │
│   $ ethtool eth0          # 查看网卡信息                     │
│   $ ethtool -i eth0       # 查看驱动版本                    │
│   $ ethtool -S eth0       # 查看统计信息                    │
│   $ ethtool -K eth0 gro on # 开启 GRO                     │
│                                                             │
│   驱动需要实现的函数:                                       │
│   ┌─────────────────────────────────────────┐               │
│   │  get_drvinfo      → 获取驱动版本等信息   │               │
│   │  get_settings     → 获取网卡参数         │               │
│   │  set_settings     → 修改网卡参数         │               │
│   │  get_stats        → 获取统计信息         │               │
│   │  set_ringparam    → 设置 Ring 大小       │               │
│   └─────────────────────────────────────────┘               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.6 第六步 & 第七步:注册 net_device

┌─────────────────────────────────────────────────────────────┐
│         注册 net_device:正式"上户口"                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   调用 register_netdev():                                  │
│                                                             │
│   ┌─────────────────────────────────────────────────┐      │
│   │  net_device 结构体                                │      │
│   │  ┌─────────────────────────────────────────────┐ │      │
│   │  │  .name = "eth0"                             │ │      │
│   │  │  .dev_addr = MAC 地址                       │ │      │
│   │  │  .netdev_ops = &igb_netdev_ops              │ │      │
│   │  │  .ethtool_ops = &igb_ethtool_ops            │ │      │
│   │  │  .napi = NAPI 结构                          │ │      │
│   │  │  ...                                        │ │      │
│   │  └─────────────────────────────────────────────┘ │      │
│   └─────────────────────────────────────────────────┘      │
│                         │                                   │
│                         ▼                                   │
│              内核网络子系统链表                              │
│                         │                                   │
│                         ▼                                   │
│          用户可见:ifconfig -a / ip link show               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

三、启动网卡:兵马未动,粮草先行

3.1 ifconfig eth0 up 做了什么?

┌─────────────────────────────────────────────────────────────┐
│              ifconfig up 完整流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   用户执行: ifconfig eth0 up                                │
│         │                                                  │
│         ▼                                                  │
│   ┌─────────────────────────────────────────────────────┐  │
│   │               内核网络子系统                          │  │
│   └─────────────────────────────────────────────────────┘  │
│         │                                                  │
│         ▼                                                  │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  1. 查找 eth0 对应的 net_device 结构                 │  │
│   │  2. 调用 dev->netdev_ops->ndo_open (igb_open)       │  │
│   └─────────────────────────────────────────────────────┘  │
│         │                                                  │
│         ▼                                                  │
│   ┌─────────────────────────────────────────────────────┐  │
│   │               igb_open 函数执行:                     │  │
│   │                                                     │  │
│   │  ① igb_setup_all_rx_resources()                   │  │
│   │     → 为接收队列分配 DMA 缓冲区和描述符数组          │  │
│   │                                                     │  │
│   │  ② request_irq()                                  │  │
│   │     → 向内核注册中断处理函数                        │  │
│   │     → 网卡有数据时 CPU 知道该执行哪段代码           │  │
│   │                                                     │  │
│   │  ③ napi_enable()                                   │  │
│   │     → 正式启用 NAPI 机制                            │  │
│   │                                                     │  │
│   │  ④ 打开硬件中断(写网卡寄存器)                    │  │
│   │     → 网卡进入"待命"状态                            │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.2 SKB 预分配机制

这是理解高性能网络收包的关键!

┌─────────────────────────────────────────────────────────────┐
│         SKB 预分配:"兵马未动,粮草先行"                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   核心思想:                                                │
│   在 ifconfig up 时,预先申请好几百个空的 SKB(粮草)        │
│   把它们的物理地址填进网卡能看到的 rx_desc 描述符数组        │
│   形成一条空的"传送带"                                       │
│                                                             │
│   初始化阶段(粮草先行):                                    │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   用户空间                                           │  │
│   │      │                                               │  │
│   │      ▼                                               │  │
│   │   ┌──────────────────────────────────────────┐      │  │
│   │   │  SKB 内存池(预分配 1000 个)             │      │  │
│   │   │  ┌────┐ ┌────┐ ┌────┐ ┌────┐ ...        │      │  │
│   │   │  │空SKB│ │空SKB│ │空SKB│ │空SKB│         │      │  │
│   │   │  └────┘ └────┘ └────┘ └────┘             │      │  │
│   │   └──────────────────────────────────────────┘      │  │
│   │      │                                               │  │
│   │      ▼ DMA 映射                                      │  │
│   │   ┌──────────────────────────────────────────┐      │  │
│   │   │  rx_desc 数组(硬件视角)                  │      │  │
│   │   │  [0] 物理地址A ──► SKB1                   │      │  │
│   │   │  [1] 物理地址B ──► SKB2                   │      │  │
│   │   │  [2] 物理地址C ──► SKB3                   │      │  │
│   │   │  ...                                      │      │  │
│   │   └──────────────────────────────────────────┘      │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   数据到来时(直接写入):                                    │
│   网卡不需要申请内存!                                       │
│   直接根据 rx_desc 里的物理地址,DMA 写入预分配好的 SKB      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

四、两个数组:rx_buffer 与 rx_desc

这是网卡数据接收的核心数据结构!

4.1 两个数组的分工

┌─────────────────────────────────────────────────────────────┐
│              rx_buffer vs rx_desc:软硬分工                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │              rx_buffer 数组(软件视角)               │   │
│   ├─────────────────────────────────────────────────────┤   │
│   │  类型:内核空间的虚拟地址数组                         │   │
│   │  内容:struct sk_buff *skb 的虚拟地址                │   │
│   │  谁用:CPU / 内核驱动 用来查看和管理数据              │   │
│   └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│                     数组下标对应                             │
│                           │                                 │
│                           ▼                                 │
│   ┌─────────────────────────────────────────────────────┐   │
│   │              rx_desc 数组(硬件视角)                 │   │
│   ├─────────────────────────────────────────────────────┤   │
│   │  类型:DMA 一致内存(物理地址连续)                   │   │
│   │  内容:缓冲区物理地址 + 状态位(如"写入完成")        │   │
│   │  谁用:网卡硬件 用来 DMA 写入数据                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   一一对应关系:                                            │
│   rx_buffer[0].skb  <──对应──> rx_desc[0].物理地址          │
│   rx_buffer[1].skb  <──对应──> rx_desc[1].物理地址          │
│        ...                                  ...             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4.2 配合流程图

┌─────────────────────────────────────────────────────────────┐
│              数据接收:两个数组的完美配合                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   网卡硬件                          内核驱动                 │
│   ┌─────────────┐                  ┌─────────────┐        │
│   │ rx_desc[0]  │                  │ rx_buffer[0] │        │
│   │ 物理地址:A   │                  │ SKB1 (虚拟) │        │
│   │ 状态:空闲    │                  │ DMA地址:A   │        │
│   └──────┬──────┘                  └──────┬──────┘        │
│          │                                  │               │
│          │ DMA 写入数据                      │               │
│          │──────────► 物理内存 A ◄──────────│               │
│          │                    (SKB1 的数据区)│               │
│          │                                  │               │
│          ▼                                  ▼               │
│   ┌─────────────┐                  ┌─────────────┐        │
│   │ rx_desc[0]  │                  │ rx_buffer[0] │        │
│   │ 状态:完成!  │ ──轮询发现───►  │ SKB1 (有数据)│        │
│   └─────────────┘                  └─────────────┘        │
│                                                             │
│   驱动取出 SKB1 交给协议栈后,重置并准备下一个:              │
│   rx_desc[0].状态 = 空闲                                     │
│   rx_desc[0].物理地址 = 新 SKB 的物理地址                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

五、NAPI 收包完整流程

这是 Linux 高性能网络的核心机制!

5.1 整体流程图

┌─────────────────────────────────────────────────────────────┐
│              NAPI 收包:完整接力赛                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   第一棒:硬中断(通知)                                     │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   网卡 ──DMA──► Ring Buffer ──IRQ──► CPU           │  │
│   │                          │                          │  │
│   │                          ▼                          │  │
│   │   硬中断处理函数(igb_msix_ring)                   │  │
│   │   ├─ 屏蔽网卡后续中断(防止中断风暴)                │  │
│   │   └─ 触发软中断 NET_RX_SOFTIRQ                      │  │
│   │                          │                          │  │
│   └──────────────────────────┼──────────────────────────┘  │
│                                │                              │
│                                ▼                              │
│   第二棒:软中断(处理)                                     │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   ksoftirqd 线程被唤醒 / 或当前 CPU 直接处理        │  │
│   │   __do_softirq() → net_rx_action()                 │  │
│   │                          │                          │  │
│   │                          ▼                          │  │
│   │   调用驱动注册的 poll 函数(igb_poll)              │  │
│   │   ├─ 批量从 Ring Buffer 取包(最多 64 个)          │  │
│   │   ├─ GRO 合并(napi_gro_receive)                   │  │
│   │   └─ 提交协议栈(netif_receive_skb)                 │  │
│   │                          │                          │  │
│   └──────────────────────────┼──────────────────────────┘  │
│                                │                              │
│                                ▼                              │
│   第三棒:协议栈(解析)                                     │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   ip_rcv() → 路由查找 → ip_local_deliver()         │  │
│   │                          │                          │  │
│   │                          ▼                          │  │
│   │   tcp_v4_rcv() / udp_rcv()                         │  │
│   │                          │                          │  │
│   │                          ▼                          │  │
│   │   找到对应 Socket,拷贝到用户缓冲区,唤醒应用进程    │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.2 硬中断到软中断的详细流程

┌─────────────────────────────────────────────────────────────┐
│              硬中断 → 软中断:内核的紧急响应机制             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   时间线:                                                   │
│   ─────────────────────────────────────────────────────     │
│   T0: 数据包从网线到达网卡                                  │
│   T1: 网卡 DMA 把数据写入 Ring Buffer                       │
│   T2: 网卡发送 IRQ 信号给 CPU                               │
│   T3: CPU 暂停当前工作,跳转执行硬中断处理函数               │
│   T4: 硬中断处理函数完成,触发软中断                         │
│   T5: CPU 返回原工作(或切换到 ksoftirqd 处理软中断)        │
│   T6: 软中断处理完成                                        │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  硬中断处理函数(必须极短!)                        │  │
│   │  ┌─────────────────────────────────────────────┐    │  │
│   │  │  igb_msix_ring() {                          │    │  │
│   │  │      ...                                    │    │  │
│   │  │      napi_schedule(&q_vector->napi);  // 触发   │  │
│   │  │      // ⚠️ 干完就退,不能处理数据包!        │    │  │
│   │  │  }                                           │    │  │
│   │  └─────────────────────────────────────────────┘    │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.3 ksoftirqd 线程机制

┌─────────────────────────────────────────────────────────────┐
│              ksoftirqd:软中断的"临时工"                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Q1: 会创建多少个 ksoftirqd 线程?                          │
│   A: 每个 CPU 核心一个                                       │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  $ ps aux | grep ksoftirqd                           │  │
│   │  root     10  0.0  0.0      0     0 ?        S   ?   │  │
│   │  root     11  0.0  0.0      0     0 ?        S   ?   │  │
│   │  ...                                                 │  │
│   └─────────────────────────────────────────────────────┘  │
│   ksoftirqd/0 ──► CPU 0 的软中断处理线程                    │
│   ksoftirqd/1 ──► CPU 1 的软中断处理线程                    │
│                                                             │
│   Q2: 线程创建后是一直运行还是休眠?                         │
│   A: 创建后立即进入 while 循环,等待被唤醒                   │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  ksoftirqd 线程代码(简化):                          │  │
│   │  while (1) {                                         │  │
│   │      set_current_state(TASK_INTERRUPTIBLE);         │  │
│   │      schedule();  // 休眠,等待被唤醒                 │  │
│   │      // 被唤醒后执行 __do_softirq()                  │  │
│   │  }                                                  │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   Q3: 多个请求同时唤醒同一 CPU 的 ksoftirqd 怎么办?         │
│   A: 软中断是串行处理的,不是并行                            │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  内核巧妙设计:                                      │  │
│   │  ├─ 第一个唤醒会执行 __do_softirq()                 │  │
│   │  ├─ 后续唤醒检测到已在执行,直接返回                 │  │
│   │  └─ 全部串行处理,不会并发                           │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.4 活少时 vs 活多时

┌─────────────────────────────────────────────────────────────┐
│         软中断执行时机:看负载决定                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   场景一:负载低 ──► CPU 直接干(快!)                      │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   硬中断执行中...                                    │  │
│   │       │                                              │  │
│   │       │ 硬中断退出前调用 do_softirq()               │  │
│   │       ▼                                              │  │
│   │   CPU 继续在"中断上下文"顺手处理软中断               │  │
│   │       │                                              │  │
│   │       │ 处理完,返回原来的程序                       │  │
│   │       ▼                                              │  │
│   │   原来的进程恢复执行                                 │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│   特点:无线程切换,极低延迟                                │
│                                                             │
│   ─────────────────────────────────────────────────────    │
│                                                             │
│   场景二:负载高 ──► 唤醒 ksoftirqd(保命!)                │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   硬中断执行中...                                    │  │
│   │       │                                              │  │
│   │       │ 检测到软中断太多,唤醒 ksoftirqd            │  │
│   │       ▼                                              │  │
│   │   CPU 返回原来的程序(先保命!)                     │  │
│   │       │                                              │  │
│   │   ksoftirqd 被调度,执行 __do_softirq()             │  │
│   │       │                                              │  │
│   │       │ 处理完后,继续等待(sleep)                  │  │
│   │       ▼                                              │  │
│   │   回到休眠状态                                       │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│   特点:防止 CPU 被软中断霸占,保证系统响应                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

六、协议栈处理流程

6.1 从驱动到协议栈的入口

┌─────────────────────────────────────────────────────────────┐
│         netif_receive_skb:进入协议栈的总入口               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ksoftirqd 调用驱动 poll 函数取到 SKB 后:                 │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   napi_gro_receive() ──► GRO 合并(同流小包合并)  │  │
│   │           │                                          │  │
│   │           ▼                                          │  │
│   │   netif_receive_skb() ──► 协议栈总入口               │  │
│   │           │                                          │  │
│   │           ▼                                          │  │
│   │   __netif_receive_skb_core() ──► 协议分发            │  │
│   │           │                                          │  │
│   │           ├──► ptype_all (tcpdump 抓包点)            │  │
│   │           │                                          │  │
│   │           └──► ptype_base (按协议类型哈希查找)        │  │
│   │                        │                              │  │
│   │                        ▼                              │  │
│   │                   ip_rcv() (IPv4)                    │  │
│   │                   arp_rcv() (ARP)                    │  │
│   │                   ipv6_rcv() (IPv6)                  │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

6.2 IP 层处理:netfilter 钩子

┌─────────────────────────────────────────────────────────────┐
│         ip_rcv:Netfilter 钩子与路由判断                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   ip_rcv()                                          │  │
│   │       │                                              │  │
│   │       ▼                                              │  │
│   │   ┌─────────────────────────────────────────┐       │  │
│   │   │  NF_HOOK(PF_INET, NF_INET_PRE_ROUTING) │       │  │
│   │   │         │                                │       │  │
│   │   │         ▼                                │       │  │
│   │   │  ┌─────────────────────────────┐        │       │  │
│   │   │  │ iptables 规则检查            │        │       │  │
│   │   │  │ ├─ filter 表                │        │       │  │
│   │   │  │ ├─ nat 表                   │        │       │  │
│   │   │  │ └─ mangle 表               │        │       │  │
│   │   │  └─────────────────────────────┘        │       │  │
│   │   │         │                                │       │  │
│   │   │         ▼                                │       │  │
│   │   │  ip_rcv_finish()                        │       │  │
│   │   └─────────────────────────────────────────┘       │  │
│   │       │                                              │  │
│   │       ▼                                              │  │
│   │   路由判断                                            │  │
│   │   ┌────────────────────┬────────────────────┐       │  │
│   │   │  转发 (本机不是     │  交付 (本机是      │       │  │
│   │   │   目的地)          │   目的地)          │       │  │
│   │   │      │             │      │             │       │  │
│   │   │      ▼             │      ▼             │       │  │
│   │   │  ip_forward()      │  ip_local_deliver()│       │  │
│   │   └────────────────────┴────────────────────┘       │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

6.3 传输层交付

┌─────────────────────────────────────────────────────────────┐
│         ip_local_deliver:传输层的最后一站                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                     │  │
│   │   ip_local_deliver()                               │  │
│   │       │                                              │  │
│   │       ├──► IP 分片重组?(检查是否是分片)          │  │
│   │       │      │                                      │  │
│   │       │      ▼                                      │  │
│   │       │   ip_defrag() ──► 重组完整数据包            │  │
│   │       │                                              │  │
│   │       ▼                                              │  │
│   │   ┌─────────────────────────────────────────┐       │  │
│   │   │  NF_HOOK(PF_INET, NF_INET_LOCAL_IN)   │       │  │
│   │   │         │                                │       │  │
│   │   │         ▼                                │       │  │
│   │   │  iptables INPUT 链规则检查              │       │  │
│   │   │         │                                │       │  │
│   │   │         ▼                                │       │  │
│   │   │  ip_local_deliver_finish()              │       │  │
│   │   └─────────────────────────────────────────┘       │  │
│   │       │                                              │  │
│   │       ▼                                              │  │
│   │   协议查找(根据 IP 头部的 Protocol 字段)           │  │
│   │   ┌─────────────────────────────────────────┐       │  │
│   │   │  Protocol = 6  ──► tcp_v4_rcv()        │       │  │
│   │   │  Protocol = 17 ──► udp_rcv()           │       │  │
│   │   │  Protocol = 1  ──► icmp_rcv()          │       │  │
│   │   └─────────────────────────────────────────┘       │  │
│   │       │                                              │  │
│   │       ▼                                              │  │
│   │   找到对应 Socket,拷贝到用户缓冲区,唤醒应用       │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

6.4 完整调用链

┌─────────────────────────────────────────────────────────────┐
│              数据包处理:完整调用链                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ksoftirqd (软中断上下文)                                   │
│       │                                                     │
│       ▼                                                     │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  驱动 poll 函数 (igb_poll)                         │   │
│   │  ├─ 从 Ring Buffer 取 SKB                          │   │
│   │  └─ 调用 napi_gro_receive(skb)                     │   │
│   └─────────────────────────────────────────────────────┘   │
│       │                                                     │
│       ▼                                                     │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  netif_receive_skb(skb)                             │   │
│   │  ├─ ptype_all (tcpdump 抓包点)                     │   │
│   │  └─ ptype_base 协议分发                             │   │
│   └─────────────────────────────────────────────────────┘   │
│       │                                                     │
│       ▼                                                     │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  ip_rcv() ──► Netfilter PRE_ROUTING 钩子          │   │
│   │       │                                              │   │
│   │       ▼                                              │   │
│   │  ip_rcv_finish()                                     │   │
│   │       │                                              │   │
│   │       ▼                                              │   │
│   │  ip_local_deliver() ──► Netfilter INPUT 钩子        │   │
│   │       │                                              │   │
│   │       ▼                                              │   │
│   │  ip_local_deliver_finish()                          │   │
│   │       │                                              │   │
│   │       ▼                                              │   │
│   │  tcp_v4_rcv() / udp_rcv()                           │   │
│   │       │                                              │   │
│   │       ▼                                              │   │
│   │  Socket 接收缓冲区 ──► 唤醒应用进程                  │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

七、数据包处理顺序总结

7.1 XDP 的特殊地位

┌─────────────────────────────────────────────────────────────┐
│              数据包处理顺序:XDP 最快!                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   处理顺序(从快到慢):                                      │
│   ─────────────────────────────────────────────────────     │
│                                                             │
│   ① XDP (Express Data Path)                                │
│      最早!网卡驱动层直接处理,甚至早于 skb 创建              │
│      适用于:防火墙、DDoS 防护、高性能负载均衡               │
│                                                             │
│   ② 硬中断 ──► 软中断 ──► 驱动 poll                        │
│      此时数据包还在驱动层,还没进协议栈                      │
│                                                             │
│   ③ tcpdump 抓包点                                         │
│      在 netif_receive_skb 的 ptype_all 位置                 │
│      此时能抓到原始数据包                                    │
│                                                             │
│   ④ IP 层 (ip_rcv)                                         │
│      进入网络层,可被 iptables PRE_ROUTING 链拦截           │
│                                                             │
│   ⑤ 传输层 (TCP/UDP)                                       │
│      协议处理,Socket 交付                                   │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  为什么 XDP 最快?                                   │  │
│   │  ├─ 跳过整个 Linux 内核网络栈                       │  │
│   │  ├─ 甚至可以不需要分配 skb                         │  │
│   │  ├─ 直接在网卡驱动层做决策(丢弃/转发/修改)         │  │
│   │  └─ 性能可达每秒百万级数据包                         │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

7.2 完整处理流程图

┌─────────────────────────────────────────────────────────────┐
│              Linux 网络数据包:完整生命周期                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                    网卡硬件层                        │  │
│   │  ┌─────────┐    ┌─────────┐    ┌─────────┐         │  │
│   │  │ Ring    │    │ DMA     │    │ 中断    │         │  │
│   │  │ Buffer  │───►│ 控制器  │───►│ 控制器  │         │  │
│   │  └─────────┘    └─────────┘    └─────────┘         │  │
│   └─────────────────────────────────────────────────────┘  │
│                           │                                 │
│                           ▼                                 │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                    内核驱动层                        │  │
│   │                                                     │  │
│   │   硬中断处理函数 ──► 屏蔽中断 ──► 触发软中断       │  │
│   │        │                                          │  │
│   │        │ NAPI 轮询                                 │  │
│   │        ▼                                          │  │
│   │   ┌─────────────────────────────────────────┐     │  │
│   │   │  XDP (最早!)                            │     │  │
│   │   │  在这里可以做最高效的处理                │     │  │
│   │   └─────────────────────────────────────────┘     │  │
│   │        │                                          │  │
│   │        ▼                                          │  │
│   │   SKB 创建 ──► napi_gro_receive()                │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                           │                                 │
│                           ▼                                 │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                    协议栈层                          │  │
│   │                                                     │  │
│   │   netif_receive_skb()                              │  │
│   │        │                                            │  │
│   │        ├──► tcpdump 抓包点 (ptype_all)             │  │
│   │        │                                            │  │
│   │        ▼                                            │  │
│   │   ip_rcv()                                          │  │
│   │        │                                            │  │
│   │        ├──► PRE_ROUTING (iptables)                 │  │
│   │        │                                            │  │
│   │        ▼                                            │  │
│   │   路由判断                                           │  │
│   │        │                                            │  │
│   │        ├──► 转发路径                                │  │
│   │        │                                            │  │
│   │        └──► 本机交付                                │  │
│   │                │                                    │  │
│   │                ├──► LOCAL_IN (iptables)            │  │
│   │                │                                    │  │
│   │                ▼                                    │  │
│   │   ip_local_deliver_finish()                        │  │
│   │        │                                            │  │
│   │        ▼                                            │  │
│   │   tcp_v4_rcv() / udp_rcv()                         │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                           │                                 │
│                           ▼                                 │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                    Socket 层                        │  │
│   │                                                     │  │
│   │   找到对应 Socket ──► 拷贝到接收缓冲区              │  │
│   │        │                                            │  │
│   │        ▼                                            │  │
│   │   唤醒等待进程(recv/read 系统调用)                │  │
│   │                                                     │  │
│   └─────────────────────────────────────────────────────┘  │
│                           │                                 │
│                           ▼                                 │
│                    应用程序收到数据                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

0voice · GitHub

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐