TCP的CHRONO统计计时

发布于:2023-01-22 ⋅ 阅读:(454) ⋅ 点赞:(0)

内核定义如下,目前由三种类型,分别是TCP_CHRONO_BUSY、TCP_CHRONO_RWND_LIMITED和TCP_CHRONO_SNDBUF_LIMITED。

enum tcp_chrono {
    TCP_CHRONO_UNSPEC,
    TCP_CHRONO_BUSY, /* Actively sending data (non-empty write queue) */
    TCP_CHRONO_RWND_LIMITED, /* Stalled by insufficient receive window */
    TCP_CHRONO_SNDBUF_LIMITED, /* Stalled by insufficient send buffer */
    __TCP_CHRONO_MAX,
};
在TCP套接口中,成员chrono_stat保存了三种计时器统计的时间长度。

struct tcp_sock {
    u32 chrono_start;   /* Start time in jiffies of a TCP chrono */
    u32 chrono_stat[3]; /* Time in jiffies for chrono_stat stats */
    u8  chrono_type:2,  /* current chronograph type */
}

一、计时开始结束

即使开始函数为tcp_chrono_start,三种计时器类型值越大优先级越高,即TCP_CHRONO_SNDBUF_LIMITED类型计时器优先级最高。所以当开启TCP_CHRONO_SNDBUF_LIMITED计时器时,需要停止其它类型的计时器。

void tcp_chrono_start(struct sock *sk, const enum tcp_chrono type)
{       
    struct tcp_sock *tp = tcp_sk(sk);
 
    if (type > tp->chrono_type)
        tcp_chrono_set(tp, type);
}
关于tcp_chrono_stop计时器停止函数,需要注意在停止类型TCP_CHRONO_RWND_LIMITED或者TCP_CHRONO_SNDBUF_LIMITED的计时器时,同时会打开TCP_CHRONO_BUSY计时器类型。

void tcp_chrono_stop(struct sock *sk, const enum tcp_chrono type)
{
    struct tcp_sock *tp = tcp_sk(sk);
 
    if (tcp_rtx_and_write_queues_empty(sk))
        tcp_chrono_set(tp, TCP_CHRONO_UNSPEC);
    else if (type == tp->chrono_type)
        tcp_chrono_set(tp, TCP_CHRONO_BUSY);
}
函数tcp_chrono_set完成最后的计时统计,用当前时间减去计时器开始时间chrono_start,将所得时长保存到对应的以类型值为索引的chrono_stat数组中。记录当前时间值到chrono_start,开始新的类型计时。

static void tcp_chrono_set(struct tcp_sock *tp, const enum tcp_chrono new)
{
    const u32 now = tcp_jiffies32;
    enum tcp_chrono old = tp->chrono_type;
 
    if (old > TCP_CHRONO_UNSPEC)
        tp->chrono_stat[old - 1] += now - tp->chrono_start;
    tp->chrono_start = now;
    tp->chrono_type = new;
}

二、TCP_CHRONO_BUSY计时

在套接口发送TCP数据包时,内核开始计时。分为两处,函数tcp_add_write_queue_tail中将数据包加入发送队列后,如果是队列中的第一个数据包,启动TCP_CHRONO_BUSY计时器;另外,在函数tcp_send_syn_data中,如果需要同SYN报文一起发送数据,即tcp fastopen情况下,也启动计时。

static inline void tcp_add_write_queue_tail(struct sock *sk, struct sk_buff *skb)
{
    __tcp_add_write_queue_tail(sk, skb);
 
    if (sk->sk_write_queue.next == skb)
        tcp_chrono_start(sk, TCP_CHRONO_BUSY);
}
static int tcp_send_syn_data(struct sock *sk, struct sk_buff *syn)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *syn_data;
 
    if (syn_data->len)
        tcp_chrono_start(sk, TCP_CHRONO_BUSY);
}

当接收到对端的ACK报文,清空本端的重传队列时,停止TCP_CHRONO_BUSY计时器。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack, u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
    if (!skb)
        tcp_chrono_stop(sk, TCP_CHRONO_BUSY);
}
另外,在tcp发送报文时,如果数据包的payload长度为0,检测一下发送队列是否为空,空的话停止TCP_CHRONO_BUSY计时器。

static inline void tcp_check_send_head(struct sock *sk, struct sk_buff *skb_unlinked)
{
    if (tcp_write_queue_empty(sk))
        tcp_chrono_stop(sk, TCP_CHRONO_BUSY);
}
int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{
    if (!skb->len) {
        tcp_unlink_write_queue(skb, sk);
        tcp_check_send_head(sk, skb);
        sk_wmem_free_skb(sk, skb);
    }
}

三、RWND_LIMITED计时
TCP_CHRONO_RWND_LIMITED计时器表示由于对端接收窗口限制,所导致的暂停发包时间。在函数tcp_write_xmit发送报文之前,通过tcp_snd_wnd_test函数判断对端窗口是否会由于当前数据包的发送而超限?首先明确当前发送数据包的长度不能大于MSS的长度,其次,计算对端窗口的最大序列号,由函数tcp_wnd_end可见,其等于本端已发送但还未接收到ACK的最后一个字节的序列号与本端计算的可用发送窗口的总和。

