当前位置:网站首页>TCP协议之《自动阻塞CORK控制》

TCP协议之《自动阻塞CORK控制》

2022-08-10 03:26:00 程序员扫地僧

当应用程序在使用write或者sendmsg系统调用连续的发送少量数据包时,内核试图将这些小包尽可能的合并在一起发送,以降低总得数据包量。得以实现的前提是,至少有一个同数据流的之前数据包正在Qdisc队列或者网络设备的队列中等待发送。以下详细解释这一点。
 
一、初始化
在TCP协议的初始化函数tcp_sk_init中,将网络命名空间的sysctl_tcp_autocorking设置为1,打开自动阻塞功能。可通过查看PROC文件系统的/proc/sys/net/ipv4/tcp_autocorking获得当前设置值,也可进行修改,置零关闭此功能。

static int __net_init tcp_sk_init(struct net *net)
{
    net->ipv4.sysctl_tcp_autocorking = 1;
}
$ cat /proc/sys/net/ipv4/tcp_autocorking
1

二、自动阻塞判断

核心函数tcp_should_autocork,这里要进行四个判断已确定是否执行自动阻塞autocork。大致解释如下:如果当前SKB的数据还未填满(size_goal为MSS整数倍),并且Qdisc或者网络设备队列中有数据包,将不发送当前的SKB而是CORK住。原因是,Qdisc或者NIC队列中的数据包马上就要发送,当发送完成中断到来前,还可将用户随后要发送的数据合并到当前CORK住的SKB中。中断发生之后,TSQ功能负责数据包的继续发送。

最后一个判断,意味着如果Qdisc或者NIC队列中仅有ACK报文,autocorking将不能使用,可能会在处理完成ACK报文之后,发送完成中断被延后,将造成当前数据包的延迟。套接口变量sk_wmem_alloc表示该套接口提交到网络层发送的数据总长度,由于ACK报文没有数据部分,所以理论上其在提交发送时sk_wmem_alloc并不增加,但是由于某种原因增加2。

static bool tcp_should_autocork(struct sock *sk, struct sk_buff *skb, int size_goal)
{
    return skb->len < size_goal &&
           sock_net(sk)->ipv4.sysctl_tcp_autocorking &&
           skb != tcp_write_queue_head(sk) &&
           refcount_read(&sk->sk_wmem_alloc) > skb->truesize;
}

三、判断执行流程

内核在函数tcp_push中调用tcp_should_autocork判断,如果autocork成立,将设置TCP套接口的TSQ_THROTTLED标志。随后再次判断sk_wmem_alloc的值是否依然大于SKB的truesize,为真直接返回,否则说明发送中断已经发生了,autocork失效,进行数据的发送__tcp_push_pending_frames。

static void tcp_push(struct sock *sk, int flags, int mss_now, int nonagle, int size_goal)
{    
    skb = tcp_write_queue_tail(sk);
    if (!skb)
        return;
    if (!(flags & MSG_MORE) || forced_push(tp))
        tcp_mark_push(tp, skb);
    
    tcp_mark_urg(tp, flags);
 
    if (tcp_should_autocork(sk, skb, size_goal)) {           
        if (!test_bit(TSQ_THROTTLED, &sk->sk_tsq_flags)) {   /* avoid atomic op if TSQ_THROTTLED bit is already set */
            NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTOCORKING);
            set_bit(TSQ_THROTTLED, &sk->sk_tsq_flags);
        }
        /* It is possible TX completion already happened before we set TSQ_THROTTLED. */
        if (refcount_read(&sk->sk_wmem_alloc) > skb->truesize)
            return;
    }
    if (flags & MSG_MORE)
        nonagle = TCP_NAGLE_CORK;
 
    __tcp_push_pending_frames(sk, mss_now, nonagle);
}

TCP数据发送函数tcp_transmit_skb如下,以上的函数__tcp_push_pending_frames将调用此函数执行发送。由函数skb_is_tcp_pure_ack可知,实际上内核将纯ACK报文的truesize设置为了2,相应套接口的sk_wmem_alloc在发送纯ACK报文时增加了2。特别要注意的是SKB的销毁回调函数,如果是纯ACK报文,其为__sock_wfree,反之,非纯ACK报文,使用tcp_wfree。

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask)
{
    skb->destructor = skb_is_tcp_pure_ack(skb) ? __sock_wfree : tcp_wfree;
    skb_set_hash_from_sk(skb, sk);
    refcount_add(skb->truesize, &sk->sk_wmem_alloc);
}
static inline bool skb_is_tcp_pure_ack(const struct sk_buff *skb) 
{               
    return skb->truesize == 2;

 

四、发送完成中断

当数据发送完成中断发生时,将调用destructor回调销毁SKB,调用如下的tcp_wfree函数,但是由于此时很可能获得了Qdisc锁,不能直接在此函数中发送之前autocork住的数据。内核调度TSQ的tasklet执行实际的发送。详情见TSQ的介绍:https://blog.csdn.net/sinat_20184565/article/details/89341370。由于纯ACK的销毁回调函数为__sock_wfree,所以在tcp_should_autocork判断函数中,如果Qdisc或者NIC队列中的报文为纯ACK报文,不能使能autocork。

void tcp_wfree(struct sk_buff *skb)
{
    WARN_ON(refcount_sub_and_test(skb->truesize - 1, &sk->sk_wmem_alloc));
 
    for (oval = READ_ONCE(sk->sk_tsq_flags);; oval = nval) {
        if (!(oval & TSQF_THROTTLED) || (oval & TSQF_QUEUED))
            goto out;
 
        nval = (oval & ~TSQF_THROTTLED) | TSQF_QUEUED | TCPF_TSQ_DEFERRED;
        nval = cmpxchg(&sk->sk_tsq_flags, oval, nval);
        if (nval != oval)
            continue;
 
        local_irq_save(flags);
        tsq = this_cpu_ptr(&tsq_tasklet);
        empty = list_empty(&tsq->head);
        list_add(&tp->tsq_node, &tsq->head);
        if (empty)
            tasklet_schedule(&tsq->tasklet);
        local_irq_restore(flags);
        return;
    }
}

原网站

版权声明
本文为[程序员扫地僧]所创,转载请带上原文链接,感谢
https://blog.csdn.net/wuyongmao/article/details/126246737