HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 网络架构师学习手册

    • 网络架构师学习教程
    • 基础篇

      • 第1章 网络模型与数据流转
      • 第2章 以太网与二层通信
      • 第3章 IP路由与三层转发
      • 第4章 TCP与可靠传输
      • 第5章 应用层协议
    • Linux网络栈

      • 第6章 数据包接收路径
      • 第7章 多核网络优化
      • 第8章 Netfilter与防火墙
      • 第9章 流量控制与QoS
    • 虚拟网络

      • 第10章 Network Namespace基础
      • 第11章 Bridge与互联
      • 第12章 VXLAN与Overlay网络
      • 第13章 OVS与SDN
    • Kubernetes网络

      • 第14章 CNI模型与实现
      • 第15章 kube-proxy与Service实现
      • 第16章 CoreDNS与服务发现
      • 第17章 NetworkPolicy与安全隔离
      • 第18章 Calico网络深度解析
      • 第19章 Cilium与eBPF网络
    • 网络架构

      • 第20章 网络设备与拓扑设计
      • 第21章 网络容量规划与计算
      • 第22章 负载均衡架构设计
      • 第23章 高可用网络架构
      • 第24章 网络安全架构
    • 性能调优

      • 第25章 系统级网络调优
      • 第26章 故障排查方法论
      • 第27章 生产环境案例分析
    • 前沿技术

      • 第28章 eBPF深度实践
      • 第29章 ServiceMesh与边车代理
      • 第30章 网络技术趋势与未来展望
    • 附录

      • 附录A:命令速查手册
      • 附录B:排错决策树
      • 附录C:学习资源
      • 附录D:技能图谱

第6章 数据包接收路径

学习目标

  • 理解Linux网络栈的完整数据包接收路径
  • 掌握硬中断、软中断、NAPI机制
  • 了解sk_buff数据结构和内存管理
  • 能够使用工具观察和调优网络性能

🔬 原理

完整数据包接收路径

网卡 → DMA → Ring Buffer → 硬中断 → 软中断(NAPI) 
→ GRO → 协议栈 → Socket缓冲区 → 应用

详细流程:

  1. 网卡接收:物理信号转换为数字数据
  2. DMA传输:网卡直接将数据写入内存
  3. 硬中断:通知CPU有数据到达
  4. 软中断:NAPI轮询处理数据包
  5. GRO合并:将小包合并成大包
  6. 协议栈处理:IP、TCP/UDP解析
  7. Socket接收:数据进入应用缓冲区

硬中断机制

中断类型:

  • MSI-X:消息信号中断,支持多队列
  • MSI:消息信号中断,单队列
  • 传统中断:共享中断线

中断处理:

// 中断处理函数
static irqreturn_t e1000_intr(int irq, void *data) {
    struct net_device *netdev = data;
    struct e1000_adapter *adapter = netdev_priv(netdev);
    
    // 读取中断状态
    u32 icr = er32(ICR);
    if (!icr) {
        return IRQ_NONE;
    }
    
    // 禁用中断,启用NAPI
    napi_schedule(&adapter->napi);
    
    return IRQ_HANDLED;
}

NAPI机制

NAPI(New API):

  • 高流量时关闭中断,使用轮询
  • 低流量时恢复中断触发
  • 减少中断开销,提升性能

NAPI结构:

struct napi_struct {
    struct list_head poll_list;    // 轮询列表
    unsigned long state;           // NAPI状态
    int weight;                    // 权重
    int (*poll)(struct napi_struct *, int); // 轮询函数
    struct net_device *dev;        // 网络设备
    // ... 更多字段
};

NAPI状态:

  • NAPIF_STATE_SCHED:已调度
  • NAPIF_STATE_DISABLE:已禁用
  • NAPIF_STATE_NPSVC:正在轮询

sk_buff数据结构

sk_buff结构:

struct sk_buff {
    struct sk_buff *next;          // 下一个skb
    struct sk_buff *prev;          // 上一个skb
    struct sock *sk;               // 关联的socket
    struct net_device *dev;        // 网络设备
    
    unsigned char *head;           // 数据缓冲区起始
    unsigned char *data;           // 当前数据指针
    unsigned char *tail;           // 数据结束位置
    unsigned char *end;            // 缓冲区结束位置
    
    unsigned int len;              // 数据长度
    unsigned int data_len;         // 数据长度
    __u16 protocol;                // 协议类型
    __u16 transport_header;        // 传输层头偏移
    __u16 network_header;          // 网络层头偏移
    __u16 mac_header;              // MAC层头偏移
    
