当前位置:网站首页>TCP协议之《TCP_CORK选项》

TCP协议之《TCP_CORK选项》

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

用户层可通过setsockopt系统调用设置TCP套接口的TCP_CORK选项。开启时,内核将阻塞不完整的报文,当关闭此选项时,发送阻塞的报文。此处的不完整指的是应用层发送的数据长度不足一个MSS长度。使用场景是在调用sendfile发送文件内容之前,提前发送一个描述文件信息的头部数据段,并且阻塞住此头部数据,与之后的sendfile数据一同发送。或者用于优化吞吐性能。但是,TCP_CORK最多只能将数据阻塞200毫秒,如果超过此时间值,内核将自动发送阻塞的数据。

一、用户层设置
内核函数do_tcp_setsockopt处理用户层套接口的TCP_CORK选项设置。开启时,TCP套接口的nonagle变量增加TCP_NAGLE_CORK标志,关闭选项时,清除TCP_NAGLE_CORK标志,调用tcp_push_pending_frames函数发送阻塞的数据。

static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, unsigned int optlen)
{
    switch (optname) {
    case TCP_CORK:
        if (val) {
            tp->nonagle |= TCP_NAGLE_CORK;
        } else {
            tp->nonagle &= ~TCP_NAGLE_CORK;
            if (tp->nonagle&TCP_NAGLE_OFF)
                tp->nonagle |= TCP_NAGLE_PUSH;
            tcp_push_pending_frames(sk);
        }
        break;
}
用户层获取TCP_CORK状态时,内核发送TCP套接口的nonagle变量的TCP_NAGLE_CORK标志位的值。

static int do_tcp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen)
{
    switch (optname) {
    case TCP_CORK:
        val = !!(tp->nonagle&TCP_NAGLE_CORK);
        break;
}

二、发送判断

TCP_CORK的判断位于NAGLE算法判断函数tcp_nagle_check中。如下的第二个判断条件,如果nonagle变量设置了TCP_NAGLE_CORK标志,阻塞数据包的发送。另外两个条件判断为NAGLE算法服务,第一个条件是判断数据包是否达到MSS的长度;第三个判断是在TCP_CORK未开启,并且nonagle为空的情况下(NAGLE算法开启),如果套接口有未确认的数据报文,而且minshell检查为真,意味着上一个发送的小报文还未得到确认,阻塞当前要发送的小报文。

RFC1122定义的延迟确认(delayed ack)功能会导致NAGLE算法在有些情况的低效运行,例如在内核要发送一个大包和紧随的一个小包时,对端的延迟确认功能可能导致第一个大包的确认ACK报文不能及时发送(最长200毫秒),进而本端的最后一个小包由于NAGLE算法也不能发出。为此minshell功能做了一些改进,即在检测到本端并没有未确认的小包时,立即发送此小包。

/* Return false, if packet can be sent now without violation Nagle's rules:
 * 1. It is full sized. (provided by caller in %partial bool)
 * 2. Or it contains FIN. (already checked by caller)
 * 3. Or TCP_CORK is not set, and TCP_NODELAY is set.
 * 4. Or TCP_CORK is not set, and all sent packets are ACKed. With Minshall's modification: all sent small packets are ACKed.
 */
static bool tcp_nagle_check(bool partial, const struct tcp_sock *tp, int nonagle)
{
    return partial &&
        ((nonagle & TCP_NAGLE_CORK) ||
         (!nonagle && tp->packets_out && tcp_minshall_check(tp)));
}
static bool tcp_minshall_check(const struct tcp_sock *tp)
{
    return after(tp->snd_sml, tp->snd_una) && !after(tp->snd_sml, tp->snd_nxt);
}

判断函数tcp_nagle_test,首先NAGLE算法不应用于发送队列中间的数据报文,因为这些报文已经不可能再获得用户空间的数据了,只有队列尾部的SKB还有可能被填充数据,见TCP_NAGLE_PUSH宏的判断。另外紧急数据和FIN报文也是直接发送。最后是进行NAGLE检查tcp_nagle_check。

/* Return true if the Nagle test allows this packet to be sent now.
 */
static inline bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb, unsigned int cur_mss, int nonagle)
{
    if (nonagle & TCP_NAGLE_PUSH)
        return true;
 
    /* Don't use the nagle rule for urgent data (or for the final FIN). */
    if (tcp_urg_mode(tp) || (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN))
        return true;
 
    if (!tcp_nagle_check(skb->len < cur_mss, tp, nonagle))
        return true;
 
    return false;
}

