TCP的CHRONO统计计时

TCPIP协议 专栏收录该内容
105 篇文章 6 订阅

内核定义如下,目前由三种类型,分别是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;
}

内核版本 4.15.0

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值