乐于分享
好东西不私藏

Linux 网络子系统源码剖析(十三):网络性能优化技术 – 突破性能瓶颈

Linux 网络子系统源码剖析(十三):网络性能优化技术 – 突破性能瓶颈

从零拷贝到硬件卸载 – 榨干网络性能的每一滴潜力

系列:Linux 网络子系统源码剖析篇号:第 13 篇内核版本:Linux 5.10 LTS重点模块:零拷贝、硬件Offload、中断优化、多队列技术


📋 本篇导读

你将学到

  • • 零拷贝技术的完整实现(sendfile、splice、MSG_ZEROCOPY)
  • • 硬件 Offload 技术(TSO/GSO、GRO/LRO、Checksum、RSS)
  • • 中断优化技术(NAPI、中断合并、中断亲和性)
  • • 内存优化技术(sk_buff 池化、页面回收、NUMA 感知)
  • • 多队列技术(RSS、RPS、RFS、XPS)
  • • 性能测试和分析方法
  • • 实战优化案例和最佳实践

前置知识

  • • 已阅读第 1-12 篇文章
  • • 了解 Linux 内核内存管理
  • • 理解网络协议栈基础
  • • 熟悉性能分析工具

阅读时间

约 100-120 分钟


🎯 概述

1.1 网络性能瓶颈

现代网络应用面临的性能挑战:

硬件层面

  • • 网卡速度:1Gbps → 10Gbps → 100Gbps
  • • CPU 性能:单核性能提升缓慢
  • • 内存带宽:成为新的瓶颈
  • • PCIe 带宽:限制数据传输

软件层面

  • • 数据拷贝:用户空间 ↔ 内核空间
  • • 上下文切换:系统调用开销
  • • 中断处理:中断风暴问题
  • • 缓存失效:CPU 缓存命中率低

网络协议栈开销

应用层数据    │    ▼ 系统调用(上下文切换)Socket 层    │    ▼ 拷贝到内核TCP/IP 层(协议处理)    │    ▼ 构造数据包设备驱动层    │    ▼ DMA 传输网卡硬件    │    ▼ 物理传输网络

1.2 优化目标

吞吐量优化

  • • 目标:接近网卡线速
  • • 指标:Gbps、pps(packets per second)
  • • 关键:减少 CPU 开销

延迟优化

  • • 目标:降低端到端延迟
  • • 指标:微秒级延迟
  • • 关键:减少处理路径

CPU 效率优化

  • • 目标:降低 CPU 使用率
  • • 指标:每 Gbps 的 CPU 使用率
  • • 关键:硬件卸载、批处理

1.3 优化技术概览

📋 零拷贝技术

2.1 传统数据传输的问题

传统 read/write 方式

/* 传统方式:4 次拷贝,4 次上下文切换 */char buf[8192];/* 1. read():磁盘 → 内核缓冲区 → 用户缓冲区 */int fd = open("file.txt", O_RDONLY);ssize_t n = read(fd, buf, sizeof(buf));  /* 2 次拷贝 *//* 2. write():用户缓冲区 → socket 缓冲区 → 网卡 */write(sockfd, buf, n);  /* 2 次拷贝 */

数据流动路径

2.2 sendfile() 零拷贝

sendfile() 在内核空间直接传输数据,避免用户空间拷贝。

源码位置fs/read_write.c

/** * do_sendfile - sendfile 实现 * @out_fd: 输出文件描述符(socket) * @in_fd: 输入文件描述符(文件) * @ppos: 文件偏移 * @count: 传输字节数 * @max: 最大传输字节数 */staticssize_tdo_sendfile(int out_fd, int in_fd, loff_t *ppos,size_t count, loff_t max){structfdinout;structinode *in_inode, *out_inode;loff_t pos;ssize_t retval;/* ========== 1. 获取文件描述符 ========== */    in = fdget(in_fd);if (!in.file)return -EBADF;    out = fdget(out_fd);if (!out.file) {        retval = -EBADF;goto fput_in;    }/* ========== 2. 检查文件类型 ========== */    in_inode = file_inode(in.file);    out_inode = file_inode(out.file);/* 输入必须是常规文件 */if (!S_ISREG(in_inode->i_mode)) {        retval = -EINVAL;goto fput_out;    }/* 输出必须是 socket */if (!out.file->f_op->splice_write) {        retval = -EINVAL;goto fput_out;    }/* ========== 3. 获取文件位置 ========== */if (ppos) {        pos = *ppos;    } else {        pos = in.file->f_pos;    }/* ========== 4. 调用 splice 传输数据 ========== */    retval = do_splice_direct(in.file, &pos, out.file,                             &out.file->f_pos, count, 0);if (retval > 0) {/* 更新文件位置 */if (ppos)            *ppos = pos;else            in.file->f_pos = pos;/* 更新统计信息 */        add_rchar(current, retval);        add_wchar(current, retval);        fsnotify_access(in.file);        fsnotify_modify(out.file);    }fput_out:    fdput(out);fput_in:    fdput(in);return retval;}

sendfile() 数据流

使用示例

#include<sys/sendfile.h>#include<fcntl.h>/** * send_file_zero_copy - 使用 sendfile 发送文件 * @sockfd: socket 文件描述符 * @filename: 文件名 */intsend_file_zero_copy(int sockfd, constchar *filename){int filefd;structstatstat_buf;off_t offset = 0;ssize_t sent;/* ========== 1. 打开文件 ========== */    filefd = open(filename, O_RDONLY);if (filefd < 0) {        perror("open");return-1;    }/* ========== 2. 获取文件大小 ========== */if (fstat(filefd, &stat_buf) < 0) {        perror("fstat");        close(filefd);return-1;    }/* ========== 3. 使用 sendfile 传输 ========== */    sent = sendfile(sockfd, filefd, &offset, stat_buf.st_size);if (sent < 0) {        perror("sendfile");        close(filefd);return-1;    }printf("Sent %zd bytes using sendfile\n", sent);    close(filefd);return0;}

2.3 splice() 零拷贝

splice() 在两个文件描述符之间移动数据,使用管道作为中介。

源码位置fs/splice.c

/** * do_splice - splice 实现 * @in: 输入文件 * @ppos: 输入位置 * @out: 输出文件 * @opos: 输出位置 * @len: 传输长度 * @flags: 标志 */longdo_splice(struct file *in, loff_t *ppos,struct file *out, loff_t *opos,size_t len, unsignedint flags){structpipe_inode_info *ipipe;structpipe_inode_info *opipe;loff_t offset;long ret;/* ========== 1. 检查文件类型 ========== */    ipipe = get_pipe_info(in);    opipe = get_pipe_info(out);if (ipipe && opipe) {/* 管道到管道 */return splice_pipe_to_pipe(ipipe, opipe, len, flags);    }if (ipipe) {/* 管道到文件 */return do_splice_from(opipe, out, opos, len, flags);    }if (opipe) {/* 文件到管道 */return do_splice_to(in, ppos, opipe, len, flags);    }/* 文件到文件:使用 sendfile */return do_sendfile(out->f_fd, in->f_fd, ppos, len, MAX_RW_COUNT);}/** * do_splice_to - 从文件 splice 到管道 * @in: 输入文件 * @ppos: 文件位置 * @pipe: 管道 * @len: 长度 * @flags: 标志 */staticlongdo_splice_to(struct file *in, loff_t *ppos,struct pipe_inode_info *pipe,size_t len, unsignedint flags){ssize_t (*splice_read)(struct file *, loff_t *,struct pipe_inode_info *, size_tunsignedint);/* ========== 1. 获取 splice_read 函数 ========== */    splice_read = in->f_op->splice_read;if (!splice_read)        splice_read = default_file_splice_read;/* ========== 2. 调用 splice_read ========== */return splice_read(in, ppos, pipe, len, flags);}

使用示例

