论 Linux 内核态全局稳态带宽的卡尔曼估计与工程实现
KCC 在内核中维护了一个全局卡尔曼滤波器,从所有连接 PROBE_BW 巡航阶段(增益=1.0)采集交付带宽,估计整机瓶颈带宽。新连接建立时,从该估计值中切取一个保守比例注入初始 pacing rate 和 CWND 跟踪器,以绕开传统冷启动的盲目探测。本文讨论该全局滤波器的建模、观测门控、定点数迭代、注入策略以及内核插入逻辑。
1. 全局滤波器工作概览
#mermaid-svg-afXlDCnOO7ZZMUrB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-afXlDCnOO7ZZMUrB .error-icon{fill:#552222;}#mermaid-svg-afXlDCnOO7ZZMUrB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-afXlDCnOO7ZZMUrB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-afXlDCnOO7ZZMUrB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-afXlDCnOO7ZZMUrB .marker.cross{stroke:#333333;}#mermaid-svg-afXlDCnOO7ZZMUrB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-afXlDCnOO7ZZMUrB p{margin:0;}#mermaid-svg-afXlDCnOO7ZZMUrB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-afXlDCnOO7ZZMUrB .cluster-label text{fill:#333;}#mermaid-svg-afXlDCnOO7ZZMUrB .cluster-label span{color:#333;}#mermaid-svg-afXlDCnOO7ZZMUrB .cluster-label span p{background-color:transparent;}#mermaid-svg-afXlDCnOO7ZZMUrB .label text,#mermaid-svg-afXlDCnOO7ZZMUrB span{fill:#333;color:#333;}#mermaid-svg-afXlDCnOO7ZZMUrB .node rect,#mermaid-svg-afXlDCnOO7ZZMUrB .node circle,#mermaid-svg-afXlDCnOO7ZZMUrB .node ellipse,#mermaid-svg-afXlDCnOO7ZZMUrB .node polygon,#mermaid-svg-afXlDCnOO7ZZMUrB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-afXlDCnOO7ZZMUrB .rough-node .label text,#mermaid-svg-afXlDCnOO7ZZMUrB .node .label text,#mermaid-svg-afXlDCnOO7ZZMUrB .image-shape .label,#mermaid-svg-afXlDCnOO7ZZMUrB .icon-shape .label{text-anchor:middle;}#mermaid-svg-afXlDCnOO7ZZMUrB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-afXlDCnOO7ZZMUrB .rough-node .label,#mermaid-svg-afXlDCnOO7ZZMUrB .node .label,#mermaid-svg-afXlDCnOO7ZZMUrB .image-shape .label,#mermaid-svg-afXlDCnOO7ZZMUrB .icon-shape .label{text-align:center;}#mermaid-svg-afXlDCnOO7ZZMUrB .node.clickable{cursor:pointer;}#mermaid-svg-afXlDCnOO7ZZMUrB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-afXlDCnOO7ZZMUrB .arrowheadPath{fill:#333333;}#mermaid-svg-afXlDCnOO7ZZMUrB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-afXlDCnOO7ZZMUrB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-afXlDCnOO7ZZMUrB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-afXlDCnOO7ZZMUrB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-afXlDCnOO7ZZMUrB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-afXlDCnOO7ZZMUrB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-afXlDCnOO7ZZMUrB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-afXlDCnOO7ZZMUrB .cluster text{fill:#333;}#mermaid-svg-afXlDCnOO7ZZMUrB .cluster span{color:#333;}#mermaid-svg-afXlDCnOO7ZZMUrB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-afXlDCnOO7ZZMUrB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-afXlDCnOO7ZZMUrB rect.text{fill:none;stroke-width:0;}#mermaid-svg-afXlDCnOO7ZZMUrB .icon-shape,#mermaid-svg-afXlDCnOO7ZZMUrB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-afXlDCnOO7ZZMUrB .icon-shape p,#mermaid-svg-afXlDCnOO7ZZMUrB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-afXlDCnOO7ZZMUrB .icon-shape .label rect,#mermaid-svg-afXlDCnOO7ZZMUrB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-afXlDCnOO7ZZMUrB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-afXlDCnOO7ZZMUrB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-afXlDCnOO7ZZMUrB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
拒绝
通过
KCC 连接 PROBE_BW
增益=1.0 巡航阶段
采集交付带宽
bw = delivered / interval_us
滤波器激活?
首次样本
无门控强制信任
卡方门控
野值过滤
返回原估计值 x
卡尔曼更新
定点数防溢出迭代
更新 x 和 P
atomic64 写回全局状态
新连接创建时
读取 kf_x 计算甜品速度
2. 状态模型
全局瓶颈带宽被建模为一个一维、准静态的随机游走:
x_k = x_{k-1} + w_k, w_k ~ N(0, Q)
z_k = x_k + v_k, v_k ~ N(0, R_k)
-
x--- 全局瓶颈带宽(内部单位 BW_UNIT)。 -
z--- 单条流在增益 1.0 巡航阶段测得的交付带宽。 -
Q--- 过程噪声方差。其作用并非描述带宽的真实物理波动,而是防止滤波器在长期运行后因协方差P衰减至零而丧失对新观测的响应能力(滤波器麻木)。实现上每次迭代固定叠加Q = 1 << q_shift,且强制P ≥ Q。 -
R_k--- 观测噪声方差,采用与带宽量级成比例的相对模型:R_k = (z_k * r_pct / 100)^2
冷启动阶段 r_pct 使用 startup_r_pct_val(默认 20%),稳态后切换为 steady_r_pct_val(默认 5%)。较大的启动 R 压低初始样本的卡尔曼增益,避免单一观测将状态锁死;5% 的稳态值是在 1 Gbps 链路上经测试确定的,能在正常抖动和响应速度之间取得可用的平衡。
3. 卡方门控
公网路径上,交付带宽会出现瞬时归零或暴增的非高斯野值。这些样本若不加过滤直接送入更新方程,会将全局状态拉偏。为此,在执行状态更新之前,使用基于马氏距离的卡方检验进行门控:
y = z - x
S = P + R
若 y² / S > χ²_{1,α} ,则丢弃该样本
在自由度 1 下,阈值 3.84 对应 95% 置信度,6.63 对应 99%。内核代码为避免 y² 的 64 位溢出和浮点运算,将分子分母同步右移后做整数比较。
4. 64 位定点数更新与防溢出
标准卡尔曼更新公式:
x = (x·R + z·P) / (P + R)
P = (P·R) / (P + R)
在 10 Gbps 量级下,P 和 R 的数值可能极大,分子中 x·R 或 P·R 易超出 u64 范围。内核不允许使用浮点,也无法随意引入 128 位除法。实现上采用动态等比右移:
- 取
P、R、P+R中的最大值max_v。 - 只要
max_v >= 2^31,就将P、R同步右移一位,记录移位数shift。 - 使用缩放后的值计算:
由于分子分母等比缩放,状态 x 的估计无精度损失。协方差 P 缩小了 2^shift 倍,左移恢复后仅在低位有截断误差,对滤波器行为不构成实质影响。整个更新仅使用标准 u64 乘除运算。
5. 采样时机的插入逻辑
样本投喂的插入点位于 BBR_main 或 UCP_main(内核 CC 回调入口函数),仅当 round_start == true 且处于 PROBE_BW 巡航增益 1.0 时触发:
cpp
/*
* 插入位置:BBR_main 或 UCP_main 函数内,
* 在每轮结束 (round_start == true) 时执行。
* 严格限制在 PROBE_BW 模式且 pacing_gain == BBR_UNIT (1.0x)
* 此时链路不积队列亦不主动排空,交付带宽最接近物理瓶颈。
*/
if (ucp_kf_enable && ucp->round_start &&
ucp->mode == UCP_PROBE_BW && ucp->pacing_gain == BBR_UNIT) {
// 1. 根据本轮交付数据量与时间间隔计算瞬时交付带宽
u64 bw = (u64)rs->delivered * BW_UNIT / max_t(u32, rs->interval_us, 1U);
// 2. 路由到全局卡尔曼更新函数
if (!atomic_read(&ucp_kf_active)) {
// 滤波器尚未激活:冷启动,使用较大的 R (startup_r_pct),
// 且关闭门控 (check = false),完全信任第一个样本
ucp_kf_update(bw, (u32)ucp_kf_startup_r_pct_val, false);
} else {
// 滤波器已激活:使用稳态 R (steady_r_pct),
// 开启卡方门控 (check = true),过滤非高斯野值
ucp_kf_update(bw, (u32)ucp_kf_steady_r_pct_val, true);
}
}
排除的采样时机:
- STARTUP(增益 ≈2.89):交付带宽中包含因增益放大而产生的人为排队成分。
- DRAIN(增益 ≈0.35):主动压低发送速率以排空队列,测量值偏低。
- PROBE_BW 的 1.25×/0.75× 增益相位:为探测上下界而有意偏离稳态。
只有增益恰好为 1.0 的巡航槽内,pacing rate 等于当前的瓶颈带宽估计,此时的交付带宽是对物理瓶颈最干净的观测。样本稀疏,但污染度极低。
6. 卡尔曼滤波更新算法实现(带中文注解)
cpp
/* ---- 全局卡尔曼滤波 BDP 更新函数 --------------------------------- */
/* 根据观测值 z(交付带宽)和噪声百分比 r_pct 更新全局状态 x 和协方差 P。
* check 参数控制是否启用卡方门控。 */
static u64 ucp_kf_update(u64 z, u32 r_pct, bool check)
{
// 读取当前的协方差 P 和状态估计 x
u64 P = atomic64_read(&ucp_kf_P);
u64 x = atomic64_read(&ucp_kf_x);
// 计算测量噪声 R = (z * r_pct%)^2,采用相对百分比模型
u64 R = ucp_kf_compute_R(z, r_pct);
u32 shift = 0;
u64 Pcopy, Rcopy, denom;
s64 delta;
// 过程噪声注入:固定加上 Q = 1 << ucp_kf_q_shift
// 防止长期运行后 P 归零导致滤波器麻木
P += (1ULL << ucp_kf_q_shift);
// 冷启动处理:若滤波器未激活,直接用当前观测初始化
if (unlikely(!atomic_read(&ucp_kf_active))) {
atomic64_set(&ucp_kf_x, z); // 初始状态设为观测值
atomic64_set(&ucp_kf_P, max(R, 1ULL)); // 初始协方差取 R 和 1 的较大值
atomic_set(&ucp_kf_active, 1); // 标记滤波器已激活
return z;
}
// 卡方门控:过滤非高斯野值
if (check) {
delta = (s64)z - (s64)x; // 残差 y = z - x
u64 nu2 = (u64)(delta < 0 ? -delta : delta); // |delta|
u64 S = P + R; // 残差协方差 S = P + R
if (S > 0) {
// 避免溢出和浮点:右移近似
nu2 = (nu2 >> 10) * (nu2 >> 10); // 近似 y^2 / 2^20
S >>= 20; // S / 2^20
// 比较 (y^2 / S) 与卡方阈值 (chi2_num / chi2_den)
if (S > 0 && nu2 / S >
(u64)ucp_kf_chi2_num_val / (u64)ucp_kf_chi2_den_val)
return x; // 门控未通过,丢弃样本,维持原估计
}
}
// 动态等比右移,防止乘法溢出
Pcopy = P;
Rcopy = R;
{
u64 max_v = Pcopy;
if (Rcopy > max_v) max_v = Rcopy;
if (Pcopy + Rcopy > max_v) max_v = Pcopy + Rcopy;
// 只要最大值 >= 2^31,就同步右移,确保相乘不超 64 位
while (max_v >= (1ULL << 31)) {
Pcopy >>= 1; Rcopy >>= 1; max_v >>= 1; shift++;
}
}
denom = Pcopy + Rcopy;
if (denom == 0)
return x;
// 卡尔曼状态更新:x = (x*R + z*P) / (P + R)
x = (x * Rcopy + z * Pcopy) / denom;
// 协方差更新:P = (P*R) / (P+R),注意结果缩小了 2^shift 倍
P = Pcopy * Rcopy / denom;
// 恢复协方差的原始量级
if (shift > 0) P <<= shift;
// 协方差下限保护,防止 P 低于过程噪声,保持滤波器响应能力
{
u64 q = 1ULL << ucp_kf_q_shift;
if (P < q) P = q;
}
// 写回全局原子变量(无锁)
if (x > 0) {
atomic64_set(&ucp_kf_x, x);
atomic64_set(&ucp_kf_P, P);
}
return x;
}
/* 辅助函数:计算测量噪声 R = (z * pct%)^2 */
static u64 ucp_kf_compute_R(u64 z, u32 pct)
{
u64 r = z * (u64)pct / 100;
return r * r;
}
7. 新连接初始注入
全局滤波器收敛后,新建连接通过以下函数获取初始带宽,流程如下:
连接自身状态 (cwnd, srtt) 全局原子变量 (kf_x, kf_active) 新连接 kcc_init() 连接自身状态 (cwnd, srtt) 全局原子变量 (kf_x, kf_active) 新连接 kcc_init() #mermaid-svg-As9XKHuCR9HLCkYF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-As9XKHuCR9HLCkYF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-As9XKHuCR9HLCkYF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-As9XKHuCR9HLCkYF .error-icon{fill:#552222;}#mermaid-svg-As9XKHuCR9HLCkYF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-As9XKHuCR9HLCkYF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-As9XKHuCR9HLCkYF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-As9XKHuCR9HLCkYF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-As9XKHuCR9HLCkYF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-As9XKHuCR9HLCkYF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-As9XKHuCR9HLCkYF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-As9XKHuCR9HLCkYF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-As9XKHuCR9HLCkYF .marker.cross{stroke:#333333;}#mermaid-svg-As9XKHuCR9HLCkYF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-As9XKHuCR9HLCkYF p{margin:0;}#mermaid-svg-As9XKHuCR9HLCkYF .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-As9XKHuCR9HLCkYF text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-As9XKHuCR9HLCkYF .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-As9XKHuCR9HLCkYF .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-As9XKHuCR9HLCkYF .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-As9XKHuCR9HLCkYF .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-As9XKHuCR9HLCkYF #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-As9XKHuCR9HLCkYF .sequenceNumber{fill:white;}#mermaid-svg-As9XKHuCR9HLCkYF #sequencenumber{fill:#333;}#mermaid-svg-As9XKHuCR9HLCkYF #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-As9XKHuCR9HLCkYF .messageText{fill:#333;stroke:none;}#mermaid-svg-As9XKHuCR9HLCkYF .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-As9XKHuCR9HLCkYF .labelText,#mermaid-svg-As9XKHuCR9HLCkYF .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-As9XKHuCR9HLCkYF .loopText,#mermaid-svg-As9XKHuCR9HLCkYF .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-As9XKHuCR9HLCkYF .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-As9XKHuCR9HLCkYF .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-As9XKHuCR9HLCkYF .noteText,#mermaid-svg-As9XKHuCR9HLCkYF .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-As9XKHuCR9HLCkYF .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-As9XKHuCR9HLCkYF .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-As9XKHuCR9HLCkYF .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-As9XKHuCR9HLCkYF .actorPopupMenu{position:absolute;}#mermaid-svg-As9XKHuCR9HLCkYF .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-As9XKHuCR9HLCkYF .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-As9XKHuCR9HLCkYF .actor-man circle,#mermaid-svg-As9XKHuCR9HLCkYF line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-As9XKHuCR9HLCkYF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt init_bw \< 下限 通过 alt 未激活 已激活 读取 kf_active 返回 0 (不注入) 读取 kf_x (公平份额估计) fair = kf_x init_bw = fair * discount_num / discount_den init_bw = init_bw * BBR_UNIT / high_gain 读取 cwnd, srtt 计算下限 返回 0 (注入无意义) 返回 init_bw 设置 sk_pacing_rate, snd_cwnd, minmax 历史
注入公式:
c
init_bw = (fair * discount_num / discount_den) / high_gain;
fair读取自atomic64_read(&kf_x)。discount_num/discount_den默认为 50/100,即取估计值的一半。high_gain为 STARTUP 增益,2885/1000 ≈ 2.885。
代入后有效注入系数约为瓶颈带宽的 0.5 / 2.885 ≈ 17.3%。乘以 STARTUP 增益后,实际首轮 pacing rate 约为瓶颈的 50%,避免因增益放大而直接过冲。discount_num 的可调范围为 35--75,对应有效系数 12.1%--25.9%。
初始注入值不追求准确。全局估计仅为统计平均,实际可用带宽可能偏高或偏低。KCC 单流卡尔曼与状态机能够在 2--4 个 RTT 内判断带宽的调整方向并完成修正,因此初始值只需落在可快速纠偏的区间内。
此外,注入的 pacing_rate 是发送速率的上限,实际发送量受拥塞窗口(CWND)约束。新连接的 CWND 初始固定为 TCP_INIT_CWND(10 个段,约 15 KB),在 200 ms RTT 路径上对应的最大速率约为 600 Kbps。CWND 随 ACK 逐步扩大,KCC 在每个 ACK 依据带宽和 RTT 估计重新计算目标 cwnd,执行 cwnd = min(cwnd + acked, target) 的收敛过程,而非无约束发包。因此,前若干 RTT 内的实际发送速率远低于 pacing_rate 所设定的上限,初始注入值主要作用于 CWND 打开后的收敛起点。
8. 新连接注入函数实现(带中文注解)
cpp
/* 获取新连接的初始带宽估计(甜品速度)*/
static u64 ucp_kf_get_init_bw(struct sock* sk)
{
struct tcp_sock* tp = tcp_sk(sk);
u64 fair, init_bw;
// 检查全局 KF 是否启用且已激活
if (!ucp_kf_enable || !atomic_read(&ucp_kf_active))
return 0;
// 读取全局瓶颈带宽估计
fair = (u64)atomic64_read(&ucp_kf_x);
if (fair == 0)
return 0;
// 按折扣比例打折,得到公平份额的初始带宽
init_bw = fair * (u64)ucp_kf_discount_num_val / (u64)ucp_kf_discount_den_val;
// 逆向补偿 STARTUP 增益:避免乘上 2.885 后冲顶
init_bw = init_bw * BBR_UNIT / (u64)ucp_high_gain_val;
// 保底下限:注入值不得低于当前 cwnd 所能达到的基础速率
if (init_bw < (u64)tcp_snd_cwnd(tp) * (u64)BBR_UNIT /
max_t(u32, tp->srtt_us >> 3, 1U))
return 0;
return init_bw;
}
9. 无锁全局状态
全局变量 kf_x、kf_P、kf_active 均通过 atomic64 进行读写,未使用锁。
- 写入发生在软中断上下文,每条流每 RTT 至多触发一次。
- 读出仅在新连接创建时发生。
- 读操作可能观察到
x已更新而P尚未更新的中间状态,这可能导致某次注入值出现微小抖动,但在下一次连接创建时状态已必然一致,不会造成持久性偏差。
与引入自旋锁或 RCU 的开销相比,无锁方案对数据路径的影响可以忽略。
10. 路径分组的取舍
全局滤波器基于单一瓶颈假设。若服务器同时承载途经不同 AS 出口的流,不同路径的瓶颈带宽并不相同,样本混合会使估计值偏离各自真实的瓶颈。KCC 未在内核中实现按目的网段或路由标记的路径分组机制。这一取舍并非出于实现复杂度的不可行,而是因为维护路径到状态的映射表在软中断上下文中开销过大,且分组后各组内样本更稀疏,滤波器收敛更慢,反而可能得不偿失。对于因混合路径产生的估计偏差,KCC 依靠单流在 2--4 个 RTT 内的快速修正机制吸收。
在多流竞争场景中,首秒重传数略高是普遍现象,BBR、CUBIC、PCC 等算法均无法避免,KCC 同样不能根本性解决。但 KCC 的注入策略并未显著恶化这一状况。尽管 KCC 拥有一个预测的甜品启动速度,实际行为并不横冲直撞:发送速率受 CWND 初始值严格约束,CWND 在启动过程中随观测逐步打开,这与其它拥塞控制算法一致。在成都至香港、成都至美国、香港至美国等真实路径的 iperf3 测试中,KCC 在多流竞争下的首秒重传表现与同类算法处于相同区间,属于合理预期。对于单流场景,首秒重传数通常为零或极低,注入的甜品速度显著缩短了收敛时间,且未以增加重传为代价。
11. 测试数据
以下数据采集自特定实验环境,供同行复现参考。
- 路径:中国成都移动(民用宽带)至美国洛杉矶
- RTT:212 ms
- 瓶颈带宽:1 Gbps(共享)
- 并发流数:8
- 丢包率:约 13.7%
稳态吞吐(8 流总和):
- BBRv1:937 Mbps
- KCC v1.0:1,010 Mbps
新流首秒吞吐(全局 Kalman 注入,折扣率 50%):
- 无注入(纯冷启动):约 2.8 Mbps
- 有注入:约 50 Mbps
- 单流首秒重传计数:0 或极低;多流竞争下首秒重传计数与同类算法可比。
代码仓库:PPP PRIVATE NETWORK™ X --- Kalman Congestion Control v1.0 (KCC)
KCC v1.0 基于 BBRv1 状态机与卡尔曼滤波器构建。
12. 附图
附图(一):单流

附图(二):单流16M

附图(三):多流

附图(四):流媒体
稳态:

突发:
