TCP-Reno擁塞算法

經典的Reno算法實現了三個擁塞函數,如下所示:

struct tcp_congestion_ops tcp_reno = {
    .flags      = TCP_CONG_NON_RESTRICTED,
    .name       = "reno",
    .owner      = THIS_MODULE,
    .ssthresh   = tcp_reno_ssthresh,
    .cong_avoid = tcp_reno_cong_avoid,
    .undo_cwnd  = tcp_reno_undo_cwnd,
};

TCP套接口在進入TCP_CA_CWR/Recovery/Loss擁塞狀態時,將執行tcp_congestion_ops擁塞結構的成員函數ssthresh,更新慢啓動閾值。如下所示,將擁塞窗口減半,但是,不低於2。

/* Slow start threshold is half the congestion window (min 2) */
u32 tcp_reno_ssthresh(struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);

    return max(tp->snd_cwnd >> 1U, 2U);
}

在擁接收到ACK確認報文時,調用cong_avoid指針函數,即tcp_reno_cong_avoid。如果當前擁塞窗口足夠使用,不進行處理。如果TCP套接口處於慢啓動階段,由函數tcp_slow_start處理擁塞窗口的增長,返回值acked表示慢啓動結束後,還剩餘的確認報文數量,這部分剩餘數量用於擁塞避免階段的窗口處理,即函數tcp_cong_avoid_ai。

/* This is Jacobson's slow start and congestion avoidance. SIGCOMM '88, p. 328.
 */
void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (!tcp_is_cwnd_limited(sk))
        return;

    /* In "safe" area, increase. */
    if (tcp_in_slow_start(tp)) {
        acked = tcp_slow_start(tp, acked);
        if (!acked)
            return;
    }
    /* In dangerous area, increase slowly. */
    tcp_cong_avoid_ai(tp, tp->snd_cwnd, acked);
}

在慢啓動階段,新的擁塞窗口等於原窗口加上ACK確認的報文數量,但是,如果結果值超過慢啓動閾值ssthresh,超出部分不執行窗口增加操作,交由擁塞避免階段算法處理。

/* Slow start is used when congestion window is no greater than the slow start
 * threshold. We base on RFC2581 and also handle stretch ACKs properly.
 * We do not implement RFC3465 Appropriate Byte Counting (ABC) per se but
 * something better;) a packet is only considered (s)acked in its entirety to
 * defend the ACK attacks described in the RFC. Slow start processes a stretch
 * ACK of degree N as if N acks of degree 1 are received back to back except
 * ABC caps N to 2. Slow start exits when cwnd grows over ssthresh and
 * returns the leftover acks to adjust cwnd in congestion avoidance mode.
 */
u32 tcp_slow_start(struct tcp_sock *tp, u32 acked)
{
    u32 cwnd = min(tp->snd_cwnd + acked, tp->snd_ssthresh);

    acked -= cwnd - tp->snd_cwnd;
    tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp);

    return acked;
}

在擁塞避免階段,如果擁塞計數snd_cwnd_cnt大於當前窗口,清空計數,將當前擁塞窗口加一(snd_cwnd)。反之,如果擁塞計數snd_cwnd_cnt小於當前窗口,將計數增加acked,如果增加之後,計數大於等於當前窗口w,新的擁塞窗口(snd_cwnd)增加值爲計數除以當前窗口的整數值,餘數還保存在窗口計數變量中(snd_cwnd_cnt)。擁塞避免階段,對於每個ACK報文,窗口增長遵循以下公式:

cwnd+=1cwnd cwnd += \frac{1}{cwnd}

/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd (or alternative w),
 * for every packet that was ACKed.
 */
void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w, u32 acked)
{
    /* If credits accumulated at a higher w, apply them gently now. */
    if (tp->snd_cwnd_cnt >= w) {
        tp->snd_cwnd_cnt = 0;
        tp->snd_cwnd++;
    }

    tp->snd_cwnd_cnt += acked;
    if (tp->snd_cwnd_cnt >= w) {
        u32 delta = tp->snd_cwnd_cnt / w;

        tp->snd_cwnd_cnt -= delta * w;
        tp->snd_cwnd += delta;
    }
    tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_cwnd_clamp);
}

Reno處理擁塞撤銷的函數tcp_reno_undo_cwnd,返回當前擁塞窗口和擁塞發生前窗口兩者之間的最大值。

u32 tcp_reno_undo_cwnd(struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);

    return max(tp->snd_cwnd, tp->prior_cwnd);
}

內核版本 5.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章