    // ... 更多字段
};

内存布局:

head    data    tail    end
|       |       |       |
|-------|-------|-------|
|headroom|payload|tailroom|

GRO机制

GRO(Generic Receive Offload):

  • 接收侧合并小包成大包
  • 减少协议栈处理次数
  • 提升吞吐量

GRO类型:

  • GRO_NETVSC:Hyper-V网络
  • GRO_GRE:GRE隧道
  • GRO_GSO:通用分段卸载

️ 实现

网卡驱动实现

网卡初始化:

static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent) {
    struct net_device *netdev;
    struct e1000_adapter *adapter;
    
    // 分配网络设备
    netdev = alloc_etherdev(sizeof(struct e1000_adapter));
    if (!netdev) {
        return -ENOMEM;
    }
    
    // 初始化适配器
    adapter = netdev_priv(netdev);
    adapter->netdev = netdev;
    
    // 设置网络设备操作
    netdev->netdev_ops = &e1000_netdev_ops;
    netdev->ethtool_ops = &e1000_ethtool_ops;
    
    // 注册网络设备
    if (register_netdev(netdev)) {
        free_netdev(netdev);
        return -EIO;
    }
    
    return 0;
}

数据包接收:

static int e1000_clean_rx_irq(struct e1000_adapter *adapter, int budget) {
    struct net_device *netdev = adapter->netdev;
    struct pci_dev *pdev = adapter->pdev;
    struct e1000_ring *rx_ring = adapter->rx_ring;
    struct sk_buff *skb;
    int cleaned = 0;
    
    while (cleaned < budget) {
        // 从Ring Buffer获取描述符
        struct e1000_rx_desc *rx_desc = E1000_RX_DESC(rx_ring, rx_ring->next_to_clean);
        
        if (!(rx_desc->status & E1000_RXD_STAT_DD)) {
            break; // 没有数据
        }
        
        // 分配sk_buff
        skb = netdev_alloc_skb_ip_align(netdev, adapter->rx_buffer_len);
        if (!skb) {
            break; // 内存不足
        }
        
        // 从DMA区域复制数据
        memcpy(skb->data, rx_desc->buffer_addr, rx_desc->length);
        skb->len = rx_desc->length;
        
        // 设置网络设备
        skb->dev = netdev;
        
        // 发送到协议栈
        netif_receive_skb(skb);
        
        cleaned++;
    }
    
    return cleaned;
}

协议栈处理

网络层处理:

int ip_rcv(struct sk_buff *skb, struct net_device *dev, 
           struct packet_type *pt, struct net_device *orig_dev) {
    struct iphdr *iph;
    u32 len;
    
    // 验证IP头
    if (!pskb_may_pull(skb, sizeof(struct iphdr))) {
        goto drop;
    }
    
    iph = ip_hdr(skb);
    
    // 验证IP头校验和
    if (ip_fast_csum((u8 *)iph, iph->ihl)) {
        goto drop;
    }
    
    // 验证长度
    len = ntohs(iph->tot_len);
    if (skb->len < len) {
        goto drop;
    }
    
    // 路由查找
    if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev)) {
        goto drop;
    }
    
    // 发送到传输层
    return dst_input(skb);
    
drop:
    kfree_skb(skb);
    return NET_RX_DROP;
}

传输层处理:

int tcp_v4_rcv(struct sk_buff *skb) {
    struct iphdr *iph;
    struct tcphdr *th;
    struct sock *sk;
    
    // 解析TCP头
    if (!pskb_may_pull(skb, sizeof(struct tcphdr))) {
        goto drop;
    }
    
    th = tcp_hdr(skb);
    iph = ip_hdr(skb);
    
    // 查找socket
    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
    if (!sk) {
        goto drop;
    }
    
    // 处理TCP段
    return tcp_v4_do_rcv(sk, skb);
    
drop:
    kfree_skb(skb);
    return NET_RX_DROP;
}

🛠️ 命令

网络统计信息

# 查看网络统计
cat /proc/net/dev

# 查看协议统计
cat /proc/net/snmp

# 查看连接统计
ss -s

# 查看软中断统计
cat /proc/softirqs

网卡状态检查

# 查看网卡信息
ethtool eth0

# 查看网卡统计
ethtool -S eth0

# 查看网卡特性
ethtool -k eth0

# 查看Ring Buffer大小
ethtool -g eth0

中断信息

# 查看中断分布
cat /proc/interrupts | grep eth0

