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