BBR算法详解

BBR算法详解 0 前言

学生时代为了完成一个Final Project,阅读了很多BBR(Bottlenect Bandwidth and Round-trip)算法相关的资料,于是决定写下这篇文章,来为自己这段时间的学习做一个梳理,大部分内容是整合(feng he)其他人的成果,再添加一点点自己的分析和理解,希望可以帮到一些和我一样从头开始学习BBR的同学。

本文内容包括BBR算法介绍,原理解析,具体实现,存在问题,BBRv2改进方案,此外,也顺便提供了一些我对于BBR做的一些性能实验,以及BBR源码解析。

文中涉及专业名词的部分,我会同时标注英文,以免翻译不当,如有疑问,请查阅原文。

面向的读者

大致掌握本科生级别计算机网络知识,最好对于TCP 拥塞控制(Congestion Control)有一些基础了解,对BBR算法感兴趣的同学。为了照顾到不同学习阶段的同学,本文在书写过程中使用了过量的信息,即某些推理、逻辑、结论可能会在文中不同阶段反复出现,是希望能够达到相互映照以帮助理解的效果。如果觉得过于冗余,还请见谅。

阅读资料的顺序

如果你有充分的时间来学习BBR相关的知识,比起直接阅读,我更推荐你先按顺序阅读一些相关的资料,能够帮助你更好的理解本文的内容。如果时间不够,可以跳过这部分,直接阅读正文内容。当然如果其中我有讲的不太清楚的地方,你也可以参照这些资料进行比对理解。

  • 首先是中科大-郑烇老师 在B站发布的BBR介绍视频,一共三期,有关BBR的前因后果都讲的很清楚,让我受益匪浅:

    BBR拥塞控制算法之1:通信模型哔哩哔哩bilibili

  • 其次是BBR的原版论文,算是我读过最好的论文之一,深入浅出,易于理解:

    BBR: Congestion-Based Congestion Control

  • 原版是英文的,如果英文不够好,我找到一个不错的译文,可以对照着阅读:

    [译] [论文] BBR:基于拥塞(而非丢包)的拥塞控制(ACM, 2017)

  • 最后是Google自己的讲解视频,除了原始版本,BBR也有很多后续的更新,BBR团队都会在IETF会议上介绍,这里先按下不表,只附上BBR初次亮相时的介绍视频。

    IETF97-ICCRG-20161115-1550

1 在BBR诞生之前

1.1 在BBR诞生前的拥塞控制算法

TCP(Transimission Control Protocol) 协议诞生于1974年,绝对算是最古老的网络协议之一,同时也是当今互联网上使用最多的网络协议。TCP带给了我们可靠的数据包传输控制(数据丢失后进行重传),但如果每一个人都为了追求更高的传输速度而疯狂发送数据,我们的互联网迟早要被数据填满,也就是拥塞(Congestion) . 因此TCP协议同样提供了拥塞控制(Congestion Control)的策略,通过改变拥塞滑动窗口(CongestionWindow,简称 cwnd) 的大小来控制正在传输中的流量,以避免拥塞的发生,来保证网络使用的公平性。