# 查看中断亲和性
cat /proc/irq/*/smp_affinity_list

# 查看软中断统计
cat /proc/softirqs | head -n2

代码

网络统计监控程序

// net_stats.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct net_stats {
    char interface[16];
    unsigned long rx_bytes;
    unsigned long rx_packets;
    unsigned long rx_errors;
    unsigned long rx_dropped;
    unsigned long tx_bytes;
    unsigned long tx_packets;
    unsigned long tx_errors;
    unsigned long tx_dropped;
};

int parse_net_dev(struct net_stats *stats, int max_interfaces) {
    FILE *fp = fopen("/proc/net/dev", "r");
    if (!fp) {
        perror("fopen");
        return -1;
    }
    
    char line[256];
    int count = 0;
    
    // 跳过前两行
    fgets(line, sizeof(line), fp);
    fgets(line, sizeof(line), fp);
    
    while (fgets(line, sizeof(line), fp) && count < max_interfaces) {
        if (sscanf(line, "%15s %lu %lu %lu %lu %lu %lu %lu %lu %lu",
                   stats[count].interface,
                   &stats[count].rx_bytes,
                   &stats[count].rx_packets,
                   &stats[count].rx_errors,
                   &stats[count].rx_dropped,
                   &stats[count].tx_bytes,
                   &stats[count].tx_packets,
                   &stats[count].tx_errors,
                   &stats[count].tx_dropped) == 9) {
            count++;
        }
    }
    
    fclose(fp);
    return count;
}

int main() {
    struct net_stats stats[32];
    int count = parse_net_dev(stats, 32);
    
    if (count < 0) {
        return 1;
    }
    
    printf("%-15s %12s %12s %12s %12s %12s %12s %12s %12s\n",
           "Interface", "RX Bytes", "RX Packets", "RX Errors", "RX Dropped",
           "TX Bytes", "TX Packets", "TX Errors", "TX Dropped");
    printf("%-15s %12s %12s %12s %12s %12s %12s %12s %12s\n",
           "--------", "--------", "----------", "---------", "----------",
           "--------", "----------", "---------", "----------");
    
    for (int i = 0; i < count; i++) {
        printf("%-15s %12lu %12lu %12lu %12lu %12lu %12lu %12lu %12lu\n",
               stats[i].interface,
               stats[i].rx_bytes,
               stats[i].rx_packets,
               stats[i].rx_errors,
               stats[i].rx_dropped,
               stats[i].tx_bytes,
               stats[i].tx_packets,
               stats[i].tx_errors,
               stats[i].tx_dropped);
    }
    
    return 0;
}

编译运行:

gcc net_stats.c -o net_stats
./net_stats

软中断监控程序

// softirq_monitor.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct softirq_stats {
    unsigned long hi;
    unsigned long timer;
    unsigned long net_tx;
    unsigned long net_rx;
    unsigned long block;
    unsigned long irq_poll;
    unsigned long tasklet;
    unsigned long sched;
    unsigned long hrtimer;
    unsigned long rcu;
};

int parse_softirqs(struct softirq_stats *stats) {
    FILE *fp = fopen("/proc/softirqs", "r");
    if (!fp) {
        perror("fopen");
        return -1;
    }
    
    char line[256];
    if (fgets(line, sizeof(line), fp)) {
        if (sscanf(line, " %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
                   &stats->hi, &stats->timer, &stats->net_tx, &stats->net_rx,
                   &stats->block, &stats->irq_poll, &stats->tasklet,
                   &stats->sched, &stats->hrtimer, &stats->rcu) == 10) {
            fclose(fp);
            return 0;
        }
    }
    
    fclose(fp);
    return -1;
}

int main() {
    struct softirq_stats stats1, stats2;
    
    if (parse_softirqs(&stats1) < 0) {
        return 1;
    }
    
    sleep(1);
    
    if (parse_softirqs(&stats2) < 0) {
        return 1;
    }
    
    printf("SoftIRQ Statistics (per second):\n");
    printf("HI:       %lu\n", stats2.hi - stats1.hi);
    printf("Timer:    %lu\n", stats2.timer - stats1.timer);
    printf("Net TX:   %lu\n", stats2.net_tx - stats1.net_tx);
    printf("Net RX:   %lu\n", stats2.net_rx - stats1.net_rx);
    printf("Block:    %lu\n", stats2.block - stats1.block);
    printf("IRQ Poll: %lu\n", stats2.irq_poll - stats1.irq_poll);
    printf("Tasklet:  %lu\n", stats2.tasklet - stats1.tasklet);
    printf("Sched:    %lu\n", stats2.sched - stats1.sched);
    printf("HRTimer:  %lu\n", stats2.hrtimer - stats1.hrtimer);
    printf("RCU:      %lu\n", stats2.rcu - stats1.rcu);
    
    return 0;
}

编译运行:

gcc softirq_monitor.c -o softirq_monitor
./softirq_monitor

🧪 实验

实验1:观察数据包接收路径

目标:理解数据包从网卡到应用的完整路径

步骤:

# 1. 查看网卡统计
ethtool -S eth0 | grep -E 'rx_packets|rx_bytes|rx_errors|rx_dropped'

# 2. 查看软中断统计
cat /proc/softirqs | head -n2

# 3. 发送数据包
ping -c 100 8.8.8.8

# 4. 再次查看统计
ethtool -S eth0 | grep -E 'rx_packets|rx_bytes|rx_errors|rx_dropped'
cat /proc/softirqs | head -n2

# 5. 观察变化
echo "RX packets increased: $(ethtool -S eth0 | grep rx_packets | awk '{print $2}')"
echo "NET_RX softirq increased: $(cat /proc/softirqs | grep NET_RX | awk '{print $2}')"

预期结果:

  • 理解数据包计数
  • 观察软中断处理
  • 掌握统计信息解读

实验2:GRO影响测试

目标:测试GRO对网络性能的影响

步骤:

# 1. 查看当前GRO状态
ethtool -k eth0 | grep generic-receive-offload

# 2. 测试基线性能
iperf3 -s &
iperf3 -c localhost -t 30

# 3. 关闭GRO
sudo ethtool -K eth0 gro off

# 4. 测试性能
iperf3 -c localhost -t 30

# 5. 重新开启GRO
sudo ethtool -K eth0 gro on

# 6. 测试性能
iperf3 -c localhost -t 30

预期结果:

  • 理解GRO的作用
  • 观察性能差异
  • 掌握GRO调优

实验3:中断亲和性优化

目标:优化中断处理性能

步骤:

# 1. 查看当前中断分布
cat /proc/interrupts | grep eth0

# 2. 查看CPU核心数
nproc

# 3. 设置中断亲和性
# 假设有4个CPU核心,将eth0中断绑定到CPU 0-3
for i in {0..3}; do
    IRQ=$(grep eth0-TxRx-$i /proc/interrupts | awk -F: '{print $1}')
    if [ ! -z "$IRQ" ]; then
        echo $i | sudo tee /proc/irq/$IRQ/smp_affinity_list
    fi
done

# 4. 验证设置
cat /proc/interrupts | grep eth0

# 5. 测试性能
iperf3 -c localhost -t 30

预期结果:

  • 理解中断亲和性
  • 观察性能提升
  • 掌握中断优化

实验4:内存使用监控

目标:监控网络内存使用情况

步骤:

# 1. 查看网络内存统计
cat /proc/net/sockstat

# 2. 查看sk_buff使用情况
cat /proc/net/sockstat | grep sk

# 3. 发送大量数据
dd if=/dev/zero bs=1M count=100 | nc localhost 8080

# 4. 再次查看统计
cat /proc/net/sockstat

# 5. 查看内存使用
free -h

预期结果:

  • 理解网络内存管理
  • 观察内存使用变化
  • 掌握内存监控

排错

常见问题排查

问题1:网卡丢包严重

# 检查网卡统计
ethtool -S eth0 | grep -i drop

# 检查Ring Buffer大小
ethtool -g eth0

# 增大Ring Buffer
sudo ethtool -G eth0 rx 4096 tx 4096

# 检查中断处理
cat /proc/interrupts | grep eth0

问题2:软中断CPU使用率高

# 查看软中断统计
cat /proc/softirqs | head -n2

# 检查中断亲和性
cat /proc/irq/*/smp_affinity_list

# 启用RPS
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus

问题3:网络延迟高

# 检查网络统计
cat /proc/net/snmp | grep -i tcp

# 检查重传情况
ss -ti | grep -i retrans

# 调整TCP参数
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr

排错清单

  • [ ] 检查网卡状态(ethtool、dmesg)
  • [ ] 验证中断处理(/proc/interrupts)
  • [ ] 确认软中断分布(/proc/softirqs)
  • [ ] 检查内存使用(/proc/net/sockstat)
  • [ ] 验证GRO设置(ethtool -k)
  • [ ] 测试网络连通性(ping、iperf3)
  • [ ] 查看系统日志(dmesg、/var/log/syslog)
Next
第7章 多核网络优化