Linux 网络内核机制深度解析:从网卡驱动到协议栈
·
一、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 系统调用) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 应用程序收到数据 │
│ │
└─────────────────────────────────────────────────────────────┘
更多推荐



所有评论(0)