#include<fcntl.h>#include<unistd.h>/** * splice_transfer - 使用 splice 传输数据 * @in_fd: 输入文件描述符 * @out_fd: 输出文件描述符 * @len: 传输长度 */ssize_tsplice_transfer(int in_fd, int out_fd, size_t len){int pipefd[2];ssize_t bytes_in, bytes_out;ssize_t total = 0;/* ========== 1. 创建管道 ========== */if (pipe(pipefd) < 0) {        perror("pipe");return-1;    }/* ========== 2. 从输入文件 splice 到管道 ========== */    bytes_in = splice(in_fd, NULL, pipefd[1], NULL, len,                     SPLICE_F_MOVE | SPLICE_F_MORE);if (bytes_in < 0) {        perror("splice in");goto cleanup;    }/* ========== 3. 从管道 splice 到输出文件 ========== */    bytes_out = splice(pipefd[0], NULL, out_fd, NULL, bytes_in,                      SPLICE_F_MOVE | SPLICE_F_MORE);if (bytes_out < 0) {        perror("splice out");goto cleanup;    }    total = bytes_out;cleanup:    close(pipefd[0]);    close(pipefd[1]);return total;}

2.4 MSG_ZEROCOPY

Linux 4.14+ 支持 MSG_ZEROCOPY 标志,实现真正的零拷贝发送。

源码位置net/core/skbuff.c

/** * sock_zerocopy_alloc - 分配零拷贝 ubuf_info * @sk: socket * @size: 数据大小 */struct ubuf_info *sock_zerocopy_alloc(struct sock *sk, size_t size){structubuf_info *uarg;structsk_buff *skb;/* ========== 1. 检查是否支持零拷贝 ========== */if (!(sk->sk_route_caps & NETIF_F_SG) ||        !(sk->sk_route_caps & NETIF_F_ZEROCOPY))returnNULL;/* ========== 2. 分配 ubuf_info ========== */    uarg = sock_kmalloc(sk, sizeof(*uarg), GFP_KERNEL);if (!uarg)returnNULL;/* ========== 3. 初始化 ========== */    uarg->callback = sock_zerocopy_callback;    uarg->id = ((u32)atomic_inc_return(&sk->sk_zckey)) - 1;    uarg->len = 1;    uarg->bytelen = size;    uarg->zerocopy = 1;    refcount_set(&uarg->refcnt, 1);    sock_hold(sk);return uarg;}/** * sock_zerocopy_put - 释放零拷贝引用 * @uarg: ubuf_info */voidsock_zerocopy_put(struct ubuf_info *uarg){if (refcount_dec_and_test(&uarg->refcnt)) {/* 所有引用释放,通知应用层 */        uarg->callback(uarg, true);    }}

使用示例

#include<linux/errqueue.h>#include<sys/socket.h>/** * zerocopy_send - 使用 MSG_ZEROCOPY 发送数据 * @sockfd: socket 文件描述符 * @buf: 数据缓冲区 * @len: 数据长度 */intzerocopy_send(int sockfd, void *buf, size_t len){ssize_t sent;int ret;/* ========== 1. 启用 SO_ZEROCOPY ========== */int one = 1;if (setsockopt(sockfd, SOL_SOCKET, SO_ZEROCOPY,                  &one, sizeof(one)) < 0) {        perror("setsockopt SO_ZEROCOPY");return-1;    }/* ========== 2. 发送数据 ========== */    sent = send(sockfd, buf, len, MSG_ZEROCOPY);if (sent < 0) {        perror("send");return-1;    }/* ========== 3. 等待发送完成通知 ========== */structmsghdrmsg = {};structioveciov = {};char control[100];    msg.msg_control = control;    msg.msg_controllen = sizeof(control);/* 从错误队列接收完成通知 */    ret = recvmsg(sockfd, &msg, MSG_ERRQUEUE);if (ret < 0) {        perror("recvmsg");return-1;    }/* ========== 4. 检查完成通知 ========== */structcmsghdr *cm;for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {if (cm->cmsg_level == SOL_IP &&            cm->cmsg_type == IP_RECVERR) {structsock_extended_err *serr;            serr = (struct sock_extended_err *)CMSG_DATA(cm);if (serr->ee_errno == 0 &&                serr->ee_origin == SO_EE_ORIGIN_ZEROCOPY) {printf("Zero-copy send completed, id=%u\n",                      serr->ee_data);/* 现在可以安全地释放或重用缓冲区 */            }        }    }return sent;}

2.5 零拷贝技术对比

技术              拷贝次数    上下文切换    适用场景              限制--------------------------------------------------------------------------------read/write        4次         4次          通用                  性能差sendfile()        2-3次       2次          文件→socket           不能修改数据splice()          0次         2次          文件描述符之间        需要管道mmap()+write()    1次         4次          大文件                内存映射开销MSG_ZEROCOPY      0次         2次          大数据(>4KB)        需要等待通知性能对比(10GB 文件传输):read/write:       100%        基准sendfile():       150%        1.5倍性能splice():         180%        1.8倍性能MSG_ZEROCOPY:     200%        2倍性能

🔧 硬件 Offload 技术

3.1 TSO/GSO(分段卸载)

TSO(TCP Segmentation Offload):将 TCP 分段工作卸载到网卡硬件。

GSO(Generic Segmentation Offload):在软件层面延迟分段,减少协议栈处理。

架构对比

传统方式:应用层 ──► TCP 层 ──► IP 层 ──► 驱动 ──► 网卡         (分段)    (添加头)GSO/TSO:应用层 ──► TCP 层 ──► IP 层 ──► 驱动 ──► 网卡         (大包)    (大包)   (大包)   (硬件分段)

源码位置net/core/dev.c

/** * skb_gso_segment - GSO 分段 * @skb: 数据包 * @features: 设备特性 */struct sk_buff *skb_gso_segment(struct sk_buff *skb,netdev_features_t features){structsk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);structpacket_offload *ptype;    __be16 type = skb->protocol;/* ========== 1. 检查是否需要分段 ========== */if (!skb_is_gso(skb))returnNULL;/* ========== 2. 准备分段 ========== */    skb_reset_mac_header(skb);    skb_reset_mac_len(skb);/* ========== 3. 调用协议相关的分段函数 ========== */    rcu_read_lock();    list_for_each_entry_rcu(ptype, &offload_base, list) {if (ptype->type == type && ptype->callbacks.gso_segment) {            segs = ptype->callbacks.gso_segment(skb, features);break;        }    }    rcu_read_unlock();return segs;}/** * tcp_gso_segment - TCP GSO 分段 * @skb: 数据包 * @features: 设备特性 */struct sk_buff *tcp_gso_segment(struct sk_buff *skb,netdev_features_t features){structsk_buff *segs =NULL;structsk_buff *seg;structtcphdr *th;unsignedint thlen;unsignedint seq;    __be32 delta;unsignedint mss;/* ========== 1. 获取 TCP 头 ========== */    th = tcp_hdr(skb);    thlen = th->doff * 4;/* ========== 2. 获取 MSS ========== */    mss = skb_shinfo(skb)->gso_size;/* ========== 3. 分段 ========== */    seq = ntohl(th->seq);while (skb->len > mss) {/* 分配新的 skb */        seg = skb_segment(skb, features);if (IS_ERR(seg))goto out;/* 更新 TCP 序列号 */        th = tcp_hdr(seg);        th->seq = htonl(seq);        seq += mss;/* 添加到链表 */if (!segs)            segs = seg;else            segs->prev->next = seg;        seg->prev = segs->prev;        segs->prev = seg;    }out:return segs;}

启用 TSO/GSO

# 查看 TSO/GSO 状态ethtool -k eth0 | grep -E 'tcp-segmentation-offload|generic-segmentation-offload'# 启用 TSOethtool -K eth0 tso on# 启用 GSOethtool -K eth0 gso on# 查看 GSO 统计cat /proc/net/dev

