• TCP协议之《传输队列长度sk_wmem_alloc统计》


    与sk_wmem_queued统计值不同,sk_wmem_alloc表示提交发送的队里所占用空间。


    一、sk_wmem_alloc初始化
    在应用层创建套接口时,内核将新分配的套接口结构的成员变量sk_wmem_alloc初始化为1。

    struct sock *sk_alloc(struct net *net, int family, gfp_t priority, struct proto *prot, int kern)
    {
        struct sock *sk;
     
        sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
        if (sk) {
            refcount_set(&sk->sk_wmem_alloc, 1);
        }
    }
    当克隆一个套接口时,主要是在三次握手完成之后克隆TCP的子套接口的情况下,子套接口的sk_wmem_alloc统计初始化为1。

    struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority)
    {
        newsk = sk_prot_alloc(sk->sk_prot, priority, sk->sk_family);
        if (newsk != NULL) {
            refcount_set(&newsk->sk_wmem_alloc, 1);
        }    
    }

    二、增加sk_wmem_alloc统计
    增加sk_wmem_alloc统计的基础函数为skb_set_owner_w。其能够将数据包skb占用的空间truesize增加到sk_wmem_alloc统计中,同时skb的销毁回调函数destructor赋值为sock_wfree函数,其将在skb销毁时,减去skb缓存长度相应的sk_wmem_alloc统计值。

    void skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
    {
        skb->destructor = sock_wfree;
        /*
         * We used to take a refcount on sk, but following operation
         * is enough to guarantee sk_free() wont free this sock until
         * all in-flight packets are completed
         */
        refcount_add(skb->truesize, &sk->sk_wmem_alloc);
    }

    对于TCP协议栈自身生成的skb缓存,如SYNACK报文发送函数tcp_v4_send_synack,其使用tcp_make_synack分配skb缓存,之后使用skb_set_owner_w增加sk_wmem_alloc统计值。

    struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst, struct request_sock *req, ...)
    {
        skb = alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC);
        switch (synack_type) {
        case TCP_SYNACK_NORMAL:
            skb_set_owner_w(skb, req_to_sk(req));
            break;
        case TCP_SYNACK_COOKIE:
            break;
        case TCP_SYNACK_FASTOPEN:
            skb_set_owner_w(skb, (struct sock *)sk);
            break;
        }
    }
    TCP传输函数tcp_transmit_skb,为数据包添加TCP头部信息,并传递到IP层发送,其将skb缓存的truesize值增加到sk_wmem_alloc统计中。请求tcp_transmit_skb函数执行发送操作的有以下几个类别函数:一类是应用层的数据发送,如函数tcp_write_xmit;一类是TCP控制相关的报文发送,如SYN报文发送函数tcp_connect;一类是TCP数据包重传,如函数tcp_retransmit_skb;最后是TCP主动发送的探测报文,如tcp_send_probe0函数。

    static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask)
    {
        skb_orphan(skb);
        skb->sk = sk;
        skb->destructor = skb_is_tcp_pure_ack(skb) ? __sock_wfree : tcp_wfree;
        refcount_add(skb->truesize, &sk->sk_wmem_alloc);
     
        /* Build TCP header and checksum it. */
        th = (struct tcphdr *)skb->data;
        th->source      = inet->inet_sport;
        th->dest        = inet->inet_dport;
     
        err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
    }

    用户层数据类发送函数。无论是do_tcp_sendpages函数还是tcp_sendmsg_locked函数,都会调用到__tcp_push_pending_frames或者tcp_push_one函数来发送用户层的数据包。这两者最终都是使用tcp_write_xmit进行发送,如下所示,tcp_transmit_skb函数在发送函数前增加sk_wmem_alloc的统计。

    static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, int push_one, gfp_t gfp)
    {
        while ((skb = tcp_send_head(sk))) {
            if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
                break;
        }
    }
    TCP控制类报文发送。此类报文包括发送SYN报文建立TCP连接的tcp_connect函数;fastopen模式下发送SYN+DATA报文的tcp_send_syn_data函数;SYN+ACK报文发送函数tcp_send_synack,与以上的tcp_v4_send_synack函数不同,其仅在TCP握手阶段同时打开连接(同时发送SYN报文)的情况下,负责回复对端的的SYN+ACK报文;发送reset重置报文的tcp_send_active_reset函数;以及ACK回复函数tcp_send_ack。

    如下tcp_connect函数所示,在分配skb缓存之后,函数tcp_connect_queue_skb将增加套接口的发送队列统计值sk_wmem_queued,因为马上就要执行发送操作,所以并没有将此skb添加到套接口发送队列中sk_write_queue。在随后的tcp_rbtree_insert函数中将其添加到套接口重传队列tcp_rtc_queue。到了tcp_transmit_skb函数中,内核将再增加sk_wmem_alloc的统计值。

    int tcp_connect(struct sock *sk)
    {
        struct sk_buff *buff;
        buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
     
        tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
        tcp_connect_queue_skb(sk, buff);
        tcp_rbtree_insert(&sk->tcp_rtx_queue, buff);
     
        /* Send off SYN; include data in Fast Open. */
        err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) : tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
    }
    TCP重传类报文发送。函数tcp_retransmit_skb负责数据包发送超时重传,在如下的超时处理函数tcp_retransmit_timer中被调用,重传tcp_rtx_queue队列头的skb数据包。其封装了__tcp_retransmit_skb函数。

    void tcp_retransmit_timer(struct sock *sk)
    {
        if (tcp_retransmit_skb(sk, tcp_rtx_queue_head(sk), 1) > 0) {
            inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL), TCP_RTO_MAX);
            goto out;
        }
    }
    int __tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs)
    {
        if (unlikely((NET_IP_ALIGN && ((unsigned long)skb->data & 3)) || skb_headroom(skb) >= 0xFFFF)) {
        } else {
            err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
        }
    }
    TCP探测类报文发送。包括PMTU探测报文函数tcp_mtu_probe和窗口探测函数tcp_send_probe0和TCP的REPAIR模式使用的tcp_send_window_probe函数。如下为tcp_send_probe0使用的tcp_write_wakeup函数,其调用tcp_transmit_skb发送数据包,其中将增加sk_wmem_alloc统计值。

    int tcp_write_wakeup(struct sock *sk, int mib)
    {
        skb = tcp_send_head(sk);
        if (skb && before(TCP_SKB_CB(skb)->seq, tcp_wnd_end(tp))) {
            err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
        } else {
            if (between(tp->snd_up, tp->snd_una + 1, tp->snd_una + 0xFFFF))
                tcp_xmit_probe_skb(sk, 1, mib);
            return tcp_xmit_probe_skb(sk, 0, mib);
        }
    }

    三、减少sk_wmem_alloc统计
    TCP套接口的释放函数sk_free将sk_wmem_alloc统计值减少1,对应于套接口创建时初始化为1的操作。对于使用skb_set_owner_w增加了sk_wmem_alloc统计值的skb缓存,由函数sock_wfree减少对应的统计值。

    void sk_free(struct sock *sk)
    {
        if (refcount_dec_and_test(&sk->sk_wmem_alloc))
            __sk_free(sk);
    }
    void sock_wfree(struct sk_buff *skb)
    {
        struct sock *sk = skb->sk;
        unsigned int len = skb->truesize;
     
        if (!sock_flag(sk, SOCK_USE_WRITE_QUEUE)) {
            WARN_ON(refcount_sub_and_test(len - 1, &sk->sk_wmem_alloc));
            sk->sk_write_space(sk);
            len = 1;
        }
        if (refcount_sub_and_test(len, &sk->sk_wmem_alloc))
            __sk_free(sk);
    }
    void skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
    {
        skb->destructor = sock_wfree;
    }


    其它的TCP相关skb缓存使用tcp_wfree函数作为销毁回调函数,其中将减低sk_wmem_alloc统计值,对于单独的TCP ACK报文,内核将其销毁回调函数destructor设定为__sock_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;
    }
    void __sock_wfree(struct sk_buff *skb)
    {
        struct sock *sk = skb->sk;
        if (refcount_sub_and_test(skb->truesize, &sk->sk_wmem_alloc))
            __sk_free(sk);
    }

    四、判断控制sk_wmem_alloc统计
    在TCP重传过程中,如果sk_wmem_alloc统计值大于套接口设定的发送缓存最大长度sk_sndbuf,或者大于发送队列所占空间的1.25倍,说明当前发送的数据已大于发送缓存队列中的数据,返回错误-EAGAIN。其中发送队列的四分之一(0.25)考虑了分片、隧道等的空间占用。

    int __tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs)
    {
        struct inet_connection_sock *icsk = inet_csk(sk);
        struct tcp_sock *tp = tcp_sk(sk);
     
        if (refcount_read(&sk->sk_wmem_alloc) > min_t(u32, sk->sk_wmem_queued + (sk->sk_wmem_queued >> 2), sk->sk_sndbuf))
            return -EAGAIN;
    }
    TCP的带宽估算模块需要对由于应用层延迟导致的无数据包发送进行标记,如下函数tcp_rate_check_app_limited,套接口的sk_wmem_alloc统计值小于SKB_TRUESIZE(1)表明在主机的Qdisc队列或者网卡的发送队列中没有数据包。

    void tcp_rate_check_app_limited(struct sock *sk)
    {
        struct tcp_sock *tp = tcp_sk(sk);
     
        if (tp->write_seq - tp->snd_nxt < tp->mss_cache &&
            /* Nothing in sending host's qdisc queues or NIC tx queue. */
            sk_wmem_alloc_get(sk) < SKB_TRUESIZE(1) &&
            tcp_packets_in_flight(tp) < tp->snd_cwnd && tp->lost_out <= tp->retrans_out)
            tp->app_limited = (tp->delivered + tcp_packets_in_flight(tp)) ? : 1;
    }
    另外,TCP的autocork功能,在判读是否需要使能的时候,需要判断套接口sk_wmem_alloc统计值是否大于当前skb缓存的truesize值,意味着如果有数据在发送,发送完成中断马上要发生,正是将数据包合并在一起的时机。

    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;
    }

  • 相关阅读:
    Mysql Innodb存储引擎的REPEATABLE-READ隔离级别到底有没有彻底解决幻读问题
    面试常见问题丨深入解析Spring中Bean的整个初始化过程
    用于生物分子修饰的Alkyne NHS ester,906564-59-8
    了解代理模式
    CSP-J1 CSP-S1 第1轮 初赛 考前强化训练
    Windows未正确加载怎么办?
    Nginx 优化
    R语言:读取loom文件,以及loom文件转成Seurat对象
    HTTP/1.1协议中的响应报文
    nvm 安装 node 安装不上 npm
  • 原文地址:https://blog.csdn.net/wuyongmao/article/details/126266239