判断函数tcp_mss_split_point作用于在TSO有多个片段的情况下,有可能最后一个片段的长度小于当前的MSS值,调用以上的函数tcp_nagle_check检查NAGLE算法是否允许发送最后的小片段,如不允许,返回去掉最后小片段长度的可发送长度值。

/* Returns the portion of skb which can be sent right away */
static unsigned int tcp_mss_split_point(const struct sock *sk, const struct sk_buff *skb, unsigned int mss_now,
                    unsigned int max_segs, int nonagle)
{   
    const struct tcp_sock *tp = tcp_sk(sk);
    u32 partial, needed, window, max_len;
    
    window = tcp_wnd_end(tp) - TCP_SKB_CB(skb)->seq;
    max_len = mss_now * max_segs;
    
    if (likely(max_len <= window && skb != tcp_write_queue_tail(sk)))
        return max_len;
    
    needed = min(skb->len, window);
    
    if (max_len <= needed)
        return max_len;
    
    partial = needed % mss_now;
    /* If last segment is not a full MSS, check if Nagle rules allow us
     * to include this last segment in this skb. Otherwise, we'll split the skb at last MSS boundary
     */
    if (tcp_nagle_check(partial != 0, tp, nonagle))
        return needed - partial;
    
    return needed;
}

三、发送路径
由函数tcp_push可见,如果用户层在发送类send函数中设置了MSG_MORE的标志,此处将nonagle变量设置为TCP_NAGLE_CORK,意味着此数据包暂时不发送出去。发送函数__tcp_push_pending_frames最终调用tcp_write_xmit进行数据传输。

static void tcp_push(struct sock *sk, int flags, int mss_now, int nonagle, int size_goal)
{   
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *skb;
    
    skb = tcp_write_queue_tail(sk);
 
    if (flags & MSG_MORE)
        nonagle = TCP_NAGLE_CORK;
    
    __tcp_push_pending_frames(sk, mss_now, nonagle);
}
实际发送函数tcp_write_xmit使用到了前一节提到的函数tcp_nagle_test和tcp_mss_split_point,前者在TSO的片段数量为1时调用,后者在TSO片段数量大于1并且非紧急模式时调用,以完成NAGLE算法的判断。

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, int push_one, gfp_t gfp)
{
    max_segs = tcp_tso_segs(sk, mss_now);
    while ((skb = tcp_send_head(sk))) {
        tso_segs = tcp_init_tso_segs(skb, mss_now);
 
        if (tso_segs == 1) {
            if (unlikely(!tcp_nagle_test(tp, skb, mss_now, (tcp_skb_is_last(sk, skb) ? nonagle : TCP_NAGLE_PUSH))))
                break;
        } else {
            if (!push_one && tcp_tso_should_defer(sk, skb, &is_cwnd_limited, max_segs))
                break;
        }
 
        limit = mss_now;
        if (tso_segs > 1 && !tcp_urg_mode(tp))
            limit = tcp_mss_split_point(sk, skb, mss_now, min_t(unsigned int, cwnd_quota, max_segs), nonagle);
 
        if (skb->len > limit &&
            unlikely(tso_fragment(sk, TCP_FRAG_IN_WRITE_QUEUE, skb, limit, mss_now, gfp)))
            break;
 
        if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
            break;
    }
}

原网站

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