3.2 GRO/LRO(聚合卸载)

GRO(Generic Receive Offload):在接收路径上聚合多个小包为大包。

LRO(Large Receive Offload):硬件层面的接收聚合。

源码位置net/core/dev.c

/** * napi_gro_receive - GRO 接收处理 * @napi: NAPI 结构 * @skb: 数据包 */gro_result_tnapi_gro_receive(struct napi_struct *napi, struct sk_buff *skb){gro_result_t ret;/* ========== 1. 准备 GRO ========== */    skb_gro_reset_offset(skb);/* ========== 2. 尝试聚合 ========== */    ret = napi_skb_finish(dev_gro_receive(napi, skb), skb);return ret;}/** * dev_gro_receive - GRO 核心处理 * @napi: NAPI 结构 * @skb: 数据包 */staticenum gro_result dev_gro_receive(struct napi_struct *napi,struct sk_buff *skb){structsk_buff **pp =NULL;structpacket_offload *ptype;    __be16 type = skb->protocol;structlist_head *head = &offload_base;enumgro_resultret = GRO_NORMAL;/* ========== 1. 检查是否可以 GRO ========== */if (netif_elide_gro(skb->dev))goto normal;/* ========== 2. 查找已有的 GRO 流 ========== */    list_for_each_entry_rcu(ptype, head, list) {if (ptype->type != type || !ptype->callbacks.gro_receive)continue;/* 调用协议相关的 GRO 函数 */        pp = ptype->callbacks.gro_receive(&napi->gro_list, skb);break;    }/* ========== 3. 处理 GRO 结果 ========== */if (pp) {/* 找到匹配的流,聚合成功 */structsk_buff *p = *pp;if (NAPI_GRO_CB(p)->same_flow) {/* 聚合到已有包 */            ret = GRO_MERGED;        } else {/* 刷新旧包,开始新流 */            ret = GRO_MERGED_FREE;        }    } else {/* 没有匹配的流,添加到 GRO 列表 */        NAPI_GRO_CB(skb)->flush = 0;        list_add(&skb->list, &napi->gro_list);        ret = GRO_HELD;    }return ret;normal:return GRO_NORMAL;}/** * tcp_gro_receive - TCP GRO 接收 * @head: GRO 列表头 * @skb: 当前数据包 */struct sk_buff **tcp_gro_receive(struct sk_buff **head, struct sk_buff *skb){structsk_buff **pp =NULL;structsk_buff *p;structtcphdr *th;structtcphdr *th2;unsignedint len;unsignedint thlen;    __be32 flags;unsignedint mss = 1;/* ========== 1. 获取 TCP 头 ========== */    th = tcp_hdr(skb);    thlen = th->doff * 4;/* ========== 2. 检查是否可以聚合 ========== */if (th->fin || th->syn || th->rst)goto out;  /* 控制包不聚合 *//* ========== 3. 查找匹配的流 ========== */for (; (p = *head); head = &p->next) {if (!NAPI_GRO_CB(p)->same_flow)continue;        th2 = tcp_hdr(p);/* 检查四元组是否匹配 */if (*(u32 *)&th->source ^ *(u32 *)&th2->source) {            NAPI_GRO_CB(p)->same_flow = 0;continue;        }/* 检查序列号是否连续 */if (ntohl(th->seq) != ntohl(th2->seq) + p->len) {            NAPI_GRO_CB(p)->same_flow = 0;continue;        }/* 找到匹配的流 */goto found;    }goto out;found:/* ========== 4. 聚合数据包 ========== */    mss = skb_shinfo(p)->gso_size;/* 更新 GRO 信息 */    NAPI_GRO_CB(skb)->same_flow = 1;    NAPI_GRO_CB(p)->count++;/* 合并数据 */    skb_gro_pull(skb, thlen);    pp = head;out:return pp;}

GRO 工作流程

启用 GRO

# 查看 GRO 状态ethtool -k eth0 | grep generic-receive-offload# 启用 GROethtool -K eth0 gro on# 查看 GRO 统计ethtool -S eth0 | grep gro

3.3 Checksum Offload

将校验和计算卸载到网卡硬件。

源码位置net/core/dev.c

/** * skb_checksum_help - 计算校验和 * @skb: 数据包 */intskb_checksum_help(struct sk_buff *skb){    __wsum csum;int ret = 0, offset;/* ========== 1. 检查是否需要计算校验和 ========== */if (skb->ip_summed == CHECKSUM_COMPLETE)goto out_set_summed;if (unlikely(skb_shinfo(skb)->gso_size)) {/* GSO 包,延迟计算 */return0;    }/* ========== 2. 计算校验和 ========== */    offset = skb_checksum_start_offset(skb);    BUG_ON(offset >= skb_headlen(skb));    csum = skb_checksum(skb, offset, skb->len - offset, 0);/* ========== 3. 填充校验和 ========== */    offset += skb->csum_offset;    BUG_ON(offset + sizeof(__sum16) > skb_headlen(skb));    *(__sum16 *)(skb->data + offset) = csum_fold(csum);out_set_summed:    skb->ip_summed = CHECKSUM_NONE;return ret;}

校验和模式

/* 校验和状态 */enum {    CHECKSUM_NONE = 0,          /* 未计算 */    CHECKSUM_UNNECESSARY = 1,   /* 硬件已验证 */    CHECKSUM_COMPLETE = 2,      /* 完整校验和 */    CHECKSUM_PARTIAL = 3,       /* 部分校验和(需要硬件完成)*/};/* 发送路径 */if (skb->ip_summed == CHECKSUM_PARTIAL) {/* 硬件将计算校验和 */if (dev->features & NETIF_F_HW_CSUM) {/* 硬件支持,直接发送 */    } else {/* 硬件不支持,软件计算 */        skb_checksum_help(skb);    }}/* 接收路径 */if (skb->ip_summed == CHECKSUM_UNNECESSARY) {/* 硬件已验证,跳过软件验证 */else {/* 软件验证校验和 */if (skb_checksum_complete(skb)) {/* 校验和错误,丢弃 */        kfree_skb(skb);    }}

启用 Checksum Offload

# 查看校验和卸载状态ethtool -k eth0 | grep checksum# 启用 TX 校验和卸载ethtool -K eth0 tx-checksumming on# 启用 RX 校验和卸载ethtool -K eth0 rx-checksumming on

3.4 RSS(Receive Side Scaling)

RSS 将接收流量分配到多个 CPU 核心。

RSS 工作原理

配置 RSS

# 查看 RSS 配置ethtool -x eth0# 查看队列数量ethtool -l eth0# 设置队列数量ethtool -L eth0 combined 4# 配置 RSS 哈希ethtool -X eth0 hfunc toeplitz hkey <key># 配置间接表ethtool -X eth0 equal 4

3.5 Offload 性能对比

特性              CPU 节省    吞吐量提升    适用场景----------------------------------------------------------------TSO/GSO          30-40%      20-30%        大数据传输GRO/LRO          20-30%      15-25%        高 PPS 场景Checksum         10-15%      5-10%         所有场景RSS              40-60%      50-100%       多核系统综合启用所有 Offload:- CPU 使用率降低:60-70%- 吞吐量提升:80-150%- 延迟降低:20-30%

⚙️ 中断优化

4.1 中断处理流程

传统中断处理

网卡接收数据      │      ▼硬件中断(IRQ)      │      ▼中断处理程序(ISR)      │      ├──► 禁用中断      ├──► 读取数据      ├──► 调度软中断      └──► 启用中断      │      ▼软中断(NET_RX_SOFTIRQ)      │      ▼NAPI 轮询      │      ▼协议栈处理

NAPI 机制

源码位置net/core/dev.c

/** * napi_schedule - 调度 NAPI 轮询 * @n: NAPI 结构 */staticinlinevoidnapi_schedule(struct napi_struct *n){if (napi_schedule_prep(n))        __napi_schedule(n);}/** * __napi_schedule - 实际调度 NAPI * @n: NAPI 结构 */void __napi_schedule(struct napi_struct *n){unsignedlong flags;    local_irq_save(flags);/* 添加到当前 CPU 的 softnet_data */    list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);/* 触发软中断 */    __raise_softirq_irqoff(NET_RX_SOFTIRQ);    local_irq_restore(flags);}/** * net_rx_action - NET_RX 软中断处理 * @h: 软中断参数 */staticvoidnet_rx_action(struct softirq_action *h){structsoftnet_data *sd = this_cpu_ptr(&softnet_data);unsignedlong time_limit = jiffies + usecs_to_jiffies(netdev_budget_usecs);int budget = netdev_budget;    LIST_HEAD(list);    LIST_HEAD(repoll);    local_irq_disable();    list_splice_init(&sd->poll_list, &list);    local_irq_enable();/* ========== 轮询所有 NAPI 设备 ========== */for (;;) {structnapi_struct *n;if (list_empty(&list))break;        n = list_first_entry(&liststruct napi_struct, poll_list);        list_del_init(&n->poll_list);/* ========== 调用驱动的 poll 函数 ========== */int work = n->poll(n, budget);        budget -= work;/* ========== 检查是否需要继续轮询 ========== */if (work >= budget || time_after(jiffies, time_limit)) {/* 超出预算或时间限制,重新调度 */            list_add_tail(&n->poll_list, &repoll);break;        }/* ========== 完成轮询 ========== */        napi_complete(n);    }/* ========== 重新调度未完成的 NAPI ========== */if (!list_empty(&repoll)) {        local_irq_disable();        list_splice(&repoll, &sd->poll_list);        __raise_softirq_irqoff(NET_RX_SOFTIRQ);        local_irq_enable();    }}

