Linux kernel 协议栈与高速网络

2023-12-15 07:27:56

都知道 linux kernel 无力处理高速网络,但各种 offloading 方案也不全对,所以到底为什么 linux kernel 对高速网络无力。

中断,锁,上下文切换,内存拷贝这些经理都够得着的视野就别烦世人皆俯视了。本文给出另一个视角。

linux kernel 是个 ms 级系统,而高速网络是个 us 级系统,用 ms 级系统调度 us 级系统,到底谁在调度谁,这不是任何 bypass/offloading 技术能解决的。

作为 IO 的网络与众不同,不光因为它是进化的,还因对端是对等的,网络吞吐同时受技术世代和对端驱动。与此相对的其它 IO 更稳定。键盘鼠标速度受制于人的操作速度,显示输出只需满足人眼分辨上限就够,高速存储虽快,但作为 slave 只受不攻,或者作为对等架构的网络存储则可归到网络范畴。

结论是,作为外设,只有网络越来越快。这对主机系统的处理方式提出了新的要求。网络发展快于 cpu 发展经理都知道,自不必多说(深层次原因看这篇:带宽和 cpu 发展),linux kernel 无法应对高速网络更重要的是系统内因。

我以 tcp 为例描述,因为 tcp 在系统缩放问题上最为典型,主要表现为,tcp 同时受到网络和对端制约,从 snd_win = min(cwnd, rwnd) 可见 tcp 性能 = min(网络性能, 对端性能)。sender 可从 tcp ack 自时钟中直接体验它的性能反馈,tcp 又攻又受。

需要澄清的是,tcp 只做一个例子描述问题,现实中 tcp 作为流式可靠传输协议,本身就无法适配高速网络,这不是 linux kernel 的问题,而是 tcp 自身的问题,经理都知道的事实,就不必再赘述。

我选择 hz = 1000 配置 linux kernel,1ms 一个滴答,再小的话 “调度器指令数/调度区间指令数” 将大到不划算 。1ms 作为理论最小的调度粒度,每个 task 最少运行 1ms,因为 granularity 如果小于 1ms,不光更不划算,调度系统还将倾斜(下面讲为什么倾斜以及倾斜到哪边),无法兑现 cfs 承诺。

100mbps 网络跑 tcp,receiver 开启 delayed ack,250us 一个 ack 确认 2 个约 1.5k 的报文,最短运行期间收到 4 次 ack,被中断 4 次,1gbps 网络约 10us 发包一个,100mbps 则 100us 发包一个,250us 间隔对整体调度影响微乎其微,因为有 (250 - 100 = 150)us 的时间执行 task 指令。1000mbps 网络的 40 次中断已经开始影响系统平衡,但也不很,大致算一下,10us 发包一个,25us 一次 ack 中断,有效无干扰指令占调度粒度的比率 (25 - 10 = 15)us / 1000us 更小,简单解释就是,这个比率越小,tcp 对系统调度越不信任。

带宽再往上,系统显然更吃力。

当一个 task 在单 cpu 上发 tcp,系统总趋向平衡,task 占 1/2 cpu,网络收发占 1/2 cpu。如果 task 发包过快,tcp ack 回复将同步加快,cpu 被中断加快,最终吃掉部分 task 的 cpu,反之,如果 tcp ack 过快中断 cpu,它将减缓 task 运行,进而降低 task 发包速度,最终吃掉部分网络收发的 cpu,这是个负反馈环,二者都将平衡。

当多个 task 在单 cpu 上发 tcp,系统会在所有 task 和所有 task 的网络收发之间平均分配 cpu,理由同上述负反馈,但 task 间将失衡。如果调度系统 granularity = 1ms,吞吐越大的 task 越被拖累,因为它得不到及时调度,如果 granularity = 1us(系统承受不起,这不可能,只是假设),吞吐越大的 task 倾向于吃掉更多 cpu,因为它总中断其它 task。

结论是,granularity 越大对大吞吐 task 越不利,而 granularity 越小对小吞吐 task 越不公平。

这就是前面提到的系统在高速网络场景的调度倾斜。granularity 很小时,高吞吐 task 将在系统调度器(比如 cfs)察觉前蚕食 cpu。换句话说,调度失效了。你以为调度失效只影响公平性,实际上你的 task 会在不同时间被映射到不同的吞吐类别,哪个都不是你想要的。

用极值简单理解一下,假设带宽无限,存在 3 个 cfs 调度系统,简单起见均禁用抢占,系统 1 hz = 1000000,系统 2 hz = 1000,系统 3 hz = 250,很容易想象,系统 1 所有 task 将获得完全一样的吞吐,系统 2 高吞吐 task 将剧烈抖动,系统 3 高吞吐 task 更剧烈抖动,低吞吐 task 被压制。我们知道 ns 级别的调度几乎是不可能的,从系统 3 接近系统 1 的过程难度越来越大,现实停留在系统 2 附近。

100mbps,1000mbps 带宽的网络场景,linux kernel tcp 也抖,只不过由于抖幅与带宽之比以单位时间获得的吞吐为增益,从 1us 到 10us 这么小的时间区间可夺 10gbps - 1gbmps = 9gbps 吞吐,而从 100us 到 1ms 这么大的时间区间只能夺 100mbps - 10mbps = 90mbps 吞吐,请问 1us 到 10us 区间和 100us 到 1ms 区间哪个更容易命中,抖动在低速网络下被不成比例缩放平滑到不明显罢了。

抛开 tcp 特例谈一般性,这就是本质,高速网络对时间更敏感,而 linux kernel 调度只能工作在 ms 粒度,对高速网络太粗糙了。

tcp 足够幸运,由于其 32 位序列号,为满足 msl 内避免回绕,它恰恰工作在 1ms 级,与 linux kernel 调度时间粒度完全一致, 这意味着在 1ms 级别的粒度,linux kernel 可相当公平高效地调度 10gbps 内的多个 tcp task,但蜜月即将结束,15 年前就有 hz1000,现在还在 hz250,然而这些年间网络带宽持续以 4 倍速增长,超过 10gbps,每 1ms 超过 40 次到达 100 次的中断,由上所述,调度可信度就快速崩塌了。

应对高速网络,不管是 tcp 还是非 tcp task,一定要直接轮询调度网络收发本身,而不是调度 task,在 task 中经中断调度网络收发,系统主动调度和中断被动调度本虽互补,实际上并不相容,二者对同一事件达成共识需要的同步过程就是造成它们无法协作调度高速网络的根源。

将所有 task 待发送的数据放在发送 queue 中,将所有收到的数据放在接收 queue 中,用一个 task 轮询调度这两个 queue,这是一种原始方式,但显然比多 task + 中断调度多 queue 这种现代并发方式要高效。

so? 这不是 linux kernel 单独的问题,这是任何想应对高速网络都要注意的问题,还是我最近不断强调的缩放比例的问题,时间缩放 10 倍,某些量变化远超 10 倍,就要注意它们了。随着带宽继续提高,系统规模继续扩大,随着经理们青睐的 hw-offloading,(x)pu 方案被塞入更多的创新,早晚会碰到同样的问题,只是 linux kernel 在 1ms 祭天,而他们可能在 10us 祭天,若出卖伸缩性换高性能,便更时不我待。

不过也没人关注比例伸缩性,这和他们经常提到的可扩展性可不是一个东西。

浙江温州皮鞋湿,下雨进水不会胖。

文章来源:https://blog.csdn.net/dog250/article/details/135006782
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。