当今互联网上主要使用的拥塞控制算法(Congestion Control Algorithms) 都是基于丢包的拥塞控制(loss-based congestion control) ,他的基本运行方式可以分为以下几个阶段:

  • 刚开始建立连接时进入 slow start 阶段,指数级别地成倍增加发送数据的速率,试探网络的带宽上限
  • 增长到达一定限度 ssthresh 之后,进入线性增长 Congestion Avoidance的阶段,慢慢增加发送的速率
  • 直到检测到 丢包(loss), 认为网络发生了拥塞,大幅度减少滑动窗口的大小
  • 一段时间后,重新回到 slow start 的状态
  • BBR算法详解-1

                                   基于丢包的拥塞控制算法
    

    这套算法是20世纪80年代设计的,后来随着网络环境的变化,也逐步推出了TCP Tahoe, Vegas, Reno, Cubic(现在Linux系统的默认算法) 等改进版本的算法,在原有基础上增加了一些更细致的功能,但整体思路不变,依旧是基于丢包来进行拥塞控制。

    • 通过以下命令看看你当前Linux机器的可用的算法

      sysctl net.ipv4.tcp_available_congestion_control

      查看正在使用的算法

      sysctl net.ipv4.tcp_congestion_control

    1.2 基于丢包(loss-based)的拥塞控制算法不足之处

    但40多年过去了,当今的网络环境早已千差万别,原本保证网络环境通畅无阻的设计,却成为了现如今许多拥塞和网络延迟的主要原因。问题主要源于以下两个方面:

    • 丢包≠拥塞(loss ≠ congestion)

      过去的网络环境往往带宽较小,而且以有线线路居多,所以一旦发生丢包的行为,99%是由于拥塞导致的,所以CUBIC这样的算法才有效地支撑了互联网几十年来的发展。

      但是当今的网络环境,链路带宽高达数十Gbps,是当年的六个数量级以上,在相同出错率的情况,丢包的发生情况就会变多。

      另一方面像Wifi这样的无线链路逐渐普及,而Wifi由于周围物理环境的不确定等原因,天然的出错率和丢包率就要高很多(所以不要在wifi环境下打游戏)

    Untitled 1.png

                                    无线网络的丢包问题
    
    • 网络交换节点的buffer变大

      随着高速buffer的价格越来越低,网络交换节点开始使用大容量的buffer来应对更大的网络需求。但是正如之前所说,基于丢包的算法在初始阶段会指数级增加发送的数据,就是想要把buffer填满,填满之后,再发送数据给交换机就会产生丢包,从而控制当前的连接速率下降。

    BBR算法详解-3

    Bufferbloat问题,大量数据被卡在buffer里,导致延迟大量增加
    

    但是如果Buffer很大,这样的策略就会导致BufferBloat问题,大量的数据卡在交换节点的buffer中,排着队等待被发送出去,导致排队延迟(Propagation Delay) 增大,从而使得整体的网络延迟增加。关于这一点,可以结合后文2.1链路瓶颈来理解。

    此外,CUBIC等算法的吞吐量(Throughput) 会随着丢包的发生反复震荡,不够稳定,同时也意味着对链路的利用率不够高,下面这张图能清晰的展现这一点

    解决这些问题需要一种全新的方式来控制拥塞,这也是今天的主角,BBR ( Bottleneck Bandwidth and Round-trip propagation time,BBR) 试图做到的。事实上,它也确实做到了,将Google自家的广域网B4(连接Google各个数据中心data center的网络)中的TCP流量从CUBIC切换到BBR,吞吐量(throughput)增加了2-25倍,在做了一些配置调优后,甚至进一步提升到了133倍。(来自论文结果,后面会进一步提到)

    2 BBR算法的原理介绍

    要解决拥塞是如何发生的,首先要对以下两点有深入理解:

  • 拥塞会发生哪里(where)
  • 拥塞是如何发生的(how)
  • 2.1 主机间的通讯模型,及概念梳理

    拥塞与瓶颈

    首先我们来解决第一个问题,对于一个全双工的TCP连接来说,在任意时刻,它在每个方向都有且只有一段最慢的链路( exactly one slowest link)或称瓶颈(bottleneck) 。

    BBR算法详解-4

       瓶颈链路(Bottleneck Link),管道最细处决定了最大速率,同时也是数据堆积的地方 
    

    瓶颈的概念很重要,因为:

    • 瓶颈决定了连接的最大数据传输速率,就好像一根水管最细处决定了它整体的水流量。

    • 瓶颈也是持久队列(persist queues)形成的地方。

      对于瓶颈链路,只有当它的离开速率(departure rate)大于到达速率(arrival rate)时,这个队列才会收缩,否则就会不断堆积,而一条链路上其他的队列也会朝着瓶颈移动(因为它的传输速率最小)。

    不管一条连接会经过多少条链路,以及每条链路的速度是多少,从 TCP 的角度来看, 任何一条复杂路径的行为,与 RTT(round-trip time)及瓶颈速率相同的单条链路的行为是一样的。 换句话说,以下两个物理特性决定了传输的性能:

    • RTprop (round-trip propagation time):往返传输时间(假如没有排队的情况下, 简称为 RTT
    • BtlBw (bottleneck bandwidth):瓶颈带宽

    BDP=RTT×BtlBWtext{BDP} = RTT times text{BtlBW}BDP=RTT×BtlBW

    带宽延迟积(Bandwidth Delay Product) BDP , 顾名思义就是将这两个参数相乘得到的概念,如果将网络路径视为一条管道,那么RTT就是管道的长度,Bandwidth是管道的直径,而BDP表示的则是管道的容量大小。所以对应到网络中, BDP 表示的意思就是某一时刻的网络中仍在传输中的最大数据量(buffer中留存的数据除外)。

    BBR算法详解-5 BandWidth, Delay 与 BDP

    在管道被灌满了之后, 继续发送数据就会导致数据被堆积在交换节点的buffer中,形成堆积的队列。同样以水管来类比的话,Bottleneck 就是下面那根比较细的水管,如果你的发送速率大于它(也就是上面那根比较粗的水管),数据就会累积在水管前的蓄水池中(也就是交换节点的buffer),自然而然也就产生了我们前面提到的BufferBloat问题。 BBR算法详解-6

    2.2 Throughput, RTT 与 Inflight Data的关系

    下面我们来解决第二个问题,拥塞是如何发生的?

    inflight数据,是指那些已经发送出去,但是还没有得到确认的数据, 也就是前面提及的,仍在管道中的数据。某一时刻的inflight数据量(也就是正在传输中的数据)决定了当前的网络的吞吐量(Throughput) 和 RTT。(在RTT不变的假设下,inflight也可以理解为在过去一个RTT时间内发送出去的数据量)

    我们可以通过下图来理解他们之间的关系:

    BBR算法详解-7

    Delivery Rate(Throughput) and Inflight Data
    
    • 在 0 text{BDP + BufSize}Inflight>BDP + BufSize (上图最右侧红色断点处)时,就出现了我们之前提到的Bufferbloat现象,中间的节点无法再接受更多的数据,就会开始随机drop一部分新接收的数据(蓄水池已满,水从蓄水池中溢出来了),也就产生了丢包(packet loss)。 CUBIC等基于丢包的算法就在此时开始降低发送的速率。

    但是上述情况的问题也是显而易见,我们再来看下图,RTT与Inflight data的关系:

    BBR算法详解-8

            Delivery rate, RTT and Inflight data
    
    • 一开始,在 0 text{BDP + BufSize}Inflight>BDP + BufSize 时,也就出现了丢包的情况。

    直观上来说, 拥塞(congestion)就是 inflight 数据量持续向右侧偏离 BDP 线的行为, 而拥塞控制(congestion control)就是各种在平均程度上控制这种偏离程度的方案或算法。

    对于像CUBIC这样基于丢包的拥塞控制算法而言,他们会选择在上图的最右侧,也就是蓝色圈圈的位置开始控制整体的传输速率, 减少发送的数据量,将其向左移动。但不难看出,此时才开始控制发送速率已经太晚了,既要忍受RTT的增加,同时也会存在丢包的风险,损失一定的Throughput.

    而最佳的策略就是将inflight data控制在上图红色圈圈的位置,这样我们就能够实现当前网络下最大的Bandwidth和最小的RTT。很不幸的是,在1981年,Gail & Kleinrock 就已经证明了理论上不可能设计出一个算法可以找到这个点,于是退而求其次,大家选择了基于丢包的拥塞控制算法,在当年,缓冲区域的Buffer只比BDP略大,所以导致的额外延迟很小,红点和蓝点之间距离很小,但是现在缓冲区已经比BDP大上好几个数量级了,bufferbloat导致的RTT达到了秒级,这就没有办法忽视了。

    • 把以上两张图结合起来就可以理解原论文中的这张图

    BBR算法详解-9

    而BDP算法的思路简单来说就是,努力将inflight数据量控制在最优点右侧一点点,努力达到 Inflight=BDPtext{Inflight} = text{BDP}Inflight=BDP,从而获得更小的RTT和更大的吞吐量,而实现的方式就是通过Bottlenect Bandwidth 和RTT。

    • 如果本段觉得无法理解,可以参考郑老师视频的这一部分(已定位时间

      BBR拥塞控制算法之1:通信模型哔哩哔哩bilibili

    2.3 通过当前的Bottleneck Bandwidth和RTT进行拥塞控制

    当一个连接满足以下两个条件时,它将运行在最高吞吐和最低延迟状态(也就是上一节中提到的红色最优点):

  • 速率平衡(rate balance) :瓶颈链路的数据包到达速率刚好等于瓶颈带宽 BtlBw

    这个条件保证了链路瓶颈已达到 100% 利用率。

  • 管道填满(full pipe) :传输中的总数据(inflight)等于 BDP(= BtlBw × RTprop)。

    这个条件保证了有恰好足够的数据,既不会产生瓶颈饥饿(bottleneck starvation), 又不会产生管道溢出(overfill the pipe)。

  • Untitled 9.png 最高吞吐与最低延迟的状态,其中中间的橙色管道为瓶颈链路被填满,而它前面的buffer为空

    需要注意,

    • 仅凭 rate balance 一个条件并不能确保没有积压。

      例如,某个连接在一个 BDP=5 的链路上,开始时它发送了 10 个包组成的 Initial Window,之后就一直稳定运行在瓶颈速率上。那么,在这个链路此后的行为就是:

      • 稳定运行在瓶颈速率上
      • 稳定有 5 个包的积压(排队)

    屏幕截图_2022-04-30_161939.png

    发送速率与瓶颈带宽相同,但是积压的队列会维持不变

    • 类似地,仅凭 full pipe 一个条件也无法确保没有积压。

      例如,如果某个连接在12frac{1}{2}21​RTT时间内发送一个 BDP 的数据, 那仍然能达到瓶颈利用率(管道刚好被填满),但却会产生平均 BDP/4 的瓶颈积压。

    在链路瓶颈以及整条路径上最小化积压的唯一方式是同时满足以上两个条件:

    • BBR通过控制数据发送的速率(pacing rate) 来保证速率平衡
    • 同时通过控制拥塞滑动窗口(cwnd)的大小来控制inflight数据量等于BDP

    3 BBR算法的具体实现

    通过上一章的介绍,我们应该了解了BBR算法是如何控制拥塞的大致思路,而想要实现上述方案,就要根据当前网络的Bottleneck Bandwidth和RTT进行调整。所以这一章,我们将深入BBR算法的细节:

    • BBR如何测量当前网络的Bottleneck Bandwidth和RTT?
    • 在得知以上两个参数后,如何控制发送的速率和inflight data?
    • BBR的四个不同阶段分别是什么,有什么样的作用?
    • BBR的实际表现如何,在哪些方面有明显的优势?

    3.1 如何测量Bottleneck Bandwidth与RTT(收到ACK时做什么

    为了控制发送的速率,我们首先需要计算出当前网络的Bottleneck Bandwidth和RTT,同时由于网络环境的动态变化,这两个参数并不是固定的,而是会随着时间而变化,所以需要进行实时的采样测量。

    得益于TCP协议的Send-ACK设计,其实每一次发送方发送数据,接收方接收到数据后发送一个ACK信号给发送方,而每一次这样的交互都可以视为一次独立的数据采样,具体的实现方法如下: BBR算法详解-10

    假设在t=0的瞬间,数据包序号 p+k刚好准备发送出去,而与此同时,数据包序号 p 的ACK信号刚好到达,那么我们就可以在数据包 p+k 上添加上这两个记录:

  • send_time 这个数据包被发送出去的时间,在这里是t=0
  • delivered_at_send 这个数据包被发送时,sender接到的最新的数据包数量,在这里就是p
  • 这两个属性会在数据包被receiver返回ACK信号时,一起发送回来。假如100ms后这个数据包的ACK信号回来了: Untitled 11.png

    那么此时我们就可以计算出两个属性:

    • 这个数据包的 RTT , 只需要将当前的到达时间 arrive_time减去发送时间 send_time ,就得到了数据包一次往返的总时长,这里就是 RTT=100ms−0ms=100mstext{RTT} = 100ms -0ms = 100msRTT=100ms−0ms=100ms
    • 过去一个RTT时间内的带宽 bw .在这100ms的时间内,从数据包 p 到数据包 p+k 总共k个数据包陆续到达,所以这段时间的 bw = k/100ms

    这样以来,每次有一个ACK信号到达时,我们就可以根据上述公式,更新相应的Bandwidth和RTT数据,每一个数据包都是一个采样估计,或许偶有偏差,但是大量数据的计算就可以实现对这两个物理属性的无偏估计。

    理论上这个算法没有问题,但是实际上RTT和瓶颈带宽BtlBW会不断变化,究竟如何确定哪一个才是网络的真实属性呢?BBR算法里通过这种方式来去确定:

    • 一段时间内测量到的最小RTT,就是这条路径在这一段时间内的往返传输延迟。在实际BBR算法中,选择10s作为这个窗口时间
    • 估计出的传输速率,不会超过真实的瓶颈带宽,后者时前者的上限 deliveryRate BtlBwFilter.current_max // 如果实际传输速率已经大于当前估计的瓶颈带宽 || !packet.app_limited) // update_max_filter(BtlBwFilter, delivery_rate) // 则更新一段时间内的最大瓶颈带宽 if (app_limited_until > 0) // 达到瓶颈带宽前,仍然可发送的字节数 app_limited_until = app_limited_until - packet.size

      由于论文原代码中的一些变量名稍有歧义,我修改了部分变量名以获得流畅的阅读体验。

      几点说明:

    • 每个包都会更新对 RTT 的估计,但只有部分包会更新对 BtlBw 的估计;

    • 应用受限的包

      • 对于一个包,当应用(application)全速发送它而仍然没有占满瓶颈带宽时, BBR 会这个包标记为 app_limited(见下面的 send() 伪代码), 即 packet.app_limited = true
      • 这将决定哪些样本会用来更新对带宽的估计(如果app_limited,则不算做采样的样本)
    • 瓶颈带宽 BtlBw 是传输速率的一个硬性上限,因此

      • 如果测量到当前的传输速率 > 当前对 BtlBw 估计, 必定意味着这个估计太低了,不管样本是不是 app-limited;对应到上面的代码,就是只要 delivery_rate > BtlBwFilter.current_max,就一定更新 BtlBw 估计;否则,
      • 如果样本不是 app-limited(说明实际带宽已经饱和),也更新 BtlBw 估计。
      • 对于前面理论部分不太了解的同学,可以参照原论文”Characterizing the Bottleneck” 这一部分阅读,也可以参考郑老师视频的这一部分(已附时间定位

        BBR拥塞控制算法之2:控制原理哔哩哔哩bilibili

      3.2 控制发送的数据(如何均匀地发送数据

      科普下 TCP pacing 这个下文将用到的概念:

      Paper: Understanding the Performance of TCP Pacing

      TCP’s congestion control mechanisms can lead to bursty traffic flows on modern high-speednetworks, with a negative impact on overall network efficiency. A pro-posed solution to this problem is to evenly space, or “pace”, data sent intothe network over an entire round-trip time, so that data is not sent in aburst. In this paper, we quantitatively evaluate this approach.

      译注。

      BBR算法详解-11

      一个时间内发送同样数量的数据包,pacing rate不同,产生的效果差别很大,前者就更容易造成拥塞

      为了让数据包的到达速率(packet-arrival rate)能匹配到 瓶颈链路的离开速率(departure rate), BBR 会对每个数据进行 pace (在每个 RTT 窗口内均匀发送数据)。

      总的来说BBR有两个控制参数:

    • pacing_gain:BBR 的主要控制参数,控制发送的速率

      send_rate=pacing_gain×BtlBWtext{send_rate} = text{pacing_gain} times text{BtlBW}send_rate=pacing_gain×BtlBW

      要求达到速率必须匹配到瓶颈速率,意味着 pacing 是 BBR 设计中 必不可少的部分。

    • cwnd_gain:拥塞滑动窗口的大小 cwnd等于 BDP 乘以一个略大于 1 的系数 cwnd_gain, 用来控制当前网络中的inflight数据量。略大于1是为了容忍常见的网络和接收端异常(pathologies),这个我们后面会讲到。

      cwnd=cwnd_gain×BDP=cwnd_gain×BtlBW×RTTtext{cwnd} = text{cwnd_gain} times text{BDP} = text{cwnd_gain} times text{BtlBW} times{RTT}cwnd=cwnd_gain×BDP=cwnd_gain×BtlBW×RTT
    • TCP发包时BBR的逻辑

    ProbeBW 阶段重新设计

    在BBRv2中,最大的改变之一应该就是ProbeBW阶段整体上算是重新设计了一个新的方案,思路上还和之前的设计差不多,但是对于带宽的控制更加精细了,不同状态的转换和持续时间也会动态变化,这样可以提升链路利用率,降低丢包率。

    这一段还没有理解透,后面补充

    ProbeRTT 更频繁,更平缓

    在以前的BBR设计中,ProbeRTT阶段每隔10s运行一次,一次持续200ms,将 cwnd 缩小为4,这样会带来两个问题:

    • 因为 10s一次,导致 min_rtt 收敛速度很慢(20-30s),不能应对变化迅速的网络环境
    • 把inflight data缩小到4个包,过于猛烈,可能会导致更高的长尾延迟

    对此的解决方案是:

    • 使ProbeRTT阶段的降低更平缓,降低为 0.75 * BDP

    • 更频繁地进入ProbeRTT阶段, 每隔2.5s一次

      为什么选择2.5s呢?(0.2 * 0.75 + 2.5 * 1.0) / (2.5 + 0.2) = 98.1% ,总的来说对于吞吐量的损失和之前差别不大。

    与其他算法的公平性

    这一段还没有理解透,后面补充

    BBR后续也有一些更新,比如降低对CPU的占有率,考虑引入delay作为信号的BBR.swift,后期的BBR更像是开始融合各种其他算法的机制来提升自己的性能。由于我本人的时间和能力限制,就暂时不在这里介绍了,大家可以参考IETF106, IETF 109来深入了解一下。

    6 BBR性能测试实验

    正如之前所提到的,这篇文章的起源是来自我的一个Final Project,这个Project的最后目标就是重现BBR论文中的一些结论。为此,我使用 mininet搭建了一个模拟的网络环境,然后使用 iperf建立连接,同时用 ethstats 测量运行时的带宽占有情况,最后我还用了Tensorboard来进行实验结果的可视化。

    项目的源代码和我的一些试验记录已经公开在Github上:

    BBRReproduce/README.md at main · sunyiwei24601/BBRReproduce

    github.com/sunyiwei246…

    项目的主体是用Python来写的,不过要完成其中特定的实验需要切换Linux Kernel的版本,更具体的运行要求我全都写在README里,对于大部分函数也都有相应的注释文档,你也可以自己进行改动和实验。

    大部分实验结果与BBR原论文中类似,比如BBR确实能在高loss rate情况下维持高吞吐量,低延迟:

    BBR算法详解-24

    BBR算法详解-25 不过也发现了一些有趣的现象,比如在低loss rate和很大的buffer情况下,其实BBR完全竞争不过Cubic,因为CUBIC会超量发包来达到拥塞:

    BBR算法详解-26

           在loss_rate=0%的情况下,红色CUBIC与黄色BBR共享同一条链路带宽
    

    此外,虽然BBR一直声称想要达到inflight=BDP,但是实际上由于cwnd设置为2*BDP,所以其实一直无法到达最小RTT,不过还是能稳定在两倍最小RTT左右:

    Untitled 35.png min_rtt 在这里大概是40ms左右,但是日常 ProbeBW阶段会维持在100ms,只有在 ProbeRTT 阶段才会下探到底部。

    7 BBR 源码解读

    在前面的项目里,我还顺便附带了一份BBR的源码逐行解析,基于Linux Kernel 4.15版本的BBR源码,增加很多原本没有的注释,希望可以帮助到大家。

    BBRReproduce/tcp_bbr_note.c at main · sunyiwei24601/BBRReproduce

    阅读源码会有一些困难,比如对C很多机制的不熟悉,Linux内核一些机制也不了解,导致你可能会卡在一些和BBR算法根本没什么关系的问题上。但是好处是,可以进一步加深对算法的了解,就我个人而言,确实是读懂了源码之后才感觉对BBR算是了如指掌了。

    到了BBRv2的源码从原来的1000行增加到了2000行,我暂时还没有时间读完,希望以后有机会给大家补上。

    在这里我也补充一些对于阅读BBR源码的小建议,首先是建议配合BBR draft一同观看:

    BBR Congestion Control

    里面对于各个参数,变量名的详细解释,对算法原理的介绍都有涉及,绝对比只看代码注释要好。这里顺便分享一下我刚开始阅读源码时遇到的几个困惑:

    阅读顺序

    不要从上往下读,因为C的一些原因,函数无法在未被声名前的代码行中调用,所以顺序看起来会有一些混乱。正确的阅读顺序是先看下面的main函数和bbr_update_model:

    static void bbr_update_model(struct sock *sk, const struct rate_sample *rs)
    {
        bbr_update_bw(sk, rs); // 根据采样,更新实时的bw
        bbr_update_ack_aggregation(sk, rs);
        bbr_update_cycle_phase(sk, rs); // 如果PROBE_BW状态下,进入循环下一个阶段
        bbr_check_full_bw_reached(sk, rs); // 判断是否跑满了bw,记录下来
        bbr_check_drain(sk, rs); //判断是否进入drain阶段,drain清空了queue后,进入probe_bw阶段
        bbr_update_min_rtt(sk, rs); // 根据采样,更新最小rtt,如果rtt长时间不变,进入probe_rtt阶段
        bbr_update_gains(sk); // 根据所处的阶段,选择不同的gain系数
    }
    /* 算法主要流程 */
    static void bbr_main(struct sock *sk, const struct rate_sample *rs)
    {
        struct bbr *bbr = inet_csk_ca(sk);
        u32 bw;
        // 更新brr的参数,如bw, min_rtt, 所处阶段,对应的gain系数等
        bbr_update_model(sk, rs);
    
        // 获取当前模型的bw
        bw = bbr_bw(sk);
        // 根据bw和gain设置pacing rate
        bbr_set_pacing_rate(sk, bw, bbr->pacing_gain);
        //设置滑动窗口大小
        bbr_set_cwnd(sk, rs, rs->acked_sacked, bw, bbr->cwnd_gain);
    }
    

    然后根据这两个函数里各个语句的执行顺序,一个个看各个分支函数的运行逻辑和作用,会通顺和舒服很多,同时也能看到与BBR论文中相互映照的地方。

    单位SCALE

    为了提升运行效率,很多常数应该以小数的形式存储,在C里会使用 原数*SCALE的形式,比如1.25就会存储为 5 / 4 * 8, 到了使用的时候再除以这个SCALE,提升效率。

    这个在BBR算法中也出现了很多:

    /* Scale factor for rate in pkt/uSec unit to avoid truncation in bandwidth
     * estimation. The rate unit ~= (1500 bytes / 1 usec / 2^24) ~= 715 bps.
     * This handles bandwidths from 0.06pps (715bps) to 256Mpps (3Tbps) in a u32.
     * Since the minimum window is >=4 packets, the lower bound isn't
     * an issue. The upper bound isn't an issue with existing technologies.
     * 时间单位一般为usec,测量包的发送速率,一般以包的数量/时间为单位,这个理就是pkt/uSec了
     * 想要以pkt/uSec 为单位进行测量,但是这个单位代表的数量太大了,1pkt/uSec = 1500bytes/1usec = 12Gbps
     * 所以这里需要进行缩小,缩小为12Gbps/2^24 = 715bps,这样一个单位unit表示的就是1pkt/uSec/2^24
     * 换句话说,如果rate=2^24, 这说明达到了1packet/usec的发送速率
     * tcp的cwnd是以packet数量为单位的,所以一般都缩小单位,乘以2^24(类似从s->ms, 乘以1000)
     * 详情见:https://blog.csdn.net/quniyade0/article/details/115579087
     */
     */
    #define BW_SCALE 24 
    #define BW_UNIT (1