4.2 中断合并

减少中断频率,批量处理数据包。

配置中断合并

# 查看中断合并配置ethtool -c eth0# 配置 RX 中断合并ethtool -C eth0 rx-usecs 50 rx-frames 32# 配置 TX 中断合并ethtool -C eth0 tx-usecs 50 tx-frames 32# 自适应中断合并ethtool -C eth0 adaptive-rx on adaptive-tx on

中断合并参数

参数              说明                      推荐值----------------------------------------------------------------rx-usecs         接收中断延迟(微秒)      10-100rx-frames        接收帧数阈值              16-64tx-usecs         发送中断延迟(微秒)      10-100tx-frames        发送帧数阈值              16-64adaptive-rx      自适应接收中断            onadaptive-tx      自适应发送中断            on低延迟场景:- rx-usecs: 10-20- rx-frames: 8-16高吞吐量场景:- rx-usecs: 50-100- rx-frames: 32-64

4.3 中断亲和性

将中断绑定到特定 CPU 核心。

配置中断亲和性

# 查看网卡中断号cat /proc/interrupts | grep eth0# 查看中断亲和性cat /proc/irq/<IRQ>/smp_affinity# 设置中断亲和性(绑定到 CPU 0)echo 1 > /proc/irq/<IRQ>/smp_affinity# 设置中断亲和性(绑定到 CPU 0-3)echo f > /proc/irq/<IRQ>/smp_affinity# 使用 irqbalance 自动平衡systemctl start irqbalance

中断亲和性脚本

#!/bin/bash# 将网卡中断均匀分配到所有 CPUset_irq_affinity() {local interface=$1local cpus=$(nproc)local cpu=0# 获取网卡的所有中断for irq in $(cat /proc/interrupts | grep $interface | awk '{print $1}' | tr -d ':'); do# 计算 CPU 掩码local mask=$((1 << cpu))# 设置亲和性printf"%x"$mask > /proc/irq/$irq/smp_affinityecho"IRQ $irq -> CPU $cpu"# 下一个 CPU        cpu=$(( (cpu + 1) % cpus ))done}set_irq_affinity eth0

4.4 NAPI 调优

NAPI 参数

# 查看 NAPI 预算sysctl net.core.netdev_budgetsysctl net.core.netdev_budget_usecs# 调整 NAPI 预算(每次软中断处理的包数)sysctl -w net.core.netdev_budget=600# 调整 NAPI 时间预算(微秒)sysctl -w net.core.netdev_budget_usecs=8000# 调整 backlog 队列大小sysctl -w net.core.netdev_max_backlog=5000

NAPI 权重

/* 驱动中设置 NAPI 权重 */netif_napi_add(dev, &priv->napi, stmmac_poll, 64);/* 权重建议: * - 低延迟:16-32 * - 平衡:64 * - 高吞吐量:128-256 */

💾 内存优化

5.1 sk_buff 内存池

sk_buff 是网络子系统中最频繁分配和释放的对象。

sk_buff 缓存

源码位置net/core/skbuff.c