static inline u32 tcp_wnd_end(const struct tcp_sock *tp)
{  
    return tp->snd_una + tp->snd_wnd;
}
static bool tcp_snd_wnd_test(const struct tcp_sock *tp, const struct sk_buff *skb, unsigned int cur_mss)
{   
    u32 end_seq = TCP_SKB_CB(skb)->end_seq;
   
    if (skb->len > cur_mss)
        end_seq = TCP_SKB_CB(skb)->seq + cur_mss;
 
    return !after(end_seq, tcp_wnd_end(tp));

如果由于对端发送窗口的原因不能发送数据包,启动TCP_CHRONO_RWND_LIMITED计时器,否则停止此计时器。

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, int push_one, gfp_t gfp)
{
    struct tcp_sock *tp = tcp_sk(sk);
    bool is_cwnd_limited = false, is_rwnd_limited = false;
 
    while ((skb = tcp_send_head(sk))) {
        if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now))) {
            is_rwnd_limited = true;
            break;
        }
    }
    if (is_rwnd_limited)
        tcp_chrono_start(sk, TCP_CHRONO_RWND_LIMITED);
    else
        tcp_chrono_stop(sk, TCP_CHRONO_RWND_LIMITED);
}

四、SNDBUF_LIMITED计时器
TCP_CHRONO_SNDBUF_LIMITED计时器表明由于本端发送缓存不足所导致的发包暂停时间。在函数tcp_cwnd_validate中,如果网络拥塞未拥塞,并且发送队列为空,而且应用层程序发包被设置了SOCK_NOSPACE标志,启动TCP_CHRONO_SNDBUF_LIMITED计时器。

static void tcp_cwnd_validate(struct sock *sk, bool is_cwnd_limited)
{
    const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops;
 
    if (tcp_is_cwnd_limited(sk)) {
        /* Network is feed fully. */
    } else {
        /* The following conditions together indicate the starvation
         * is caused by insufficient sender buffer:
         * 1) just sent some data (see tcp_write_xmit)
         * 2) not cwnd limited (this else condition)
         * 3) no more data to send (tcp_write_queue_empty())
         * 4) application is hitting buffer limit (SOCK_NOSPACE)
         */
        if (tcp_write_queue_empty(sk) && sk->sk_socket &&
            test_bit(SOCK_NOSPACE, &sk->sk_socket->flags) &&
            (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
            tcp_chrono_start(sk, TCP_CHRONO_SNDBUF_LIMITED);
    }
}

函数tcp_cwnd_validate在tcp_write_xmit中被调用,sent_pkts表明进行了数据包发送操作。

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, int push_one, gfp_t gfp)
{
    if (likely(sent_pkts)) {
        tcp_cwnd_validate(sk, is_cwnd_limited);
        return false;
    }
}
不管在函数tcp_sendmsg_locked或者函数do_tcp_sendpages中,如果检测到了发送队列为空,调用回调函数sk_write_space(对于TCP而言为函数sk_stream_write_space)进行发送队列空间的清理,之后停止TCP_CHRONO_SNDBUF_LIMITED计时器。

ssize_t do_tcp_sendpages(struct sock *sk, struct page *page, int offset, size_t size, int flags)
{
    /* make sure we wake any epoll edge trigger waiter */
    if (unlikely(skb_queue_len(&sk->sk_write_queue) == 0 && err == -EAGAIN)) {
        sk->sk_write_space(sk);
        tcp_chrono_stop(sk, TCP_CHRONO_SNDBUF_LIMITED);
    }
}
同理,在函数tcp_data_snd_check中发送完数据之后,进行SOCK_NOSPACE空间检测,不成立的情况下,停止TCP_CHRONO_SNDBUF_LIMITED计时器。

static void tcp_check_space(struct sock *sk)
{
    if (sock_flag(sk, SOCK_QUEUE_SHRUNK)) {
        sock_reset_flag(sk, SOCK_QUEUE_SHRUNK);
        if (sk->sk_socket && test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
            tcp_new_space(sk);
            if (!test_bit(SOCK_NOSPACE, &sk->sk_socket->flags))
                tcp_chrono_stop(sk, TCP_CHRONO_SNDBUF_LIMITED);
        }
    }
}
static inline void tcp_data_snd_check(struct sock *sk)
{
    tcp_push_pending_frames(sk);
    tcp_check_space(sk);
}

五、计时统计信息获取
可在应用层通过NETLINK_SOCK_DIAG类型的netlink获取。

static int __net_init diag_net_init(struct net *net)
{
    struct netlink_kernel_cfg cfg = {
        .groups = SKNLGRP_MAX,
        .input  = sock_diag_rcv,
        .bind   = sock_diag_bind,
        .flags  = NL_CFG_F_NONROOT_RECV,
    };
 
    net->diag_nlsk = netlink_kernel_create(net, NETLINK_SOCK_DIAG, &cfg);
    return net->diag_nlsk == NULL ? -ENOMEM : 0;
}
 

本文含有隐藏内容,请 开通VIP 后查看