• socket与Linux TCP


    继续填满长肥管道,接上回:

    填满TCP长肥管道

    修正了TCP流控rwnd算法后,吞吐就与RTT和rcvbuff解耦了,它的值就是横截面:
    在这里插入图片描述
    buffer的目的是平滑到达和处理之间的gap而不再作为“要被填满的批量搜集站”,好处至少两点:

    • 解决了rcvbuffer的bufferbloat,就平滑了主机处理延时抖动。
    • 数据读取速率直接取决于CPU而不是rcvbuffer的配置。

    但关于这个buffer,又有另一个问题。这个问题可能就是损失那2Gbps(理论25Gbps,实际23Gbps)的祸首。

    之前说过,Linux TCP非全双工,socket读写不能(多CPU)并行,ACK和触发发送不能并行,除了这些,软中断Data收和进程socket收也不能并行。

    换句话说软中断和socket无法同时将数据放入receive queue和从中取走数据。

    无论TCP软中断处理还是socket收,都直接lock或own整个socket,造成软中断和socket进程的串行处理。具体参考Linux内核协议栈的backlog实现。

    lock或own整个socket显然粒度太粗,需要lock的只是TCP连接的元数据。

    Linux UDP之前也有这个问题,但UDP本身无状态,也就元数据需要保护,针对Linux UDP的优化一气呵成,可参见2019年的一篇文章:

    Linux内核UDP收包为什么效率低?能做什么优化?

    给出一个UDP双队列优化后的图示:
    在这里插入图片描述
    Linux TCP也需要类似的双队列buffer,两个队列原子转移,深入细致地管理这个双队列buffer,目的是软中断往buffer放数据的同时,socket可在另一个CPU上从buffer取数据。

    这才真正实现TCP协议栈到进程并行流式交接,buffer作为平滑适配器而不是批量搜集站,这是真正的排队系统,socket就像网络中继转发节点,将数据从TCP转交给进程。

    如昨天文中所说,进程不一定是最后一站,可能还要落盘,IO库,文件系统,磁盘buffer也依然要如是转交。
    话回正途,Linux TCP拆解这个socket lock范围很难,我尝试将receiveve queue独立出来,但socket读也要独立才行,最终失败。

    退而求其次,在socket读频率和每次读取的数量之间找一个平衡点。SO_LOWAT正好做这事,但iperf工具不支持该sockopt,可以用LD_PRELOAD来做,也可以用stap:

    #!/usr/local/bin/stap -g
    
    %{
    #include <net/tcp.h>
    %}
    
    function alter_lowat(skk:long)
    %{
    	struct sock *sk = (struct sock *)STAP_ARG_skk;
    
    	if (inet_sk(sk)->inet_num == 5001 && sk->sk_rcvlowat == 1) {
    		sk->sk_rcvlowat = 65500*2;
    		STAP_PRINTF("%d\n", sk->sk_rcvlowat);
    	}
    %}
    
    probe kernel.function("tcp_data_ready")
    {
    	alter_lowat($sk);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    意思是buffer里至少攒够了65500*2字节数据后才wakeup进程。保持相同的速率,CPU利用率下降20%。

    此外就是调节系统调度器了,我们追求的目标是软中断将n字节放入receive queue和socket从receive queue中读n字节这两件事交替进行,但这几乎做不到:

    • GRO/LRO必须开启,ACK过多会抑制发送。
    • GRO/LRO无法确定数据包长度。
    • Linux软中断无法精确控制pacing。
    • wakeup收包进程无法保证它立即运行。

    不过25Gbps尚未touch到CPU瓶颈,若100Gbps打进主机,恐怕上述问题就必须考虑了,由于Linux内核本身是一个复杂的控制面,跑100Gbps注定会吃力,任何wakeup模式因调度延时不确定而不能信任,busy poll模式就更加适合。不过这些都是后话。

    终于还是避不开TCP单机性能的损失,核心问题不是调度系统调优,如果真去那般做,可能路子本身就是错的,内核本是一个控制面,内核协议栈跑数据面尚且还好因为尚未touch到CPU瓶颈,但随着带宽持续演进,早晚会touch到CPU瓶颈。作一短文,备忘。

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

  • 相关阅读:
    测试驱动开发 002:VSCode + CMake + Unity 环境搭建
    ubuntu 怎样查看隐藏文件
    等保测评有那些流程?为什么要做等保
    解决IE浏览器无法删除证书的问题
    猿创征文|Python-sklearn机器学习快速入门:你的第一个机器学习实战项目
    fastTEXT入门自然语言处理NLP
    ActiveMQ漫谈(一)
    校园快餐店网上订餐管理系统(JSP+MySQL+MyEclipse)
    Word粘贴时出现“运行时错误53,文件未找到:MathPage.WLL“的解决方案
    十七、Java 方法
  • 原文地址:https://blog.csdn.net/dog250/article/details/124992654