/* sk_buff 缓存 */staticstructkmem_cache *skbuff_head_cache __read_mostly;staticstructkmem_cache *skbuff_fclone_cache __read_mostly;/** * skb_init - 初始化 sk_buff 缓存 */void __init skb_init(void){/* ========== 1. 创建 sk_buff 缓存 ========== */    skbuff_head_cache = kmem_cache_create("skbuff_head_cache",sizeof(struct sk_buff),0,        SLAB_HWCACHE_ALIGN|SLAB_PANIC,NULL);/* ========== 2. 创建 fclone 缓存 ========== */    skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",2 * sizeof(struct sk_buff),0,        SLAB_HWCACHE_ALIGN|SLAB_PANIC,NULL);}/** * __alloc_skb - 分配 sk_buff * @size: 数据大小 * @gfp_mask: 分配标志 * @flags: SKB 标志 * @node: NUMA 节点 */structsk_buff *__alloc_skb(unsignedintsizegfp_tgfp_mask,intflagsintnode){structkmem_cache *cache;structskb_shared_info *shinfo;structsk_buff *skb;    u8 *data;bool pfmemalloc;/* ========== 1. 选择缓存 ========== */    cache = (flags & SKB_ALLOC_FCLONE)        ? skbuff_fclone_cache : skbuff_head_cache;/* ========== 2. 从缓存分配 sk_buff ========== */    skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);if (!skb)goto out;/* ========== 3. 分配数据缓冲区 ========== */    size = SKB_DATA_ALIGN(size);    size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));    data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);if (!data)goto nodata;/* ========== 4. 初始化 sk_buff ========== */memset(skb, 0, offsetof(struct sk_buff, tail));    skb->truesize = SKB_TRUESIZE(size);    skb->pfmemalloc = pfmemalloc;    refcount_set(&skb->users, 1);    skb->head = data;    skb->data = data;    skb_reset_tail_pointer(skb);    skb->end = skb->tail + size;/* ========== 5. 初始化 skb_shared_info ========== */    shinfo = skb_shinfo(skb);memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));atomic_set(&shinfo->dataref, 1);return skb;nodata:    kmem_cache_free(cache, skb);out:returnNULL;}

5.2 页面回收优化

页面池(Page Pool)

源码位置net/core/page_pool.c

/** * struct page_pool - 页面池 */structpage_pool {structpage_pool_paramsp;/* 快速缓存(per-CPU)*/structptr_ringring;/* 慢速缓存(全局)*/structpage *alloc_pages;/* 统计信息 */structpage_pool_statsstats;/* DMA 映射 */structdevice *dev;dma_addr_t dma_addr;};/** * page_pool_alloc_pages - 从页面池分配页面 * @pool: 页面池 * @gfp: 分配标志 */struct page *page_pool_alloc_pages(struct page_pool *pool, gfp_t gfp){structpage *page;/* ========== 1. 尝试从快速缓存获取 ========== */    page = __ptr_ring_consume(&pool->ring);if (page) {        pool->stats.fast++;return page;    }/* ========== 2. 从慢速缓存获取 ========== */    page = pool->alloc_pages;if (page) {        pool->alloc_pages = page->next;        pool->stats.slow++;return page;    }/* ========== 3. 分配新页面 ========== */    page = alloc_pages_node(pool->p.nid, gfp, pool->p.order);if (!page) {        pool->stats.empty++;returnNULL;    }/* ========== 4. DMA 映射 ========== */if (pool->p.flags & PP_FLAG_DMA_MAP) {        page->dma_addr = dma_map_page(pool->dev, page, 0,                                     PAGE_SIZE << pool->p.order,                                     pool->p.dma_dir);if (dma_mapping_error(pool->dev, page->dma_addr)) {            __free_pages(page, pool->p.order);returnNULL;        }    }    pool->stats.alloc++;return page;}/** * page_pool_put_page - 归还页面到页面池 * @pool: 页面池 * @page: 页面 * @allow_direct: 是否允许直接回收 */voidpage_pool_put_page(struct page_pool *pool, struct page *page,bool allow_direct){/* ========== 1. 检查引用计数 ========== */if (page_ref_count(page) != 1)return;/* ========== 2. 尝试放入快速缓存 ========== */if (allow_direct && __ptr_ring_produce(&pool->ring, page) == 0) {        pool->stats.fast++;return;    }/* ========== 3. 放入慢速缓存 ========== */    page->next = pool->alloc_pages;    pool->alloc_pages = page;    pool->stats.slow++;}

5.3 NUMA 感知

NUMA 架构

NUMA 优化

/** * numa_aware_alloc_skb - NUMA 感知的 sk_buff 分配 * @size: 数据大小 * @gfp_mask: 分配标志 */struct sk_buff *numa_aware_alloc_skb(unsignedint size, gfp_t gfp_mask){int node = numa_node_id();  /* 当前 CPU 的 NUMA 节点 */return __alloc_skb(size, gfp_mask, 0, node);}

配置 NUMA

# 查看 NUMA 拓扑numactl --hardware# 查看进程的 NUMA 绑定numactl --show# 将进程绑定到 NUMA 节点 0numactl --cpunodebind=0 --membind=0 ./server# 查看 NUMA 统计numastat# 查看网卡所在的 NUMA 节点cat /sys/class/net/eth0/device/numa_node

5.4 内存预分配

RX 缓冲区预分配

/** * preallocate_rx_buffers - 预分配接收缓冲区 * @priv: 驱动私有数据 * @queue: 队列索引 */staticintpreallocate_rx_buffers(struct stmmac_priv *priv, u32 queue){structstmmac_rx_queue *rx_q = &priv->rx_queue[queue];int i;/* ========== 预分配所有 RX 描述符的缓冲区 ========== */for (i = 0; i < DMA_RX_SIZE; i++) {structdma_desc *p;structsk_buff *skb;dma_addr_t dma_addr;        p = rx_q->dma_rx + i;/* 分配 sk_buff */        skb = __netdev_alloc_skb_ip_align(priv->dev,                                         priv->dma_buf_sz,                                         GFP_KERNEL);if (!skb)return -ENOMEM;/* DMA 映射 */        dma_addr = dma_map_single(priv->device, skb->data,                                 priv->dma_buf_sz,                                 DMA_FROM_DEVICE);if (dma_mapping_error(priv->device, dma_addr)) {            dev_kfree_skb(skb);return -ENOMEM;        }/* 设置描述符 */        rx_q->rx_skbuff[i] = skb;        rx_q->rx_skbuff_dma[i] = dma_addr;        p->des0 = cpu_to_le32(dma_addr);        p->des1 = 0;/* 设置为可用 */        dma_wmb();        stmmac_set_rx_owner(priv, p, priv->use_rxtoe);    }return0;}

🔀 多队列技术

6.1 RPS(Receive Packet Steering)

软件实现的接收包分发。

源码位置net/core/dev.c

/** * get_rps_cpu - 获取 RPS 目标 CPU * @dev: 网络设备 * @skb: 数据包 * @rflow: RFS 流 */staticintget_rps_cpu(struct net_device *dev, struct sk_buff *skb,struct rps_dev_flow **rflowp){structnetdev_rx_queue *rxqueue;structrps_map *map;structrps_dev_flow_table *flow_table;structrps_sock_flow_table *sock_flow_table;int cpu = -1;    u32 hash;/* ========== 1. 检查是否启用 RPS ========== */if (!rcu_access_pointer(dev->_rx))goto done;    rxqueue = dev->_rx;map = rcu_dereference(rxqueue->rps_map);    flow_table = rcu_dereference(rxqueue->rps_flow_table);    sock_flow_table = rcu_dereference(rps_sock_flow_table);if (!map && !flow_table)goto done;/* ========== 2. 计算哈希值 ========== */    hash = skb_get_hash(skb);if (!hash)goto done;/* ========== 3. 查找 RFS 流 ========== */if (flow_table && sock_flow_table) {structrps_dev_flow *rflow;        u32 next_cpu;        u32 ident;/* 查找流表 */        ident = sock_flow_table->ents[hash & sock_flow_table->mask];if ((ident ^ hash) & ~rps_cpu_mask)goto try_rps;        next_cpu = ident & rps_cpu_mask;/* 查找设备流表 */        rflow = &flow_table->flows[hash & flow_table->mask];        cpu = next_cpu;if (rflow->cpu == cpu)goto done;  /* 流已在正确的 CPU 上 *//* 更新流 */        rflow->cpu = cpu;        rflow->filter = hash;        *rflowp = rflow;goto done;    }try_rps:/* ========== 4. 使用 RPS 映射 ========== */if (map) {        cpu = map->cpus[reciprocal_scale(hash, map->len)];if (cpu_online(cpu))goto done;    }done:return cpu;}

配置 RPS

# 查看 RPS 配置cat /sys/class/net/eth0/queues/rx-0/rps_cpus# 启用 RPS(所有 CPU)echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus# 启用 RPS(CPU 0-3)echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus# 配置 RPS 流表大小echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

6.2 RFS(Receive Flow Steering)

基于流的接收包分发,将数据包发送到应用程序所在的 CPU。

配置 RFS

# 全局 RFS 表大小echo 32768 > /proc/sys/net/core/rps_sock_flow_entries# 每个队列的 RFS 表大小echo 2048 > /sys/class/net/eth0/queues/rx-0/rps_flow_cntecho 2048 > /sys/class/net/eth0/queues/rx-1/rps_flow_cnt

6.3 XPS(Transmit Packet Steering)

发送包分发,将发送队列绑定到特定 CPU。

配置 XPS

# 查看 XPS 配置cat /sys/class/net/eth0/queues/tx-0/xps_cpus# 将 TX 队列 0 绑定到 CPU 0echo 1 > /sys/class/net/eth0/queues/tx-0/xps_cpus# 将 TX 队列 1 绑定到 CPU 1echo 2 > /sys/class/net/eth0/queues/tx-1/xps_cpus# 将 TX 队列 2 绑定到 CPU 2echo 4 > /sys/class/net/eth0/queues/tx-2/xps_cpus# 将 TX 队列 3 绑定到 CPU 3echo 8 > /sys/class/net/eth0/queues/tx-3/xps_cpus

6.4 多队列配置最佳实践

完整配置脚本

#!/bin/bashINTERFACE="eth0"NUM_CPUS=$(nproc)# ========== 1. 设置网卡队列数 ==========ethtool -L $INTERFACE combined $NUM_CPUS# ========== 2. 配置中断亲和性 ==========cpu=0for irq in $(cat /proc/interrupts | grep $INTERFACE | awk '{print $1}' | tr -d ':'); do    mask=$((1 << cpu))printf"%x"$mask > /proc/irq/$irq/smp_affinityecho"IRQ $irq -> CPU $cpu"    cpu=$(( (cpu + 1) % NUM_CPUS ))done# ========== 3. 配置 RPS ==========for queue in /sys/class/net/$INTERFACE/queues/rx-*; doecho f > $queue/rps_cpusecho 2048 > $queue/rps_flow_cntdone# ========== 4. 配置 RFS ==========echo 32768 > /proc/sys/net/core/rps_sock_flow_entries# ========== 5. 配置 XPS ==========queue=0for xps in /sys/class/net/$INTERFACE/queues/tx-*/xps_cpus; do    mask=$((1 << queue))printf"%x"$mask > $xpsecho"TX queue $queue -> CPU $queue"    queue=$(( (queue + 1) % NUM_CPUS ))done# ========== 6. 启用硬件 Offload ==========ethtool -K $INTERFACE tso on gso on gro onethtool -K $INTERFACE rx-checksumming on tx-checksumming on# ========== 7. 配置中断合并 ==========ethtool -C $INTERFACE adaptive-rx on adaptive-tx onecho"Multi-queue configuration completed for $INTERFACE"

📊 性能测试与分析

7.1 性能测试工具

iperf3 测试

# 服务器端iperf3 -s# 客户端 TCP 测试iperf3 -c <server_ip> -t 60 -P 4# 客户端 UDP 测试iperf3 -c <server_ip> -u -b 10G -t 60# 反向测试(服务器发送)iperf3 -c <server_ip> -R -t 60# JSON 输出iperf3 -c <server_ip> -J > results.json

netperf 测试

# 服务器端netserver# TCP 流测试netperf -H <server_ip> -t TCP_STREAM -l 60# TCP 请求/响应测试netperf -H <server_ip> -t TCP_RR -l 60# UDP 流测试netperf -H <server_ip> -t UDP_STREAM -l 60# 指定消息大小netperf -H <server_ip> -t TCP_STREAM -- -m 8192

7.2 性能分析工具

perf 分析

# 记录网络相关的性能数据perf record -e net:* -a -g -- sleep 10# 查看报告perf report# 实时监控perf top -e cycles# 分析特定进程perf record -p <pid> -g -- sleep 10# 生成火焰图perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg

bpftrace 分析

# 跟踪网络发送bpftrace -e 'kprobe:dev_queue_xmit { @bytes = hist(arg0->len); }'# 跟踪网络接收bpftrace -e 'kprobe:netif_receive_skb { @[comm] = count(); }'# 跟踪 TCP 连接bpftrace -e 'kprobe:tcp_connect { printf("%s -> %s\n", comm, str(arg0)); }'# 跟踪丢包bpftrace -e 'tracepoint:skb:kfree_skb { @[stack] = count(); }'

7.3 系统监控

实时监控脚本

#!/bin/bash# 网络性能监控脚本INTERFACE="eth0"INTERVAL=1echo"Monitoring $INTERFACE (Ctrl+C to stop)"echo"Time,RX_pps,TX_pps,RX_Mbps,TX_Mbps,RX_drops,TX_drops"# 获取初始值rx_bytes_old=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)tx_bytes_old=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)rx_packets_old=$(cat /sys/class/net/$INTERFACE/statistics/rx_packets)tx_packets_old=$(cat /sys/class/net/$INTERFACE/statistics/tx_packets)whiletruedosleep$INTERVAL# 获取当前值    rx_bytes=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)    tx_bytes=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)    rx_packets=$(cat /sys/class/net/$INTERFACE/statistics/rx_packets)    tx_packets=$(cat /sys/class/net/$INTERFACE/statistics/tx_packets)    rx_drops=$(cat /sys/class/net/$INTERFACE/statistics/rx_dropped)    tx_drops=$(cat /sys/class/net/$INTERFACE/statistics/tx_dropped)# 计算速率    rx_pps=$(( (rx_packets - rx_packets_old) / INTERVAL ))    tx_pps=$(( (tx_packets - tx_packets_old) / INTERVAL ))    rx_mbps=$(( (rx_bytes - rx_bytes_old) * 8 / INTERVAL / 1000000 ))    tx_mbps=$(( (tx_bytes - tx_bytes_old) * 8 / INTERVAL / 1000000 ))# 输出    timestamp=$(date +%H:%M:%S)echo"$timestamp,$rx_pps,$tx_pps,$rx_mbps,$tx_mbps,$rx_drops,$tx_drops"# 更新旧值    rx_bytes_old=$rx_bytes    tx_bytes_old=$tx_bytes    rx_packets_old=$rx_packets    tx_packets_old=$tx_packetsdone

💡 实战案例

8.1 案例 1:高性能 Web 服务器优化

优化前性能

  • • 吞吐量:5 Gbps
  • • CPU 使用率:80%
  • • 延迟:50ms

优化步骤

#!/bin/bash# ========== 1. 系统参数优化 ==========# 增大文件描述符限制ulimit -n 1000000# 增大 TCP 缓冲区sysctl -w net.core.rmem_max=16777216sysctl -w net.core.wmem_max=16777216sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"# 增大连接队列sysctl -w net.core.somaxconn=4096sysctl -w net.ipv4.tcp_max_syn_backlog=8192# 启用 TCP Fast Opensysctl -w net.ipv4.tcp_fastopen=3# ========== 2. 网卡优化 ==========INTERFACE="eth0"# 启用所有 Offloadethtool -K $INTERFACE tso on gso on gro onethtool -K $INTERFACE rx-checksumming on tx-checksumming on# 配置多队列ethtool -L $INTERFACE combined 8# 配置中断合并ethtool -C $INTERFACE adaptive-rx on adaptive-tx on# ========== 3. 中断优化 ==========# 绑定中断到不同 CPU./set_irq_affinity.sh $INTERFACE# ========== 4. 应用层优化 ==========# 使用 SO_REUSEPORT# 启用 epoll# 使用 sendfile 或 MSG_ZEROCOPY

优化后性能

  • • 吞吐量:9 Gbps(提升 80%)
  • • CPU 使用率:45%(降低 44%)
  • • 延迟:15ms(降低 70%)

8.2 案例 2:数据库服务器网络优化

场景:MySQL 数据库服务器,大量小查询。

优化配置

#!/bin/bash# ========== 1. 低延迟优化 ==========# 禁用 Nagle 算法sysctl -w net.ipv4.tcp_nodelay=1# 减小中断合并延迟ethtool -C eth0 rx-usecs 10 rx-frames 8# 启用 TCP Quick ACKsysctl -w net.ipv4.tcp_quickack=1# ========== 2. 连接优化 ==========# 增大连接跟踪表sysctl -w net.netfilter.nf_conntrack_max=1048576# 减小 TIME_WAIT 时间sysctl -w net.ipv4.tcp_fin_timeout=15# 启用 TIME_WAIT 重用sysctl -w net.ipv4.tcp_tw_reuse=1# ========== 3. 内存优化 ==========# 调整 TCP 内存sysctl -w net.ipv4.tcp_mem="786432 1048576 1572864"# 调整 socket 缓冲区sysctl -w net.core.optmem_max=40960

8.3 案例 3:视频流服务器优化

场景:实时视频流传输,要求高吞吐量和低延迟。

优化配置

#!/bin/bash# ========== 1. UDP 优化 ==========# 增大 UDP 缓冲区sysctl -w net.core.rmem_default=26214400sysctl -w net.core.rmem_max=26214400sysctl -w net.core.wmem_default=26214400sysctl -w net.core.wmem_max=26214400# 增大 UDP 接收队列sysctl -w net.core.netdev_max_backlog=30000# ========== 2. 多播优化 ==========# 增大多播缓冲区sysctl -w net.core.optmem_max=40960# ========== 3. 零拷贝 ==========# 应用层使用 MSG_ZEROCOPY# 或使用 sendfile# ========== 4. 硬件优化 ==========# 启用 TSO/GSOethtool -K eth0 tso on gso on# 增大 TX 队列ethtool -G eth0 tx 4096# 配置多队列ethtool -L eth0 combined 16

📝 总结

9.1 核心要点

零拷贝技术

  • • sendfile():文件到 socket,2-3 次拷贝
  • • splice():文件描述符之间,0 次拷贝
  • • mmap() + write():内存映射,1 次拷贝
  • • MSG_ZEROCOPY:真正零拷贝,适合大数据传输

硬件 Offload

  • • TSO/GSO:分段卸载,减少协议栈处理
  • • GRO/LRO:接收聚合,减少中断次数
  • • Checksum Offload:校验和卸载,降低 CPU 负载
  • • RSS:硬件多队列,分散中断到多个 CPU

中断优化

  • • NAPI 轮询:减少中断频率
  • • 中断合并:批量处理数据包
  • • 中断亲和性:绑定中断到特定 CPU
  • • 软中断预算:控制每次处理的包数

内存优化

  • • sk_buff 缓存:减少内存分配开销
  • • 页面池:复用页面,减少 DMA 映射
  • • NUMA 感知:本地内存访问
  • • 预分配:避免运行时分配

多队列技术

  • • RSS:硬件队列分发
  • • RPS:软件包分发
  • • RFS:基于流的分发
  • • XPS:发送队列绑定

9.2 性能优化检查清单

系统级优化

# 1. 文件描述符ulimit -n 1000000# 2. TCP 缓冲区sysctl -w net.core.rmem_max=16777216sysctl -w net.core.wmem_max=16777216sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"# 3. 连接队列sysctl -w net.core.somaxconn=4096sysctl -w net.ipv4.tcp_max_syn_backlog=8192# 4. 网络设备队列sysctl -w net.core.netdev_max_backlog=5000sysctl -w net.core.netdev_budget=600# 5. TCP 优化sysctl -w net.ipv4.tcp_fastopen=3sysctl -w net.ipv4.tcp_tw_reuse=1sysctl -w net.ipv4.tcp_fin_timeout=30

网卡级优化

# 1. Offload 特性ethtool -K eth0 tso on gso on gro onethtool -K eth0 rx-checksumming on tx-checksumming on# 2. 多队列ethtool -L eth0 combined 8# 3. 中断合并ethtool -C eth0 adaptive-rx on adaptive-tx on# 4. Ring 缓冲区ethtool -G eth0 rx 4096 tx 4096

应用级优化

// 1. SO_REUSEPORTint reuse = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));// 2. TCP_NODELAYint nodelay = 1;setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));// 3. 零拷贝sendfile(sockfd, filefd, &offset, size);// 或send(sockfd, buf, len, MSG_ZEROCOPY);// 4. 批量操作sendmmsg(sockfd, msgvec, vlen, flags);recvmmsg(sockfd, msgvec, vlen, flags, timeout);

9.3 性能优化效果

典型优化效果

优化项目              吞吐量提升    CPU 降低    延迟降低----------------------------------------------------------------零拷贝技术            50-100%       30-50%      10-20%硬件 Offload          80-150%       60-70%      20-30%中断优化              30-50%        40-60%      15-25%多队列技术            50-100%       40-60%      10-20%内存优化              20-40%        20-30%      5-10%综合优化效果:- 吞吐量提升:200-400%- CPU 使用率降低:70-80%- 延迟降低:40-60%

9.4 不同场景的优化策略

高吞吐量场景(文件传输、视频流):

  • • 启用所有 Offload 特性
  • • 使用零拷贝技术
  • • 增大缓冲区和队列
  • • 配置多队列和 RSS

低延迟场景(游戏、金融交易):

  • • 禁用 Nagle 算法
  • • 减小中断合并延迟
  • • 使用 CPU 亲和性
  • • 启用 TCP Quick ACK

高并发场景(Web 服务器):

  • • 使用 SO_REUSEPORT
  • • 启用 TCP Fast Open
  • • 增大连接队列
  • • 使用 epoll + 多线程

混合场景

  • • 根据流量类型动态调整
  • • 使用 QoS 分类
  • • 配置多个队列优先级
  • • 监控和自适应调整

9.5 下一篇预告

下一篇《eBPF/XDP 网络加速》将深入分析:

  • • XDP 框架和工作原理
  • • eBPF 网络应用
  • • XDP 程序开发
  • • 性能对比和适用场景

❓ 常见问题(FAQ)

10.1 零拷贝技术如何选择?

选择依据

场景
推荐技术
原因
小文件(<64KB)
read/write
零拷贝开销大于收益
中等文件(64KB-1MB)
sendfile()
简单高效
大文件(>1MB)
sendfile() 或 splice()
性能最优
需要修改数据
mmap() + write()
可以修改数据
超大数据流
MSG_ZEROCOPY
真正零拷贝
网络代理
splice()
零拷贝转发

性能对比(传输 1GB 文件):

方法              时间      CPU 使用率----------------------------------------read/write       10.0s     100%sendfile()       6.5s      65%splice()         5.8s      58%MSG_ZEROCOPY     5.0s      50%

10.2 如何判断是否需要启用 GRO?

启用 GRO 的条件

  1. 1. 高 PPS(packets per second)场景
  2. 2. 小包为主的流量
  3. 3. CPU 成为瓶颈
  4. 4. 网卡支持 GRO

检查方法

# 1. 查看 PPSsar -n DEV 1 10# 2. 查看 CPU 使用率mpstat -P ALL 1# 3. 查看网卡是否支持ethtool -k eth0 | grep generic-receive-offload# 4. 测试效果# 禁用 GROethtool -K eth0 gro offiperf3 -c <server> -t 60# 启用 GROethtool -K eth0 gro oniperf3 -c <server> -t 60

GRO 效果

  • • PPS 降低:50-70%
  • • CPU 使用率降低:20-30%
  • • 吞吐量提升:15-25%

10.3 中断合并参数如何设置?

参数说明

rx-usecs:接收中断延迟(微秒)rx-frames:接收帧数阈值低延迟场景:- rx-usecs: 10-20- rx-frames: 8-16高吞吐量场景:- rx-usecs: 50-100- rx-frames: 32-64平衡场景:- rx-usecs: 25-50- rx-frames: 16-32

自适应中断合并

# 启用自适应ethtool -C eth0 adaptive-rx on adaptive-tx on# 内核会根据流量自动调整参数

10.4 多队列数量如何确定?

队列数量建议

CPU 核心数    队列数量    说明----------------------------------------1-2          1-2         单队列或双队列4            4           队列数 = CPU 数8            8           队列数 = CPU 数16+          8-16        不超过 16 个队列原则:1. 队列数 ≤ CPU 核心数2. 队列数 = 2^n(便于哈希)3. 考虑 NUMA 拓扑

检查和设置

# 查看当前队列数ethtool -l eth0# 查看最大支持队列数ethtool -l eth0 | grep -A 4 "Pre-set"# 设置队列数ethtool -L eth0 combined 8

10.5 NUMA 系统如何优化网络性能?

NUMA 优化策略

  1. 1. 网卡绑定到 NUMA 节点
# 查看网卡所在 NUMA 节点cat /sys/class/net/eth0/device/numa_node# 查看 CPU 的 NUMA 节点lscpu | grep NUMA
  1. 2. 中断绑定到同一 NUMA 节点
# 获取网卡所在节点的 CPU 列表numactl --hardware | grep "node 0 cpus"# 绑定中断到这些 CPU./set_irq_affinity.sh eth0 0-7
  1. 3. 进程绑定到同一 NUMA 节点
# 绑定进程到 NUMA 节点 0numactl --cpunodebind=0 --membind=0 ./server
  1. 4. 内存分配优化
/* 从本地 NUMA 节点分配内存 */int node = numa_node_id();void *mem = numa_alloc_onnode(size, node);

10.6 如何诊断网络性能瓶颈?

诊断步骤

  1. 1. 检查硬件限制
# 网卡速度ethtool eth0 | grep Speed# 链路状态ethtool eth0 | grep "Link detected"# 错误统计ethtool -S eth0 | grep -i error
  1. 2. 检查 CPU 瓶颈
# CPU 使用率mpstat -P ALL 1# 软中断 CPU 使用率mpstat -P ALL 1 | grep -E "CPU|%soft"# 查看哪个 CPU 处理网络中断cat /proc/interrupts | grep eth0
  1. 3. 检查内存瓶颈
# 内存使用free -h# sk_buff 分配失败cat /proc/net/sockstat# 丢包统计netstat -s | grep -i drop
  1. 4. 检查队列瓶颈
# 接收队列丢包ethtool -S eth0 | grep rx_queue.*drop# backlog 队列丢包cat /proc/net/softnet_stat
  1. 5. 使用 perf 分析
# 记录性能数据perf record -a -g -- sleep 10# 查看热点函数perf report# 查看网络相关函数perf report | grep -E "net|tcp|ip"

10.7 TSO/GSO 和 GRO 可以同时启用吗?

可以同时启用,它们作用于不同方向:

发送方向:应用 → TCP → IP → TSO/GSO → 网卡                    ↓              延迟分段,减少处理接收方向:网卡 → GRO → IP → TCP → 应用        ↓    聚合小包,减少处理

配置

# 同时启用ethtool -K eth0 tso on gso on gro on# 验证ethtool -k eth0 | grep -E "tcp-segmentation|generic-segmentation|generic-receive"

效果

  • • 发送:大包延迟分段,减少协议栈处理
  • • 接收:小包聚合,减少中断和处理次数
  • • 综合:CPU 使用率降低 50-70%

10.8 如何监控网络性能优化效果?

监控指标

  1. 1. 吞吐量
# 实时监控iftop -i eth0# 历史数据sar -n DEV 1 10
  1. 2. PPS(包速率)
# 查看 PPScat /sys/class/net/eth0/statistics/rx_packetscat /sys/class/net/eth0/statistics/tx_packets
  1. 3. CPU 使用率
# 总体 CPUtop# 软中断 CPUmpstat -P ALL 1 | grep %soft
  1. 4. 丢包率
# 网卡丢包ethtool -S eth0 | grep drop# 协议栈丢包netstat -s | grep -i drop# 队列丢包cat /proc/net/softnet_stat
  1. 5. 延迟
# ICMP 延迟ping -c 100 <target># TCP 延迟ss -ti | grep rtt

监控脚本

#!/bin/bash# 综合性能监控whiletruedoecho"=== $(date) ==="# 吞吐量echo"Throughput:"    sar -n DEV 1 1 | grep eth0# CPUecho"CPU:"    mpstat 1 1 | tail -1# 丢包echo"Drops:"    netstat -s | grep -i "segments retransmited"echo""sleep 5done

10.9 优化后性能反而下降怎么办?

可能原因和解决方法

  1. 1. 过度优化
# 问题:队列过多导致缓存失效# 解决:减少队列数ethtool -L eth0 combined 4# 问题:缓冲区过大导致延迟增加# 解决:减小缓冲区sysctl -w net.ipv4.tcp_rmem="4096 87380 4194304"
  1. 2. 配置冲突
# 问题:RPS 和 RSS 冲突# 解决:只启用 RSSecho 0 > /sys/class/net/eth0/queues/rx-0/rps_cpus
  1. 3. 硬件不支持
# 问题:网卡不支持某些 Offload# 解决:检查并禁用不支持的特性ethtool -k eth0ethtool -K eth0 <feature> off
  1. 4. NUMA 配置错误
# 问题:跨 NUMA 访问# 解决:检查并修正绑定numactl --hardwarenumactl --cpunodebind=0 --membind=0 ./server

10.10 如何进行 A/B 测试验证优化效果?

测试流程

  1. 1. 建立基准
# 记录优化前性能iperf3 -c <server> -t 60 -J > baseline.json
  1. 2. 应用优化
# 应用单项优化ethtool -K eth0 gro on
  1. 3. 测试优化效果
# 记录优化后性能iperf3 -c <server> -t 60 -J > optimized.json
  1. 4. 对比分析
import json# 读取数据withopen('baseline.json'as f:    baseline = json.load(f)withopen('optimized.json'as f:    optimized = json.load(f)# 对比吞吐量baseline_bps = baseline['end']['sum_received']['bits_per_second']optimized_bps = optimized['end']['sum_received']['bits_per_second']improvement = (optimized_bps - baseline_bps) / baseline_bps * 100print(f"Throughput improvement: {improvement:.2f}%")
  1. 5. 多次测试取平均
#!/bin/bash# 运行 10 次测试取平均for i in {1..10}; do    iperf3 -c <server> -t 60 -J > test_$i.jsondone# 计算平均值python calculate_average.py test_*.json

📚 参考资料

11.1 内核源码文件

零拷贝相关

fs/read_write.c                 # sendfile 实现fs/splice.c                     # splice 实现net/core/skbuff.c              # MSG_ZEROCOPY 实现

Offload 相关

net/core/dev.c                  # GSO/GRO 实现net/ipv4/tcp_offload.c         # TCP Offloadnet/ipv4/tcp_input.c           # GRO 接收

中断和 NAPI

net/core/dev.c                  # NAPI 实现kernel/softirq.c               # 软中断处理

多队列

net/core/dev.c                  # RPS/RFS/XPS 实现drivers/net/ethernet/          # 网卡驱动

11.2 内核文档

官方文档

Documentation/networking/scaling.txt           # 网络扩展技术Documentation/networking/segmentation-offloads.txt  # 分段卸载Documentation/networking/msg_zerocopy.rst      # MSG_ZEROCOPYDocumentation/networking/napi.txt              # NAPI 文档

在线文档

  • • Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/
  • • Networking Documentation: https://www.kernel.org/doc/html/latest/networking/

11.3 性能测试工具

网络测试

# iperf3https://github.com/esnet/iperf# netperfhttps://github.com/HewlettPackard/netperf# wrk (HTTP 测试)https://github.com/wg/wrk

性能分析

# perfman perf# bpftracehttps://github.com/iovisor/bpftrace# FlameGraphhttps://github.com/brendangregg/FlameGraph

11.4 经典书籍

网络性能

  1. 1. 《Systems Performance: Enterprise and the Cloud》
    • • 作者:Brendan Gregg
    • • 全面的性能分析方法
  2. 2. 《Linux Performance Tuning》
    • • Linux 性能调优指南
  3. 3. 《High Performance Browser Networking》
    • • 作者:Ilya Grigorik
    • • 网络性能优化

内核网络4. 《深入理解 Linux 网络技术内幕》

  • • 包含性能优化章节
  1. 5. 《Linux Kernel Networking》
    • • 深入讲解内核网络实现

11.5 在线资源

技术博客

  • • Brendan Gregg’s Blog: http://www.brendangregg.com/
  • • LWN.net: https://lwn.net/
  • • Cloudflare Blog: https://blog.cloudflare.com/

性能优化

  • • The C10K Problem: http://www.kegel.com/c10k.html
  • • Linux Network Performance: https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/

工具和脚本

  • • perf-tools: https://github.com/brendangregg/perf-tools
  • • bcc-tools: https://github.com/iovisor/bcc

作者:肇中内核版本:Linux 5.10 LTS

系列文章

  • • 上一篇:《第12篇:网络命名空间(Network Namespace)》
  • • 下一篇:《第14篇:eBPF/XDP 网络加速》

勘误和建议:欢迎提出勘误和改进建议。