Linux 网络调优之重新认识 Linux 本机网络
每个人都想成为生活中的重要的人物。事实是,不论他们多么重要,总会出现更重要的人,人们很计较这件事。他们没有意识到,别人是否看重你,根本不重要。重要的是自信,一旦有了自信,人就会赢得一切。—塞缪尔·克罗瑟斯
写在前面
- 博文涉及跨主机网络IO认知以及本机网络IO分析
- 没有调优相关的Demo
- 理解不足小伙伴帮忙指正 :),生活加油
每个人都想成为生活中的重要的人物。事实是,不论他们多么重要,总会出现更重要的人,人们很计较这件事。他们没有意识到,别人是否看重你,根本不重要。重要的是自信,一旦有了自信,人就会赢得一切。—塞缪尔·克罗瑟斯
一、跨主机网络IO认知
在传统的跨主机网络IO中,本机发包和收包的流程是这样的:
发包流程
发包:
- 用户态应用程序(用户态程序发生数据,触发send 系统调用)
- 内核态网络协议栈(拷贝数据到内核态,经过TCP/IP 协议栈处理,TCP封包,IP路由选择获取吓一跳地址,邻居子系统ARP获取MAC地址,网络设备子系统调用网卡驱动进⼊RingBuffer)
- 内核态网络设备驱动(内核态调用驱动对应的函数发送数据包,CPU时间片内没有发送完触发软中断)
- 网卡(数据发送完触发硬中断,清空RingBuffer缓冲区)
1 | 1. 用户态应用程序 |
收包流程
收包:
- 网卡(数据写入到网卡缓冲区DMA,触发硬中断通知CPU)
- 内核态网络设备驱动(CPU触发软中断,软中断处理线程收取数据包从网卡缓冲区DMA拷贝到内核态)
- 内核态网络协议栈(内核态协议栈解析数据包拷贝到用户态,唤醒用户进程)
- 用户态应用程序(触发recv 系统调用,获取数据,)
1 | 1. 网卡(硬件层) |
与跨机网络IO相比,本机网络IO的关键差异集中在两处:
- 路由选择:优先查询
local路由表,直接匹配回环设备; - 驱动与硬件交互:无需物理网卡驱动,回环设备”驱动”仅做软件层面的数据包转发。
本机网络IO核心流程
下面我们依次来看一下,下面为本机物理网啦的配置信息
1 | [root@developer ~]# ip a show enp3s0 |
第一步:网络层路由选择:优先匹配local路由表,绑定回环设备
当用户进程发起本机网络请求(如curl 127.0.0.1:8080或curl 10.4.196.164:8080),也就是发包的时候,数据包进入内核协议栈后,首先在网络层完成路由选择,这是决定”是否走回环设备”的关键步骤。
网络层的核心入口函数是ip_queue_xmit,其核心逻辑是”先查缓存路由,无缓存则重新查找
路由查找最终会进入fib_lookup函数,该函数会优先查询local路由表(本地路由表,处理本机内部流量),查询命中则终止,不进入main表(认路由表,处理跨网段 / 外网流量)
通过ip route list table local命令,可查看local路由表的内容(以本机IP为10.4.196.164为例)
1 | [root@developer ~]# ip route list table local |
可以看到上面的路由表中的路由条目都是通过内核自动添加的,内核在初始化IP时,会通过fib_inetaddr_event函数将所有本机IP添加到local路由表,并标记路由类型为RTN_LOCAL(这里很关键)。
其中127.0.0.0/8和127.0.0.1是本机IP,172.17.0.1是Docker容器网段,10.4.196.164是本机物理网卡IP。并且 本机IP 在local路由表中都绑定了lo回环设备,本机物理网卡IP 绑定了 物理网卡, docker 容器相关的IP绑定了docker0设备。这里可能会有小伙伴说如果访问的是物理网卡的IP,是走物理网卡,因为绑定了物理网卡,后面会说明
第一条路由意思访问本机的物理网卡 IP 10.4.196.164 时,虽然绑定了物理网卡,但是走本机内部路由(scope host),不经过物理网卡,直接走回环设备(lo),并且源地址也用本机物理网卡IP 10.4.196.164 发送。
第三/四条为回环设备的路由,访问 127.0.0.0/8 网段(包含 127.0.0.1 ~ 127.255.255.254)的所有流量,都走回环设备 lo,完全在本机内部流转,源地址也用 127.0.0.1 发送。
剩下的一条是 Docker 容器网段的路由,还有一些广播地址的路由。回环网段的广播地址,发送到该地址的广播包,仅在本机内部广播(不会出本机)。10.4.192.0/20 网段的广播地址,发送到该地址的广播包(如 ARP 请求),通过 enp3s0 网卡在当前网段内广播。
这里我们顺便把 main 表的路由配置也看一下
1 | [root@developer ~]# ip route list table main |
main 表是 Linux 系统的默认路由表,负责处理 “非本机内部” 的流量(如访问其他网段、外网),上面输出包含 4 条路由:
第一条是默认路由,所有 “找不到匹配网段” 的流量(如访问百度、其他公司的服务器),都会走这条路由:
default via 10.4.192.1:目标 IP 不在任何已知网段时,将流量转发给网关 10.4.192.1(网关是连接当前网段和其他网段的 “桥梁”);dev enp3s0:通过物理网卡 enp3s0 发往网关;proto dhcp:这条默认路由是通过 DHCP 自动获取的(不是手动配置的);metric 100:路由优先级为 100(若有其他默认路由,metric 更小的会被优先使用)
第二条是当前网段的路由,访问 10.4.192.0/20 网段内的设备时,直接通过物理网卡 enp3s0 通信,无需经过网关,
10.4.192.0/20:目标网段(/20 表示子网掩码是 255.255.240.0,该网段包含的 IP 范围是 10.4.192.1 ~ 10.4.207.254);scope link:作用范围是 “当前链路(网段)”,即该网段内的设备在同一物理网络,直接通过二层(MAC 地址)通信;src 10.4.196.164:访问该网段时,源 IP 用 10.4.196.164(这是 enp3s0 网卡的 IP);proto kernel:这条路由是内核自动添加的(当 enp3s0 获取到 IP 后,内核会自动生成当前网段的路由)。
第三条访问特定 IP 169.254.169.254 的流量,需通过网关 10.4.207.254 转发。并且也是DHCP自动生成,一般有一些用户态隧道的服务器会用到
第四条是 Docker 容器网段的路由,但当前 docker0 网卡(Docker 默认的网桥)处于链路断开状态,无法访问该网段。
- 172.17.0.0/16:Docker 默认的容器网段(所有 Docker 容器的 IP 通常在这个网段内,如 172.17.0.2、172.17.0.3);
- dev docker0:访问容器时,通过 Docker 网桥 docker0 通信(docker0 是 Linux 虚拟网桥,连接主机和容器);
- src 172.17.0.1:主机访问容器时,源 IP 用 172.17.0.1(这是 docker0 网桥的 IP);
- linkdown:关键状态 —— 当前 docker0 网桥未启用(可能是 Docker 服务未启动,或网桥被手动禁用),因此无法访问容器网段。
1 | [root@developer ~]# ip addr show docker0 |
关键误区澄清:
如果访问 127 开头的网段,可以看到直接绑定的回环设备,直接回环设备本机网络,但是对于物理网卡的IP,在网络层路由处理中会强制使用回环设备
当fib_lookup查询到RTN_LOCAL类型的路由后,会进入net/ipv4/route.c的ip_route_output_key函数,明确将输出设备设置为回环设备:
1 | struct rtable *ip_route_output_key(struct net *net, struct flowi4 *fl4) { |
所以访问本机IP(如10.4.196.164)并不会经过物理网卡(如enp3s0),而是和127.0.0.1一样,通过lo设备通信——可通过tcpdump -i enp3s0 port 8888验证:即使发起telnet 10.4.196.164 8888,enp3s0上也抓不到任何数据包;而tcpdump -i lo port 8888能清晰看到TCP握手包。所以即使网卡down,本机网络IO仍能正常工作。
同样我们也可以解释127.0.0.1和本机IP速度的一致性,两者的路由流程、设备选择、驱动处理完全一致,唯一差异是127.0.0.1是回环设备的默认IP,而本机IP是物理网卡IP,但最终都走lo设备——性能无任何区别。
第二步:网络设备子系统:回环设备无队列,直接进入驱动
路由选择完成后,数据包进入网络设备子系统,入口函数是net/core/dev.c的dev_queue_xmit。与物理网卡不同,回环设备的处理逻辑极简化:无发送队列,直接调用dev_hard_start_xmit进入驱动。
对于物理网卡(如Intel igb),dev_queue_xmit会经历”选择队列→skb入队→出队发送”的复杂流程,甚至可能触发软中断;但回环设备的q->enqueue为false(无队列),直接跳过队列逻辑
1 | int dev_queue_xmit(struct sk_buff *skb) { |
调用回环设备”驱动”,loopback_xmit函数,这里的”驱动”之所以加引号,是因为它没有任何物理硬件交互——纯软件逻辑,核心作用是”将发送的数据包直接转发到接收队列”。
第三步:回环驱动处理——跳过硬件,直接触发接收软中断
loopback_xmit是本机网络IO的”转折点”:它不做任何硬件操作,而是将发送的skb(数据包缓存)重新注入到内核的接收队列,并触发软中断,相当于”自己发、自己收”。
loopback_xmit把数据包注入接收队列,触发接收软中断,这是本机网络IO与跨机IO的另一个关键差异,跨机IO需要”物理网卡硬中断→软中断”的触发链,本机IO直接跳过硬中断,由loopback_xmit主动触发软中断。
第四步:接收软中断处理—与跨机IO流程统
软中断触发后,内核会执行NET_RX_SOFTIRQ对应的处理函数net_rx_action,后续流程与跨机网络IO的接收逻辑完全一致:
net_rx_action从input_pkt_queue取出skb;- 调用
__netif_receive_skb将skb送入网络协议栈; - 网络层(
ip_rcv)、传输层(tcp_v4_rcv/udp_rcv)依次处理,最终将数据放入目标socket的接收缓冲区; - 唤醒等待数据的用户进程(如
recv/read阻塞的进程)。
本机网络IO完整流程总结
1 | 1. 用户进程(send/recv) |
本机和跨机网络IO对比
本机网络 IO 并非 “零开销”。即使通过 lo 回环设备,数据仍需经历完整的协议栈处理,相比的跨主机IO对于发包来说,省略了:
- 网络层的IP路由寻址,链路数据包传输
- 不需要经过发送队列调度,物理网卡和DMA的数据拷贝
- 也不需要发包软中断触发,以及数据包处理的完缓冲区清理的硬中断触发
但是内核态整体的系统调用开销基本都在,还是需要用户态和内核态的切换,以及协议栈的处理。需要三次数据拷贝(用户态到内核态,内核态ACK浅拷贝,以及大包切片)
对于收包来说:
- 不需要硬中断处理,直接触发软中断
- 不需要物理网卡的DMA交互、RingBuffer队列
但协议栈开销并未减少:TCP/UDP协议处理、内核态Socket缓冲区拷贝、Netfilter过滤等步骤一个不少。
本机网络IO相比于跨主机网络IO有很大的性能优势,但是不能滥用,上面这些环节会带来显著开销,每次 send/recv 都会触发用户态与内核态切换,系统调用开销同时伴随着数据拷贝,协议栈处理中TCP 的序列号校验、滑动窗口管理,IP的大数据包切片等逻辑都是内核态CPU开销。同时高频本地通信会导致 NET_RX_SOFTIRQ 持续占用 CPU。
对于本机网络IO的性能优化,常用的解决方案绕过 Linux 内核协议栈:
DPDK(用户态旁路):通过 UIO/VFIO 驱动将网卡映射到用户空间,应用程序通过轮询模式驱动(PMD)直接操作网卡,数据包直达用户态,处理过程完全无中断、无系统调用。DP(eBPF 内核态旁路):数据包从网卡进入后,在分配 SKB(内核数据包结构)之前,立即被 XDP 程序处理。程序可以决定是丢弃、转发、重定向还是将数据包送上内核协议栈。类比抓包工具,在协议栈之前某些系统调用埋点,捕获流量。这也是为什么抓包工具可以捕获被防火墙拦截的流量。因为 Netfilter/iptables过滤 在协议栈解析的时候。 K8s 中 Istio Pod 中的边车(Envoy 代理)也是类似的思路,在协议栈之前利用eBPF拦截流量,进行安全策略检查、负载均衡等。
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
https://blog.csdn.net/wll1228/article/details/121311707
《深入理解Linux网络: 修炼底层内功,掌握高性能原理 (张彦飞)》
© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
Linux 网络调优之重新认识 Linux 本机网络
https://liruilongs.github.io/2024/09/09/待发布/Linux 网络调优之重新认识 Linux 本机网络/
1.Linux 网络调优之TCP握手认知与观测
2.Linux网络优化之从Linux内核epoll/io_uring 到Python(ASGI/WSGI)及Java Tomcat(BIO/NIO)网络IO模型认知
3.Linux网络调优之内核网络栈发包收包认知
4.Linux 性能调优之 OOM Killer 的认知与观测
5.为什么进程的物理内存占用(RSS)不停增长? 利用 BPF 跟踪、统计 Linux 缺页异常
6.如何使用 BPF 监控 Linux 用户态小内存分配:Linux 内存调优之 BPF 分析用户态小内存分配
7.Linux 内存调优之 BPF 分析用户态 mmap 大内存分配
8.如何使用 BPF 分析 Linux 内存泄漏,Linux 性能调优之 BPF 分析内核态、用户态内存泄漏

