TCP ADI in Linux(7): TCP send

这种重点描述从应用层下发的数据,是在TCP中如何被管理以及被传输的。主要会涉及到MTU,各类TCP层的算法(如Nagle)以及核心的拥塞控制算法和flow control算法(滑动窗口机制)。
另外需要注意的是,这块我看的内核3.10与书中的内核版本实现存在较大不同,看代码的时候需要引起注意。

7.1 TCP segmentation unit for sending data
当应用层调用send发送数据后,内核中对应的函数就是tcp_sendmsg()。因此我们从这个函数切入来理解TCP的发送流程。

SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len, unsigned int, flags) // net/socket.c  
=> sys_sendto(fd, buff, len, flags, NULL, 0)  // send a datagram to a given address, net/socket.c line 1754  
    => sock = sockfd_lookup_light()  // 通过fd找到sock  
    => err = sock_sendmsg(sock, &msg, len)  
        => sock->ops->sendmsg()  == tcp_sendmsg()

tcp_sendmsg()  
    => mss_now = tcp_send_mss()  // 获得current mss
    => sg = !!(sk->sk_route_caps * NETIF_F_SG)  // 检查硬件是否支持scatter-gather
    => 两个循环,第一层遍历所有的buffer块,第二层遍历某一个buffer的所有数据  
        /* 获取sk->sk_write_queue的最后一个skb,用于检查是否用满。
         * 用满了就新建一个skb放新数据,否则将新数据拼接到这最后一个skb中   
         */
        => skb = tcp_write_queue_tail(sk)  
        => if (copy <= 0) // 需要new a segment
            => sk_stream_memory_free(sk)  // 检查send buffer的配额是否超过上限, 超过了要跳转到wait_for_sndbuf  
                => return sk->sk_wmem_queued < sk->sk_sndbuf  
            => skb = sk_stream_alloc_skb() // 为新数据新分配一个skb  
            => skb_entail(sk, skb)    // 将新生成的skb挂到sk->sk_write_queue的尾部  
        => skb_can_coalesce()  // 判断最后一页能否合并更多数据  

        => forced_push(tp) // 解释见接下来的note
        => tcp_mark_push(tp, skb)  // 解释见接下来的note
        /* push out any pending frames which were held back due to TCP_CORK 
         * or attempt at coalescing tiny packets 
         */
        => __tcp_push_pending_frame()  // 如果是设置了PSH flag,会调用该函数尽快的将数据发送出去  

        => if (copied) tcp_push()  //发送数据
            => check sk->sk_send_head is NULL or not // 不为空表示有数据待发送  
            => __tcp_push_pending_frames()  // 大部分数据应该是走这条流程被发送出去的  
                => tcp_write_xmit()  // this rountine write packets to the network  
                    /* 只要有数据pending在write queue里面就继续发送,
                     * 当然循环内部有各种条件判断是否应该终止循环  
                     */
                    => while (skb = tcp_send_head(sk))  
                        => cwnd_quota = tcp_cwnd_test(tp, skb)  // 根据cwnd与packet in flight的差得到配额  
                        => if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)) break;  // 判断是否受限于rwnd  
                        => if (unlikely(!tcp_nagle_test()) break;  // return true if allow by Nagle
                        => if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)) break;  // 发送一个skb,不成功则break。参数1表示传递一份clone  
                            => 细节见后续章节    
                        => tcp_event_new_data_sent(sk, skb)  // 更新sk->sk_send_head,tp->snd_nxt, tp->packet_out等  
                        => if (push_one) break;  // 如果之前只允许发送一个,则break  



tcp_mark_push()为sk_buff设置PSH flag。当满足下面某一个条件时,PSH会被设置:  
    1. we have written more than half of the so far maximum window size from the last byte marked as pushed  
    2. we have one full-sized TCP segment ready for transmission.  

forced_push()就是用来判断上面条件一是否成立的  

tcp_transmit_skb

源代码函数上方的几句注释摘录一下,应该基本知道该函数的功能了。

This routine actually transmits TCP packets queued in by tcp_do_sendmsg().  
This is used by both the initial transmission and possible later retransmissions.  
All SKB's seen here are completely headerless.  

话多不说,直接读代码

tcp_transmit_skb()  
    => skb = skb_clone(skb, gfp_mask)  // 复制一份skb,新的skb不属于任何一个socket  
    => if (tcp_packets_in_flight(tp) == 0) tcp_ca_event(sk, CA_EVENT_TX_START);  // 根据packet inflight判断传输的开始  
    => skb_push(skb, tcp_head_size)  // 根据计算得到的TCP header长度调整skb  
    => th = tcp_hdr(skb)  // 获得TCP header对应位置,并开始构建TCP header
    => tcp_options_write()  // write previously computed TCP options to the packet
    => icsk->icsk_af_ops->send_check(sk, skb) == tcp_v4_send_check()  // compute an IPv4 TCP checksum  
    => if (skb->len != tcp_header_size) tcp_event_data_send(tp, sk)  // congestion state accounting after a packet has been sent  
    => err = icsk->icsk_af_ops->queue_xmit()   ==  ip_queue_xmit() // 目前不关心ip层及以下的实现  

TODO

sock结构体中有一个sk_forward_alloc,需要理解一下这个变量