linux内核协议栈 之 TCP-TSQ

TCP Small Queues (TSQ) 是为了限制每个TCP连接在Qdisc和device队列中的skb数量,以降低RTT并防止buffer bloat。本文详细介绍了TSQ的初始化、检查、阻塞时机、阻塞处理及延迟处理过程,阐述了如何在Linux内核中实现TCP流量控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TCP Small Queues的目的是限制每个TCP连接在Qdisc和device队列中的skb数量,以达到降低RTT(Round-Trip Time)和避免buffer bloat的目的。

kernel版本:4.15

1 TSQ初始化

内核定义了静态的每处理器变量tsq_tasklet结构,初始化函数tcp_tasklet_init,为定义的每处理器结构变量初始化一个单独的tasklet,并且每个tasklet有其自身独立的套接口队列。

2 TSQ检查

2.1 tcp_small_queue_check

基础的检查函数为tcp_small_queue_check,其逻辑如下。

首先取以下两者之间的较大值:

  1. 当前发送的数据报文skb结构所占用空间的两倍值(2*skb->truesize);

  1. 以及当前大约每毫秒的流量值(其通过sk_pacing_rate计算而来,内核将sk_pacing_shift变量定义为10,将当前每秒钟的流量(sk_pacing_rate)除以2的10次方,得到大约1毫秒的流量值)。

其次如果此结果值大约sysctl_tcp_limit_output_bytes 限定的值,使用sysctl_tcp_limit_output_bytes作为限定值。最后,如果是重传报文,即factor等于1,将最终的判定值增大一倍。

如果sk_wmem_alloc大于limit说明已经发送了太多的数据在Qdisc或者设备队列中,但是如果重传队列为空,此次发送还是允许进行。否则,返回true,禁止发送操作,在此之前,设置sk_tsp_flags的标志位TSQ_THROTTLED,表明是由于TSQ检查结果导致的不能发送。另外,有可能在此函数设置TSQ_THROTTLED标志之前,发生了TX中断,进行了skb释放操作,导致了sk_wmem_alloc的递减,所以,在返回前再次判断sk_wmem_alloc是否超过限值limit。

2.2 tcp_write_xmit

以上基础函数tcp_small_queue_check在TCP发送路径的tcp_write_xmit函数和tcp_xmit_retransmit_queue函数中都有调用。如下的tcp_write_xmit函数,在调用TSQ检查函数之前,清空套接口的TCP_TSQ_DEFERRED标志,表明进行了发送操作,在之后的TSQ处理函数中将跳过此套接口。如果TSQ检查未通过,将设置TSQ_THROTTLED标志。

2.3 tcp_push

另外,在系统调用函数do_tcp_sendpages与tcp_sendmsg_locked函数中,如果缺少TCP缓存,发送函数将已经挂载到发送队列中的数据进行发送,由函数tcp_push负责处理。当其判断如果不需要立即发送数据包(autocork)时,需要设置TSQ_THROTTLED标志,表明TSQ功能阻塞了数据发送。如果TX中断随后发生,其将释放部分发送缓存,发送函数如tcp_sendmsg_locked得以继续进行。

2.4 tcp_should_autocork

此处有必要说一下tcp_should_autocork函数的判断条件:

  1. skb的数据长度len小于size_goal,表明数据还未填满允许的发送目标长度(参见函数tcp_xmit_size_goal,支持GSO时此目标长度可能大小MSS,反之等于MSS);

  1. PROC文件/proc/sys/net/ipv4/tcp_autocorking设置为1;

  1. 当前skb不是发送队列头,表明已经有数据发送;

  1. 并且,当前正在发送的数据的总truesize长度大于此skb的truesize,表明位于Qdisc或者设备队列中的数据不是仅有ACK报文,所以TX发送处理不会被延迟。

综上的四个条件,TX中断马上要发送,此时有机会将数据包暂时阻塞,为之后发生的sendmsg的发送数据提供与此skb合并的机会。由于TX中断马上要发生,不会造成此数据包的发送延时。

3 TSQ阻塞时机

3.1 tcp_wfree

当数据报文skb释放时(如TX完成中断发生),检查其所属套接口的TSQ阻塞状态,如下发送缓存释放函数tcp_wfree。首先将发送缓存sk_wmem_alloc的值将其skb的truesize-1的值,保留1个计数是由于此函数之后或者tcp_tasklet_func函数将调用sk_free函数,如果sk_wmem_alloc为1,sk_free将释放套接口结构。其次如果当前进程上下文在ksoftirqd中,并且Qdisc或者设备队列中还有数据未发送,表明系统当前繁忙,不在进行TSQ拥塞处理,避免导致加重系统的繁忙状况,也可使此TCP流的确认ACK报文得以处理。

如果此套接口没有被TSQ阻塞,TSQF_THROTTLED标志未设置,或者其已经位于TSQ处理队列中,直接返回。如果在此期间套接口的阻塞状态已经缓解(见cmpxchg函数实现),直接返回。否则,将处于阻塞状态的套接口的sk_tsq_flags中的阻塞状态清除,并且将此套接口添加到当前处理器的TSQ处理队列中,并且调用处理器的takslet进行处理。

3.2 tcp_pace_kick

除以以上的tcp_wfree函数之外,在TCP的pacing功能中,pacing超时处理函数tcp_pace_kick已将处理TSQ除以阻塞状态的套接口,其逻辑与tcp_wfree中的一致,唯一区别在于pacing处理中,不会先检查套接口是否处于阻塞状态。

4 TSQ阻塞处理

4.1 tcp_tasklet_func

以上的tasklet_schedule调用了初始化时注册的tcp_tasklet_func函数。如下,在其处理过程中,首先从TSQ队列中移除套接口,清除其TSQ_QUEUE入队标志。其次如果此套接口未设置TCP_TSQ_DEFERRED标志,表明其已经在tcp_write_xmit发送函数中得到处理,此处不再处理。否则,如果此套接口没有被用户层系统调用锁定,调用TSQ核心处理函数tcp_tsq_handler处理。

4.2 tcp_tsq_handler

TSQ的核心处理函数tcp_tsq_handler,负责重新调用TCP的发送或者重传函数。

5 TSQ延迟处理

如果在以上的操作中,由于用户层正在执行套接口相关的系统调用,导致未能进行。将在用户层系统调用退出时,在套接口释放函数中tcp_release_cb,根据标志TCPF_TSQ_DEFERRED的判断,执行TSQ函数tcp_tsq_handler。

 void tcp_release_cb(struct sock *sk)
{
    do {
        flags = sk->sk_tsq_flags;
        if (!(flags & TCP_DEFERRED_ALL))
            return;
        nflags = flags & ~TCP_DEFERRED_ALL;
    } while (cmpxchg(&sk->sk_tsq_flags, flags, nflags) != flags);
 
    if (flags & TCPF_TSQ_DEFERRED)
        tcp_tsq_handler(sk);
}

6 参考文献

TCP-TSQ:控制,不再是限制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值