一个linux bbr存在的调用顺序bug
最近跟踪bbr的状态转换的代码,发现一个问题:
[11241.360364]mode=3,min_rtt_us=553,full_bw=0,cycle_idx=0,pacing_gain=0,cwnd_gain=0,rtt_cnt=0 [11241.360373] main mode=3,min_rtt_us=553,cur_bw=0,cycle_idx=0,pacing_gain=256,cwnd_gain=256,rtt_cnt=0,snd_cwnd=4 [11241.360377]mode=0,min_rtt_us=553,full_bw=0,cycle_idx=0,pacing_gain=256,cwnd_gain=256,rtt_cnt=0
可以看到,bbr的第一个处理,是mode=3,也就是执行链:
bbr_main-->bbr_update_model-->bbr_update_min_rtt-->
static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs) { struct tcp_sock *tp = tcp_sk(sk); struct bbr *bbr = inet_csk_ca(sk); bool filter_expired; /* Track min RTT seen in the min_rtt_win_sec filter window: */ filter_expired = after(tcp_jiffies32, bbr->min_rtt_stamp + bbr_min_rtt_win_sec * HZ); if (rs->rtt_us >= 0 && (rs->rtt_us <= bbr->min_rtt_us || filter_expired)) { bbr->min_rtt_us = rs->rtt_us; bbr->min_rtt_stamp = tcp_jiffies32; } if (bbr_probe_rtt_mode_ms > 0 && filter_expired && !bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) { bbr->mode = BBR_PROBE_RTT; /* dip, drain queue */-----------------------第一个状态值
而按到正常的设计,一般来说,是先调用init再执行拥塞控制。按道理,filter_expired怎么会为1呢?因为链路刚建立,bbr->min_rtt_stamp 的初始化值是当前时间啊,还没有经过10s,
经过打点,发现bbr->min_rtt_stamp 是0,而不是bbr_init之后的值。然后继续分析代码:
static struct tcp_congestion_ops tcp_bbr_cong_ops __read_mostly = { .flags = TCP_CONG_NON_RESTRICTED, .name = "bbr", .owner = THIS_MODULE, .init = bbr_init,----------------------------初始化函数 .cong_control = bbr_main,------------------------bbr拥塞控制的主函数 .sndbuf_expand = bbr_sndbuf_expand, .undo_cwnd = bbr_undo_cwnd, .cwnd_event = bbr_cwnd_event, .ssthresh = bbr_ssthresh, .tso_segs_goal = bbr_tso_segs_goal, .get_info = bbr_get_info, .set_state = bbr_set_state, };
这样就导致了,init函数的调用在bbr_main函数之后,而不是之前。
根据tcp链接的建立调用链:
tcp_rcv_synsent_state_process-->tcp_finish_connect-->tcp_init_transfer-->tcp_init_congestion_control-->icsk->icsk_ca_ops->init(sk);
这个时候发起请求的客户端会调用拥塞控制函数的init,
tcp_rcv_synsent_state_process-->tcp_ack-->tcp_cong_control-->
static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked, int flag, const struct rate_sample *rs) { const struct inet_connection_sock *icsk = inet_csk(sk); if (icsk->icsk_ca_ops->cong_control) {------------------------bbr走这个分支 icsk->icsk_ca_ops->cong_control(sk, rs); return; }
而在 tcp_rcv_synsent_state_process 函数中,
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th) { 。。。。 tcp_ack(); 。。。。 smp_mb(); tcp_finish_connect(sk, skb); 。。。。 }
所以可以看出,拥塞函数调用是在拥塞init调用之前。
从github上最新的内核2019-2-21号的版本来看,也存在这个问题,不知道是google故意为之还是bug,个人认为应该是bug,毕竟影响了一段时间的状态以及初始值。
有心的童鞋可以去提交一个patch解决。
水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。