看Linux驱动相关的代码, 却没有一个好的调试环境可以跟踪内核相关的调用流程。于是,想着用QEMU虚拟机来搭建一个Linux系统。花了大半天时间,终于能够启动一个简单的Linux系统了。中间踩了不少坑,找了不少资料,这里简单总结下整个过程。
以下操作都是基于Ubuntu 18.04 x86_64平台
最开始参考了如何使用QEMU跑内核,使用系统自带的QEMU工具,结果提示如下错误:
1 |
|
怀疑是QEMU的版本太低导致,于是只好又重新编译QEMU源码,最后总算大功告成。总的说来,大致要做的事情有这么几个:
- 编译Linux内核,利用busybox来生成一个小的rootfs(关于什么是rootfs,可以参考The rootfs FS)
- 编译QEMU,确保正常配置ARM64架构的虚拟环境
- 一切就绪,通过
qemu-system-aarch64
跑起来虚拟机来
在前面的一篇文章Linux网络优化之链路层优化中,我们已经看到,随着网卡速率超过1Gbps
,增加到10Gbps/100Gbps
时,CPU已经很难处理如此大量的数据包了。总结来说,主要有如下瓶颈:
- 内核协议栈处理在
L3(IP)/L4(TCP)
的数据处理上,消耗了比较多的时间,会导致网络延迟与传输受限 - 高速网卡会在短时间内产生大量中断,导致CPU频繁发生上下文切换,性能收到影响,进而影响网络吞吐
针对10Gbps/100Gbps
等高速网卡中存在的延迟与带宽受限问题,Intel在2010年提出了DPDK(Data Plane Development Kit)
基于用户空间的解决方案,并开源了实现方案, 目前DPDK
支持包括Intel/ARM等多个芯片架构的指令集; 同样是Intel的工程师在2018年提出了XDP(eXpress Data Path)
,与DPDK
不一样的是,XDP
基于现有内核socket
接口,与eBPF
相结合实现网卡与用户空间的数据传输,从而避免了内核协议栈的处理延迟。
TCP(Transmision Control Protocol
)即传输控制协议, 位于TCP/IP协议栈的第三层(L3), 是一种提供了可靠连接的字节流协议; TCP是目前使用最为广泛的协议之一, HTTP/MQTT/FTP等诸多应用层协议都是基于TCP实现的, 更多关于TCP协议相关的具体内容可以参考标准文档RFC793以及早前写的一篇聊一聊TCP协议.
在上一篇文章中讲到了高速以太网如1Gbps/10Gpbs中Linux网络L2(链路层)的一些优化方法, 包括了offload(卸荷)
以及scaling(缩放)
两种技术. 随着高速网络的不断普及, 1Gbps/10Gpbs以太网已经被广泛使用, 40Gbps/100Gbps也已经制定标准, TCP也在随着网络带宽的提升而不断进化.这篇文章我们就来看下如何在高速以太网下对TCP相关的参数的进行调优.
现在车内网络都开始内卷到1Gbps
了, 有同学给我反馈说以太网的吞吐量上不来, 跟理论带宽差距很大, 之前虽然优化了一波TCP
相关的参数, 但估计不能解决全部问题. 遂决定重新学习下网络优化, 从底层链路对开发平台上的网络进行改善. 趁着这个机会, 索性写一个系列文章-Linux网络优化, 用来总结下Linux网络优化的一些方法与技术, 目前计划从如下几篇文章展开(希望不要放飞了):
- Linux网络优化之数据链路层优化: 数据链路层
L2
的优化, 如何从收发两个方面优化网卡吞吐量,即本篇。 - Linux网络优化之TCP优化: 以TCP协议为例, 说明
L3
协议栈优化 - Linux网络优化之高速网络优化: 基于
DPDK/XDP
解决高速网络传输延迟问题 - Linux网络优化之AVB: 介绍以太网中用于音视频传输的低时延
TSN/AVB
协议
这篇文章主要讲第一个话题: Linux是如何在数据链路层L2
对网络数据的接收与发送进行优化的.
最近碰到棘手的问题: 以太网进行iperf测试时, 发生了SMMU (System Memory Management Unit)访问异常导致内核崩溃. 原本只是内部测试发现, 后面在试验车上也概率性的出现. 问题发生的概率还不小. 很严重. 只能先从头把一些基本概念与流程梳理清楚. 好在最后还是找到了原因并解决了. 松了口气, 才有时间把整个问题的来龙去脉细细的总结下, 算是一个SMMU相关问题的案例.
首先来看看问题的发生的背景.
问题背景
问题发生在利用iperf
做网络性能测试的时候, 测试系统(采用高通8155平台, 内置一个EMAC芯片, 最高支持1Gbps速率)作为客户端:
1 |
|
这里加-R
参数表示客户端作为数据接收方(奇怪的是, 测试不加-R
参数就不会有问题, 这也说明只有在接收数据的过程才会出现问题), 而服务端是发送方:
1 |
|
这么测试几十个小时就很快出现了, 抓取到的问题堆栈如下. 前面的日志是SMMU相关的寄存器状态打印, 后面是内核调用堆栈.
最近抽空学习下Linux网络协议栈, 读源码时总会前一天梳理完, 等两天再来看却发现又忘记的差不多, 只能再过一遍, 没有对协议栈构建一个系统性的框架. 于是想着要把看过的代码逻辑整理下来, 算是对这段时间学习的总结, 也方便后面的查阅.
在之前的一篇有关网络协议的文章从NAPI说一说Linux内核数据的接收流程简单的讲到了Linux的数据接收流程, 但主要是集中在数据链路层与设备驱动之间的交互, 并没有涉及到IP网络层以及TCP传输层的逻辑.对于Linux系统来说, TCP数据的传输大致要经历如下几个步骤:
- 用户进程创建
socket
并创建相应数据的buffer, 通过send
函数发送给服务端 - 内核收到数据后, 将用户空间的buffer数据拷贝到内核协议栈
- 内核协议栈需要经过TCP层(L4)/IP层(L3)/数据链路层(L2)最后发送到网卡
- CPU与网卡的数据传输一般通过
DMA
进行, 完成后网卡发送中断告知CPU, 此时内核会释放之前分配的buffer
这篇文章, 我们着重来看下数据传输中内核部分的流程, 并梳理下Linux协议栈大致的结构与初始化步骤, 主要分为如下两个部分:
- Linux内核协议栈的初始化流程
- 数据是如何从TCP传输层发送到设备驱动的
NAPI(New API)是Linux内核针对网络数据传输做出的一个优化措施,其目的是在高负载的大数据传输时,网络驱动收到硬件中断后,通过poll(轮询)方式将传输过来的数据包统一处理, 在poll时通过禁止网络设备中断以减少硬件中断数量(Interrupt Mitigation
),从而实现更高的数据传输速率。
基于NAPI接口, 一般的网络传输(接收)有如下几个步骤:
- 网络设备驱动加载与初始化(配置IP等)
- 数据包从网络侧发送到网卡(
Network Interface Controller
, NIC) - 通过DMA(Direct Memory Access),将数据从网卡拷贝到内存的环形缓冲区(ring buffer)
- 数据从网卡拷贝到内存后, NIC产生硬件中断告知内核有新的数据包达到
- 内核收到中断后, 调用相应中断处理函数, 此时就会调用NAPI接口
__napi_schedule
开启poll线程(实际是触发一个软中断NET_RX_SOFTIRQ
)(常规数据传输, 一般在处理NIC的中断时调用netif_rx_action
处理网卡队列的数据) ksoftirqd
(每个CPU上都会启动一个软中断处理线程)收到软中断后被唤醒, 然后执行函数net_rx_action
, 这个函数负责调用NAPI的poll
接口来获取内存环形缓冲区的数据包- 解除网卡
ring buffer
中的DMA内存映射(unmapped), 数据由CPU负责处理,netif_receive_skb
传递回内核协议栈 - 如果内核支持数据包定向分发(
packet steering
)或者NIC本身支持多个接收队列的话, 从网卡过来的数据会在不同的CPU之间进行均衡, 这样可以获得更高的网络速率 - 网络协议栈处理数据包,并将其发送到对应应用的
socket
接收缓冲区
平时在Android/Ubuntu这样的Linux系统中, 经常跟网络打交道, 不可避免的要使用网络工具来定位问题;这里对Linux常用的网络工具进行简单的总结, 方便后续查阅使用. 主要看看以下几个常用的网络工具:
- ping: 基于ICMP协议, 发送ICMP数据包用于测试网络连通状态
- traceroute: 基于UDP/ICMP/TCP协议用于跟踪网络连通状态, 打印达到目标地址的路由信息
- tcpdump : 用于抓取tcp/ip包, 分析网络问题的必需神器
- iproute2 : 查看/添加/删除当前路由信息
- netstat: 查看网络状态
- netcfg: 配置网口(使能网口以及配置IP等)
- iptables: 网络数据包的过滤以及防火墙策略配置
- netcat: 用于快速建立TCP/UDP链接,检测网络的连通性
- iperf: 网络性能测试工具, 用来衡量网络吞吐量/带宽
- tc: Traffic Control, 用于显示/修改网卡配置的工具
- curl: 基于libcurl的数据传输工具, 支持HTTP/HTTPS/FTP/RTMP/RTSP/SCP等常见协议
- iw: 用于显示/设置WiFi设备接口的工具, 比如展示当前设备WiFi热点信息, 主动扫描周围WiFi热点等
- ethtool: 查看/设置以太网网卡驱动/硬件配置