在2.6.32内核中,基于丢包的拥塞算法基本都需要考虑,delay_ack所带来的影响。例如每个ack都确认两个数据包,如果被拥塞算法当做一个ack确认一个数据包,那窗口的增加速率必然下降。在4.9内核版本之前,由于拥塞窗口接口函数原型中并不携带本次ack确认多少个数据包信息,当拥塞窗口进行调整时,确需要考虑到delay_ack进行适当的调整。主要的过程是使用类似计算srtt_us的方式,估算一个ack平均确认多少个数据包,在拥塞窗口调整时,对调整幅度进行微调。
主要数据结构
struct tcp_congestion_ops {
struct list_head list;
unsigned long flags;
/* initialize private data (optional) */
void (*init)(struct sock *sk);
/* cleanup private data (optional) */
void (*release)(struct sock *sk);
/* return slow start threshold (required) */
u32 (*ssthresh)(struct sock *sk);
/* lower bound for congestion window (optional) */
u32 (*min_cwnd)(const struct sock *sk);
/* do new cwnd calculation (required) */
void (*cong_avoid)(struct sock *sk, u32 ack, u32 in_flight);//拥塞窗口调整接口
/* call before changing ca_state (optional) */
...
void (*pkts_acked)(struct sock *sk, u32 num_acked, s32 rtt_us);//收到ack确认数据包调用接口
/* get info for inet_diag (optional) */
void (*get_info)(struct sock *sk, u32 ext, struct sk_buff *skb);
char name[TCP_CA_NAME_MAX];
struct module *owner;
};
需要注意的是4.9内核中拥塞窗口调整函数原型已经变为
void (*cong_avoid)(struct sock *sk, u32 ack, u32 acked);
不同之处在于最后一个变量,网络中存在数据包个数in_flight变为了本次ack确认的数据包个数。已经将该ack确认的数据包个数传递进来,因此,不需要再进行delay_ack的比例估算。
以2.6.32内核中的bic算法为例,使用计算平均delay_ack比例在拥塞窗口调整函数bictcp_cong_avoid中调用bictcp_update函数
static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 in_flight)
{
struct tcp_sock *tp = tcp_sk(sk);
struct bictcp *ca = inet_csk_ca(sk);
if (!tcp_is_cwnd_limited(sk, in_flight))
return;
if (tp->snd_cwnd <= tp->snd_ssthresh)
tcp_slow_start(tp);
else {
bictcp_update(ca, tp->snd_cwnd);
tcp_cong_avoid_ai(tp, ca->cnt);
}
}
bictcp_update函数中结尾部分,对控制调整拥塞窗口快慢的变量ca->cnt进行按比例调整。
static inline void bictcp_update(struct bictcp *ca, u32 cwnd)
{
....
ca->cnt = (ca->cnt << ACK_RATIO_SHIFT) / ca->delayed_ack; //对调整拥塞窗口的幅度进行按比例调整
if (ca->cnt == 0) /* cannot be zero */
ca->cnt = 1;
}
而估算delay_ack比例的部分在(*pkts_acked)接口函数中进行
#define ACK_RATIO_SHIFT 4
/* Track delayed acknowledgment ratio using sliding window
* ratio = (15*ratio + sample) / 16
*/
static void bictcp_acked(struct sock *sk, u32 cnt, s32 rtt)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
if (icsk->icsk_ca_state == TCP_CA_Open) {
struct bictcp *ca = inet_csk_ca(sk);
cnt -= ca->delayed_ack >> ACK_RATIO_SHIFT;
ca->delayed_ack += cnt;
}
}
代码比较简单就不分析了,但是存在一个bug,假设初始状态ca->delayed_ack = 32,每次ack都不是delay_ack,都确认一个数据包,ca->delay_ack最小值却停留在31,不会继续减小。原因就是先ca->delay_ack/16,由于整型除法是向下取整,因此
cubic版本中对delay_ack的计算也类似,也存在ca->delay_ack最小值为31的问题,不同的是增加了最大值限制。
#define ACK_RATIO_SHIFT 4
#define ACK_RATIO_LIMIT (32u << ACK_RATIO_SHIFT)
static void bictcp_acked(struct sock *sk, u32 cnt, s32 rtt_us)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcp_sock *tp = tcp_sk(sk);
struct bictcp *ca = inet_csk_ca(sk);
u32 delay;
if (icsk->icsk_ca_state == TCP_CA_Open) {
u32 ratio = ca->delayed_ack;
ratio -= ca->delayed_ack >> ACK_RATIO_SHIFT;
ratio += cnt;
ca->delayed_ack = min(ratio, ACK_RATIO_LIMIT);//最大比例不能超过32
}
......
}