本文主要介绍内核网络中GRO、RFS、RPS等技术,并针对其对应的规则进行网络调优。重点对RPS的工作过程和内核代码进行了分析,分析了数据如何从网卡进入到协议层。
Large Receive Offloading (LRO) 是一个硬件优化,GRO 是 LRO 的一种软件实现。
两种方案的主要思想都是:通过合并“足够类似”的包来减少传送给网络栈的包数,这有助于减少 CPU 的使用量。例如,考虑大文件传输的场景,包的数量非常多,大部分包都是一段文件数据。相比于每次都将小包送到网络栈,可以将收到的小包合并成一个很大的包再送到网络栈。GRO 使协议层只需处理一个 header,而将包含大量数据的整个大包送到用户程序。
这类优化方式的缺点是信息丢失:包的 option 或者 flag 信息在合并时会丢失。这也是为什么大部分人不使用或不推荐使用LRO 的原因。
LRO 的实现,一般来说,对合并包的规则非常宽松。GRO 是 LRO 的软件实现,但是对于包合并的规则更严苛。如果用 tcpdump 抓包,有时会看到机器收到了看起来不现实的、非常大的包, 这很可能是系统开启了 GRO。
使用 ethtool 的 -k 选项查看 GRO 配置:
-K 修改 GRO 配置:
$ sudo ethtool -K ens33 gro on
对于大部分驱动,修改 GRO 配置会涉及先 down 再 up 这个网卡,因此这个网卡上的连接都会中断。
如果开启了 GRO,napi_gro_receive
将负责处理网络数据,并将数据送到协议栈,大部分相关的逻辑在函数 dev_gro_receive
里实现。
dev_gro_receive
这个函数首先检查 GRO 是否开启了,如果是,就准备做 GRO。GRO 首先遍历一个 offload filter
列表,如果高层协议认为其中一些数据属于 GRO 处理的范围,就会允许其对数据进行操作。
协议层以此方式让网络设备层知道,这个 packet 是不是当前正在处理的一个需要做 GRO 的 network flow
的一部分,而且也可以通过这种方式传递一些协议相关的信息。例如,TCP 协议需要判断是否应该将一个 ACK 包合并到其他包里。net/core/dev.c:
list_for_each_entry_rcu(ptype, head, list) {
if (ptype->type != type || !ptype->callbacks.gro_receive)
continue;
skb_set_network_header(skb, skb_gro_offset(skb));
skb_reset_mac_len(skb);
NAPI_GRO_CB(skb)->same_flow = 0;
NAPI_GRO_CB(skb)->flush = 0;
NAPI_GRO_CB(skb)->free = 0;
pp = ptype->callbacks.gro_receive(&napi->gro_list, skb);
break;
}
如果协议层提示是时候 flush GRO packet
了,那就到下一步处理了。这发生在 napi_gro_complete
,会进一步调用相应协议的 gro_complete
回调方法,然后调用 netif_receive_skb
将包送到协议栈。这个过程见net/core/dev.c:
if (pp) {
struct sk_buff *nskb = *pp;
*pp = nskb->next;
nskb->next = NULL;
napi_gro_complete(nskb);
napi->gro_count--;
}
接下来,如果协议层将这个包合并到一个已经存在的 flow,napi_gro_receive
就没什么事情需要做,因此就返回了。如果 packet 没有被合并,而且 GRO 的数量小于 MAX_GRO_SKBS
( 默认是 8),就会创建一个新的 entry 加到本 CPU 的 NAPI 变量的 gro_list
。net/core/dev.c:
if (NAPI_GRO_CB(skb)->flush || napi->gro_count >= MAX_GRO_SKBS)
goto normal;
napi->gro_count++;
NAPI_GRO_CB(skb)->count = 1;
NAPI_GRO_CB(skb)->age = jiffies;
skb_shinfo(skb)->gso_size = skb_gro_len(skb);
skb->next = napi->gro_list;
napi->gro_list = skb;
ret = GRO_HELD;
这就是 Linux 网络栈中 GRO 的工作原理。
一旦 dev_gro_receive
完成,napi_skb_finish
就会被调用,如果一个 packet 被合并了 ,就释放不用的变量;或者调用 netif_receive_skb
将数据发送到网络协议栈。
RFS(Receive flow steering)和 RPS 配合使用。RPS 试图在 CPU 之间平衡收包,但是没考虑数据的本地性问题,如何最大化 CPU 缓存的命中率。RFS 将属于相同 flow 的包送到相同的 CPU 进行处理,可以提高缓存命中率。
RPS 记录一个全局的 hash table
,包含所有 flow 的信息。这个 hash table 的大小可以在 net.core.rps_sock_flow_entries
配置:
$ sudo sysctl -w net.core.rps_sock_flow_entries=32768
其次,可以设置每个 RX queue 的 flow 数量,对应着 rps_flow_cnt
:
例如,eth0 的 RX queue0 的 flow 数量调整到 2048:
$ sudo bash -c 'echo 2048 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt'
每个 NAPI 变量都会运行在相应 CPU 的软中断的上下文中。而且,触发硬中断的这个 CPU 接下来会负责执行相应的软中断处理函数来收包。换言之,同一个 CPU 既处理硬中断,又处理相应的软中断。
一些网卡(例如 Intel I350)在硬件层支持多队列。这意味着收进来的包会被通过 DMA 放到位于不同内存的队列上,而不同的队列有相应的 NAPI 变量管理软中断 poll()过程。因此, 多个 CPU 同时处理从网卡来的中断,处理收包过程。这个特性被称作 RSS(Receive Side Scaling,接收端扩展)。
RPS (Receive Packet Steering,接收包控制,接收包引导)是 RSS 的一种软件实现。因为是软件实现的,意味着任何网卡都可以使用这个功能,即便是那些只有一个接收队列的网卡。但是,因为它是软件实现的,这意味着 RPS 只能在 packet 通过 DMA 进入内存后,RPS 才能开始工作。
这意味着,RPS 并不会减少 CPU 处理硬件中断和 NAPI poll(软中断最重要的一部分)的时间,但是可以在 packet 到达内存后,将 packet 分到其他 CPU,从其他 CPU 进入协议栈。
如果 RPS 没启用,会调用__netif_receive_skb
,它做一些 bookkeeping 工作,进而调用 __netif_receive_skb_core
,将数据移动到离协议栈更近一步。
如果 RPS 启用了,它会做一些计算,判断使用哪个 CPU 的 backlog queue
,这个过程由 get_rps_cpu
函数完成。net/core/dev.c:
cpu = get_rps_cpu(skb->dev, skb, &rflow);
if (cpu >= 0) {
ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
rcu_read_unlock();
return ret;
}
get_rps_cpu
会考虑 RFS 和 aRFS 设置,以此选出一个合适的 CPU,通过调用 enqueue_to_backlog
将数据放到它的 backlog queue
。
假如网卡支持 aRFS,你可以开启它并做如下配置:
- 打开并配置 RPS
- 打开并配置 RFS
- 内核中编译期间指定了 CONFIG_RFS_ACCEL 选项。Ubuntu kernel 3.13.0 是有的
- 打开网卡的 ntuple 支持。可以用 ethtool 查看当前的 ntuple 设置
- 配置 IRQ(硬中断)中每个 RX 和 CPU 的对应关系
以上配置完成后,aRFS 就会自动将 RX queue 数据移动到指定 CPU 的内存,每个 flow 的包都会到达同一个 CPU,不需要你再通过 ntuple 手动指定每个 flow 的配置了。
首先从远端 CPU 的 struct softnet_data
变量获取 backlog queue
长度。如果 backlog 大于 netdev_max_backlog
,或者超过了 flow limit
,直接 drop,并更新 softnet_data
的 drop 统计。注意这是远端 CPU 的统计。net/core/dev.c:
qlen = skb_queue_len(&sd->input_pkt_queue);
if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {
if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
__skb_queue_tail(&sd->input_pkt_queue, skb);
input_queue_tail_incr_save(sd, qtail);
return NET_RX_SUCCESS;
}
/* Schedule NAPI for backlog device */
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);
}
goto enqueue;
}
sd->dropped++;
kfree_skb(skb);
return NET_RX_DROP;
enqueue_to_backlog
被调用的地方很少。在基于 RPS 处理包的地方,以及 netif_rx
,会调用到它。大部分驱动都不应该使用 netif_rx
,而应该用 netif_receive_skb
。如果没用到 RPS,驱动也没有使用 netif_rx
,那增大 backlog 并不会带来益处,因为它根本没被用到。
注意:检查驱动,如果它调用了 netif_receive_skb
,而且没用 RPS,那增大 netdev_max_backlog
并不会带来任何性能提升,因为没有数据包会被送到 input_pkt_queue
。
如果 input_pkt_queue
足够小,而 flow limit 也还没达到(或者被禁掉了 ),那数据包将会被放到队列。这里的逻辑有点 funny,但大致可以归为为:
____napi_schedule
进一步处理。net/core/dev.c:
if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
__skb_queue_tail(&sd->input_pkt_queue, skb);
input_queue_tail_incr_save(sd, qtail);
rps_unlock(sd);
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
/* Schedule NAPI for backlog device
* We can use non atomic operation since we own the queue lock
*/
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);
}
goto enqueue;
RPS 在不同 CPU 之间分发 packet,但是,如果一个 flow 特别大,会出现单个 CPU 被打爆,而其他 CPU 无事可做(饥饿)的状态。因此引入了 flow limit 特性,放到一个 backlog 队列的属 于同一个 flow 的包的数量不能超过一个阈值。这可以保证即使有一个很大的 flow 在大量收包 ,小 flow 也能得到及时的处理。net/core/dev.c:
/*
* enqueue_to_backlog is called to queue an skb to a per CPU backlog
* queue (may be a remote CPU queue).
*/
static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
unsigned int *qtail)
{
struct softnet_data *sd;
unsigned long flags;
unsigned int qlen;
sd = &per_cpu(softnet_data, cpu);
local_irq_save(flags);
rps_lock(sd);
qlen = skb_queue_len(&sd->input_pkt_queue);
if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {
if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
__skb_queue_tail(&sd->input_pkt_queue, skb);
input_queue_tail_incr_save(sd, qtail);
rps_unlock(sd);
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
/* Schedule NAPI for backlog device
* We can use non atomic operation since we own the queue lock
*/
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);
}
goto enqueue;
}
sd->dropped++;
rps_unlock(sd);
local_irq_restore(flags);
atomic_long_inc(&skb->dev->rx_dropped);
kfree_skb(skb);
return NET_RX_DROP;
}
默认,flow limit 功能是关掉的。要打开 flow limit,需要指定一个 bitmap(类似于 RPS 的 bitmap)。
监控:由于 input_pkt_queue
打满或 flow limit 导致的丢包,在/proc/net/softnet_stat
里面的 dropped 列计数。
调优 Tuning: Adjusting netdev_max_backlog to prevent drops 在调整这个值之前,请先阅读前面的“注意”。
如果使用了 RPS,或者驱动调用了 netif_rx
,那增加 netdev_max_backlog
可以改善在 enqueue_to_backlog
里的丢包:
例如:
increase backlog to 3000 with sysctl.
$ sudo sysctl -w net.core.netdev_max_backlog=3000
默认值是 1000。
Tuning: Adjust the NAPI weight of the backlog poll loop
net.core.dev_weight
决定了 backlog poll loop 可以消耗的整体 budget
$ sudo sysctl -w net.core.dev_weight=600
默认值是 64。
backlog 处理逻辑和设备驱动的 poll 函数类似,都是在软中断(softirq)的上下文中执行,因此受整体 budget 和处理时间的限制。
Tuning: Enabling flow limits and tuning flow limit hash table size
$ sudo sysctl -w net.core.flow_limit_table_len=8192
默认值是 4096.
这只会影响新分配的 flow hash table。所以,如果你想增加 table size 的话,应该在打开 flow limit 功能之前设置这个值。
打开 flow limit 功能的方式是,在/proc/sys/net/core/flow_limit_cpu_bitmap
中指定一 个 bitmask,和通过 bitmask 打开 RPS 的操作类似。
每个 CPU 都有一个 backlog queue,其加入到 NAPI 变量的方式和驱动差不多,都是注册一个 poll 方法,在软中断的上下文中处理包。此外,还提供了一个 weight,这也和驱动类似 。注册发生在网络系统初始化的时候, net/core/dev.c的 net_dev_init 函数:
sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
sd->backlog.gro_list = NULL;
sd->backlog.gro_count = 0;
backlog NAPI 变量和设备驱动 NAPI 变量的不同之处在于,它的 weight 是可以调节的,而设备驱动是 hardcode 64。
process_backlog
process_backlog
是一个循环,它会一直运行直至 weight用完,或者 backlog 里没有数据了。
backlog queue 里的数据取出来,传递给__netif_receive_skb
。这个函数做的事情和 RPS 关闭的情况下做的事情一样。即,__netif_receive_skb
做一些 bookkeeping 工作,然后调用__netif_receive_skb_core
将数据发送给更上面的协议层。
process_backlog
和 NAPI 之间遵循的合约,和驱动和 NAPI 之间的合约相同:
NAPI is disabled if the total weight will not be used. The poller is restarted with the call to ____napi_schedule from enqueue_to_backlog as described above.
函数返回接收完成的数据帧数量(在代码中是变量 work),net_rx_action
将会从 budget(通过 net.core.netdev_budget
可以调整)里减去这个值。
__netif_receive_skb_core
:将数据送到抓包点(tap)或协议层__netif_receive_skb_core
完成将数据送到协议栈这一繁重工作(the heavy lifting of delivering the data)。在此之前,它会先检查是否插入了 packet tap(探测点),这些 tap 是抓包用的。例如,AF_PACKET
地址族就可以插入这些抓包指令, 一般通过 libpcap 库。
如果存在抓包点(tap),数据就会先到抓包点,然后才到协议层。
如果有 packet tap(通常通过 libpcap),packet 会送到那里。net/core/dev.c:
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
packet 如何经过 pcap 可以阅读 net/packet/af_packet.c。
处理完 taps 之后,__netif_receive_skb_core
将数据发送到协议层。它会从数据包中取出协议信息,然后遍历注册在这个协议上的回调函数列表。可以看__netif_receive_skb_core
函数,net/core/dev.c:
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type &&
(ptype->dev == null_or_dev || ptype->dev == skb->dev ||
ptype->dev == orig_dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
上面的 ptype_base
是一个 hash table,定义在net/core/dev.c中:
struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
每种协议在上面的 hash table 的一个 slot 里,添加一个过滤器到列表里。这个列表的头用如下函数获取:
static inline struct list_head *ptype_head(const struct packet_type *pt)
{
if (pt->type == htons(ETH_P_ALL))
return &ptype_all;
else
return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
}
添加的时候用 dev_add_pack
这个函数。这就是协议层如何注册自身,用于处理相应协议的网络数据的。
使用 RPS 需要在内核做配置(Ubuntu + Kernel 3.13.0 支持),而且需要一个掩码( bitmask)指定哪些 CPU 可以处理那些 RX 队列。相关的一些信息可以在内核文档里找到。
bitmask 配置位于:/sys/class/net/DEVICE_NAME/queues/QUEUE/rps_cpus
例如,对于 eth0 的 queue 0,你需要更改/sys/class/net/eth0/queues/rx-0/rps_cpus
。内核文档里说,对一些特定的配置下,RPS 没必要了。
注意:打开 RPS 之后,原来不需要处理软中断(softirq)的 CPU 这时也会参与处理。因此相应 CPU 的 NET_RX
数量,以及 si 或 sitime 占比都会相应增加。可以对比启用 RPS 前后的数据,以此来确定配置是否生效,以及是否符合预期(哪个 CPU 处理哪个网卡的哪个中断)。
本文大篇幅在分析RPS的工作原理。RPS 的工作原理是对个 packet 做 hash,以此决定分到哪个 CPU 处理。然后 packet 放到每个 CPU 独占的接收后备队列(backlog)等待处理。这个 CPU 会触发一个进程间中断( IPI,Inter-processor Interrupt)向对端 CPU。如果当时对端 CPU 没有在处理 backlog 队列收包,这个进程间中断会 触发它开始从 backlog 收包。/proc/net/softnet_stat
其中有一列是记录 softnet_data
变量(也即这个 CPU)收到了多少 IPI(received_rps 列)。因此,netif_receive_skb
或者继续将包送到协议栈,或者交给 RPS,后者会转交给其他 CPU 处理。
参考资料:
https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/
http://arthurchiao.art/blog/tuning-stack-rx-zh/#chap_1
https://blog.csdn.net/cloudvtech/article/details/80182074
https://www.coder.work/article/448092
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/wAm2obMZ9keHcvaQTiRQ5g
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。