天天看點

TCP資料發送接口

1.1 tcp_sendmsg

使用 TCP 發送資料的大部分工作都是在tcp_sendmsg函數中實作的。

int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *skb;
    struct sockcm_cookie sockc;
    int flags, err, copied = 0;
    int mss_now = 0, size_goal, copied_syn = 0;
    bool process_backlog = false;
    bool sg;
    long timeo;

    /*
     * 在發送和接收TCP資料前都要對傳輸控制塊上鎖,以免
     * 應用程式主動發送接收和傳輸控制塊被動接收而導緻
     * 控制塊中的發送或接收隊列混亂。
     */
    lock_sock(sk);

    flags = msg->msg_flags;
    if (flags & MSG_FASTOPEN) {
        err = tcp_sendmsg_fastopen(sk, msg, &copied_syn, size);
        if (err == -EINPROGRESS && copied_syn > 0)
            goto out;
        else if (err)
            goto out_err;
    }
/*
     * 擷取發送資料是否進行阻塞辨別,如果阻塞,則通過
     * sock_sndtimeo()擷取阻塞逾時時間。發送阻塞逾時時間儲存
     * 在sock結構的sk_sndtimeo成員中。
     */
    timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);skb_shinfo

    /* Wait for a connection to finish. One exception is TCP Fast Open
     * (passive side) where data is allowed to be sent before a connection
     * is fully established.
     */
      /* 
        * sk_state的值在tcp_states.h中定義,使用的是上面的
        * TCP_ESTABLISHED所在的枚舉中的值,而不是TCPF_ESTABLISHED
        * 所在的枚舉。上下兩個枚舉的關系是:
        * TCPF_xxx = 1<<TCP_xxx。TCPF_ESTABLISHED所在的枚舉
        * 隻是用來驗證sk->sk_state中的狀态是什麼,通過
        * 位運算可以同時驗證多個,減少比較的次數。
        * 這裡或許是為了相容以前的作法,或許是
        * 協定規定,否則可以将TCP_xxx直接使用TCPF_xxx的形式即可
        */
    /*
     * TCP隻在ESTABLISHED或CLOSE_WAIT這兩種狀态下,接收視窗
     * 是打開的,才能接收資料。是以如果不處于這兩種
     * 狀态,則調用sk_stream_wait_connect()等待建立起連接配接,一旦
     * 逾時則跳轉到out_err處做出錯處理。
     */
    if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) &&
        !tcp_passive_fastopen(sk)) {
        err = sk_stream_wait_connect(sk, &timeo);kmem_cache_alloc_node
        if (err != 0)
            goto do_error;
    }

    if (unlikely(tp->repair)) {
        if (tp->repair_queue == TCP_RECV_QUEUE) {
            copied = tcp_send_rcvq(sk, msg, size);
            goto out_nopush;
        }

        err = -EINVAL;
        if (tp->repair_queue == TCP_NO_QUEUE)
            goto out_err;

        /* 'common' sending to sendq */
    }

    sockc.tsflags = sk->sk_tsflags;
    if (msg->msg_controllen) {
        err = sock_cmsg_send(sk, msg, &sockc);
        if (unlikely(err)) {
            err = -EINVAL;
            goto out_err;
        }
    }

    /* This should be in poll */
    sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);

    /* Ok commence sending. */
    copied = 0;

restart:
    /*
     * 調用tcp_send_mss()擷取目前有效MSS,tcp_current_mss()中MSG_OOB是判斷是否支援GSO的條件之一,而
     * 帶外資料不支援GSO。
     * 這裡除了擷取目前的MSS外,還會擷取目标發送的資料
     * size_goal存儲的是TCP分段中資料部分的最大長度,如果網卡不支援TSO,
     * 其值和MSS是相等的;如果網卡支援TSO,其值要綜合考慮網卡支援
     * 的最大分段大小及接收方通告的最大視窗等,參見tcp_xmit_size_goal().
     *
     擷取 MSS 大小。 size_goal 是資料報到達網絡裝置時所允許的最大長度。
     * 對于不支援分片的網卡, size_goal 是 MSS 的大小。否則,是 MSS 的整倍數。
     */
    mss_now = tcp_send_mss(sk, &size_goal, flags);

    err = -EPIPE;
    /*在使用shutdown關閉了發送之後,再次調用tcp_sendmsg發送資料
    那麼該函數會傳回錯誤;*/
    if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
        goto out_err;

    sg = !!(sk->sk_route_caps & NETIF_F_SG);//是否支援 SG(scatter/gather)聚合分散IO
 /* 周遊發送緩存 */
    while (msg_data_left(msg)) {
        int copy = 0;
        int max = size_goal;
    
  /* 拿到發送隊列的尾部skb,因為隻有隊尾的skb才有空間存儲資料 */
        skb = tcp_write_queue_tail(sk);
        if (tcp_send_head(sk)) {//判斷發送隊列是否為空,如果為空則目前擷取skb無效
            if (skb->ip_summed == CHECKSUM_NONE)
                max = mss_now;
            copy = max - skb->len;// 如果skb已經使用空間還沒有達到size_goal 表明還可以往skb裡面指派資料
        }
/* 剩餘空間為0,或者不能合并,配置設定一個新的skb */
        if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
            //說明sk發送隊列的最後一個skb已經沒有多餘的空間了,則需要從新開辟skb空間
new_segment:
            /* Allocate new segment. If the interface is SG,
             * allocate skb fitting to single page.
             *//* 空閑記憶體不足,進入等待 */
            if (!sk_stream_memory_free(sk))
                goto wait_for_sndbuf;

            if (process_backlog && sk_flush_backlog(sk)) {
                process_backlog = false;
                goto restart;
            }//注意這裡面掉的是alloc_skb_fclone
            skb = sk_stream_alloc_skb(sk,
                          select_size(sk, sg),
                          sk->sk_allocation,
                          skb_queue_empty(&sk->sk_write_queue));
            if (!skb)//如果支援SG(NETIF_F_SG),則無需線性緩沖區,所有資料直接存到shinfo頁中
                goto wait_for_memory;

            process_backlog = true;
            /*
             * Check whether we can use HW checksum.
             *//* 網卡允許計算校驗和 */
            if (sk_check_csum_caps(sk))
                skb->ip_summed = CHECKSUM_PARTIAL;
 /* 添加到發送隊列 */
            skb_entail(sk, skb);
            /*
                 * 初始化copy變量為發送資料包到網絡
                 * 裝置時最大資料段長度。copy表示每
                 * 次複制到SKB的資料長度。
                 */
            copy = size_goal;
            max = size_goal;

            /* All packets are restored as if they have
             * already been sent. skb_mstamp isn't set to
             * avoid wrong rtt estimation.
             */
            if (tp->repair)
                TCP_SKB_CB(skb)->sacked |= TCPCB_REPAIRED;
        }

        /* Try to append data to the end of skb. */
        //從應用層發送的資料中拷貝複制資料到skb的時候,最多隻能拷貝實際資料大小
        if (copy > msg_data_left(msg))
            copy = msg_data_left(msg);

        /* Where to copy to? *//* 線性區域還有空間 */
        if (skb_availroom(skb) > 0) {//說明線性緩沖區中還有資料
            /* We have some space in skb head. Superb! */
             /* 取要拷貝的數量和線性區域的較小值 */
            copy = min_t(int, copy, skb_availroom(skb));//先把tail skb中的剩餘空間填上,但最多隻能填剩餘的空間大小
 /* 從使用者空間拷貝到核心 */
            err = skb_add_data_nocache(sk, skb, &msg->msg_iter, copy);
            //把應用層發送來的資料線填充一部分到tail skb中
            if (err)
                goto do_fault;
        } else { //如果線性緩沖區已經滿了,則需要把資料拷貝到shinfo裡面
        /* 線性區域沒有空間,則使用分頁區 */
            bool merge = true;//判斷最後一個分頁是否能追加資料  1可以  0不可以
             /* 擷取頁數量 */
            int i = skb_shinfo(skb)->nr_frags;
            /*擷取目前SKB的分片段數,在skb_shared_info中用nr_frags表示。*/
             /* 擷取緩存的頁 */
            struct page_frag *pfrag = sk_page_frag(sk);//擷取最後一個分片的頁面page 資訊;第一次使用的時候這裡一般為NULL
             /*如果SKB線性存儲區底部已經沒有空間了,那就需要把資料複制到支援分散聚合的分頁中*/
                /*merge辨別是否在最後一個分頁中添加資料,初始化為0*/
                //可以參考http://blog.chinaunix.net/uid-23629988-id-196823.html  Scatter/Gather I/O在L3中的應用 

              /* 檢查是否有足夠的空間,空間不足則申請新頁,失敗則等待 */
            if (!sk_page_frag_refill(sk, pfrag))
                goto wait_for_memory;
                /* 判斷能否在最後一個分片加資料。 */
            if (!skb_can_coalesce(skb, i, pfrag->page,
                          pfrag->offset)) {//該頁還沒寫滿,可以繼續忘該也寫。如果是後面兩種else則需要從新配置設定page頁
                if (i == sysctl_max_skb_frags || !sg) {//這個數和skb_shared_info->的frags[]對應
                /* 無法設定配置設定,那麼就重新配置設定一個 SKB。
                沒設定NETIF_F_SG就隻能線性化處理;
                nr_frags的值,該字段确定了分段數,這些分散的片段以關聯的方式存儲在frags數組中
                */
                    tcp_mark_push(tp, skb);//不支援 sg,則設定pushed_seq 成員表示希望盡快發送出去 
                    goto new_segment;
                }
                merge = false;
            }

            copy = min_t(int, copy, pfrag->size - pfrag->offset);
  /*
                 * 在複制資料之前,還需判斷用于輸出使用的緩存
                 * 是否達到上限,一旦達到則隻能等待,直到有可用
                 * 輸出緩存或逾時為止.
                 */
            if (!sk_wmem_schedule(sk, copy))
                goto wait_for_memory;
  /*
                 * 這時,SKB 的分頁已準備好,無論是原先存在還是剛剛配置設定,
                 * 接下來就調用skb_copy_to_page_nocache()将資料複制到分頁中.
                 */
            err = skb_copy_to_page_nocache(sk, &msg->msg_iter, skb,
                               pfrag->page,
                               pfrag->offset,
                               copy);
            if (err)
                goto do_error;

            /* Update the skb. */
            /*
                 * 完成複制資料到一個分頁,這時需要更新有關分段的
                 * 資訊.如果是在最後一個頁面分段中追加的,則需更新
                 * 該頁面内有效資料的長度.
                 */
            if (merge) {//merge為1表示之前已經有資料存在到該分頁中了
                skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
            } else {
            //為0表示剛建立的page頁,第一次寫資料到該頁中
                    /*
                     * 如果是複制到一個新的頁面分段中,則需更新的有關
                     * 分段的資訊就會多一些,如分段資料的長度、頁内偏移、
                     * 分段數量等,這由skb_fill_page_desc()來完成。如果辨別最近
                     * 一次配置設定頁面的sk_sndmsg_page不為空,則增加對該頁面的
                     * 引用;否則說明複制了資料的頁面時新配置設定的,且沒有
                     * 使用完,在增加對該頁面的引用的同時,還需要更新
                     * sk_sndmsg_page的值。如果新配置設定的頁面已使用完,就無需
                     * 更新sk_sndmsg_page的值了,因為如果SKB未超過段上限,那麼
                     * 下次必定還會配置設定新的頁面,是以在此就省去了對off+copy==PAGE_SIZE
                     * 這條分支的處理
                     */
                skb_fill_page_desc(skb, i, pfrag->page,
                           pfrag->offset, copy);
                get_page(pfrag->page);
            } /*
                     * 複制了新資料,需更新資料尾端在最後一頁
                     * 分片的頁内偏移.
                     */
                      //記錄下該頁已經寫了資料的記憶體的頁偏移的地方,下次緊跟後面寫
            pfrag->offset += copy;
        }
 /*
             * 如果複制的資料長度為零,則取消TCPCB_FLAG_PSH标志.
             */
        if (!copied)
            TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_PSH;
 /*
             * 更新發送隊列中的最後一個序号write_seq,以及每個資料包的
             * 最後一個序列end_seq,初始化gso分段數gso_segs.
             */
        tp->write_seq += copy;/* 更新 TCP 的序号 */
        TCP_SKB_CB(skb)->end_seq += copy;
        tcp_skb_pcount_set(skb, 0);

        copied += copy;//已經拷貝的總位元組數加上最新拷貝的copy位元組。
        if (!msg_data_left(msg)) {
            tcp_tx_timestamp(sk, sockc.tsflags, skb);
            if (unlikely(flags & MSG_EOR))
                TCP_SKB_CB(skb)->eor = 1;
            goto out;
        }

        if (skb->len < max || (flags & MSG_OOB) || unlikely(tp->repair)){
            //說明這個skb還沒達到mss,可以繼續向其中拷貝下一個iovec中的資料進來
            continue;
        }
        /* 檢查該資料是否必須立即發送。 */
        if (forced_push(tp)) { /* 需要使用push标記 */
             /*
                 * 檢查是否必須立即發送,即檢查自上次發送後
                 * 産生的資料是否已超過對方曾經通告過的最
                 * 大通告視窗值的一半.如果必須立即發送,則設定
                 * PSH标志後調用__tcp_push_pending_frames()将在發送隊列
                 * 上的SKB從sk_send_head開始發送出去.
                 * __tcp_push_pending_frames()将發送隊列上的段發送出去.如果
                 * 發送失敗,則會檢測是否需要激活持續定時器.實際上,
                 * 很多處理都是在tcp_write_xmit()中進行的,frames()隻是在判斷
                 * 是否有段需要發送時簡單地調用tcp_write_xmit()發送段,如果
                 * 發送失敗,再調用tcp_check_probe_timer()複位持續探測定時器.
                 */
            tcp_mark_push(tp, skb); /* 打psh标記 */
            __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);/* 發送隊列中的多個skb */
        } else if (skb == tcp_send_head(sk))
            tcp_push_one(sk, mss_now); /* 否則,有資料要發送,則發送一個skb */
        /*
             * 如果沒有必要立即發送,且發送隊列上隻存在這個段,則
             * 調用tcp_push_one()隻發送目前段.
             */
        continue;

wait_for_sndbuf:/* 設定空間不足标記 */
    /* 設定目前的狀态為無空間狀态,并等待記憶體空間。 */
        set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
        if (copied)/* 已經有拷貝資料到發送隊列,則發送之 */
            tcp_push(sk, flags & ~MSG_MORE, mss_now,
                 TCP_NAGLE_PUSH, size_goal);

        err = sk_stream_wait_memory(sk, &timeo);
        if (err != 0)
            goto do_error;

        mss_now = tcp_send_mss(sk, &size_goal, flags);
    }
//拷貝完成或者空間不足從do_error走到這裡
out:
    if (copied)//應用層資料拷貝完成或者發送隊列中的buffer空間達到sk_sndbuf時,傳回拷貝成功的位元組數。
    //是以這裡也說明了應用層send或者write資料的時候并不會一次write或者send完,如果資料包過大需要多次發送
        tcp_push(sk, flags, mss_now, tp->nonagle, size_goal);
out_nopush:
    release_sock(sk);/* 将 sk 釋放,并傳回已經發出的資料量。 */
    return copied + copied_syn;

do_fault:
    if (!skb->len) {
        /* 當拷貝資料發送異常是,會進入這個分支。如果目前的 SKB 是新配置設定的,
        * 那麼,将該 SKB 從發送隊列中去除,并釋放該 SKB。*/
        tcp_unlink_write_queue(skb, sk);
        /* It is the one place in all of TCP, except connection
         * reset, where we can be unlinking the send_head.
         */
        tcp_check_send_head(sk, skb);
        sk_wmem_free_skb(sk, skb);
    }

do_error:
    if (copied + copied_syn)/* 如果已經拷貝了資料,那麼,就将其發出。 */
        goto out;
out_err:
    err = sk_stream_error(sk, flags, err);/* 擷取并傳回錯誤碼,釋放鎖。 */
    /* make sure we wake any epoll edge trigger waiter */
    if (unlikely(skb_queue_len(&sk->sk_write_queue) == 0 && err == -EAGAIN))
        sk->sk_write_space(sk);
    release_sock(sk);
    return err;
}      

TCP Push 操作

  TCP 協定提供了 PUSH 功能,隻要添加了該标志位, TCP 層會盡快地将資料發送出去。以往多用于傳輸程式的控制指令。在目前的多數 TCP 實作中,使用者往往不會自行指定 PUSH。 TCP 的實作會根據情況自行指定 PUSH 位。 既然目前的 TCP 實作多數都會自行指定 PUSH 标志位,那麼究竟在什麼情況下, 資料包會被設定 PUSH 标志位呢? forced_push函數給出了必須設定 PUSH 标志位的 一種情況。

static inline bool forced_push(const struct tcp_sock *tp)
2 {
3 /* 當上一次被 PUSH 出去的包的序号和目前的序号
4 * 相差超過視窗的一半時,會強行被 PUSH。
5 */
6 return after(tp->write_seq, tp->pushed_seq + (tp->max_window >> 1));
7 }      

  從這裡可以看出,在 Linux 中,如果緩存的資料量超過了視窗大小的一半以上,就會被盡快發送出去

tcp_sendmsg_fastopen

/*
tcp_sendmsg_fastopen建立tp->fastopen_req, 請求上下文
調用__inet_stream_connect()->tcp_v4_connect()->tcp_connect()進入正常的connect邏輯, 先生成一個不帶資料的syn包放入sk_write_queue重傳隊列
調用tcp_send_syn_data發送syn+data的TFO包
先從tcp metric中查找目标位址的fast open cookie, 如果找到也就是fo->cookie.len>0, 則會放倒tcp選項中發送; fo->cookie.len=0則會發送fastopen請求選項; <0則使用原來的方式,不帶資料
在tcp_send_syn_data中,配置設定一個新的skb,并把使用者态資料copy到這個新的skb中,最多隻能發送一個MSS大小,最終會傳回發送的資料量給調用者
通過tcp_transmit_skb發送這個帶資料的skb, 調用tcp_syn_options和tcp_options_write設定選項
再把這個新的包含資料的skb放入重傳隊列sk_write_queue, 實其seq+1,并去掉syn選項, 這樣重傳隊列中就包含了syn包和data包兩個
*/
static int tcp_sendmsg_fastopen(struct sock *sk, struct msghdr *msg,
                int *copied, size_t size)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int err, flags;
        /* 如果沒有開啟該功能,傳回錯誤值 */
    if (!(sysctl_tcp_fastopen & TFO_CLIENT_ENABLE))
        return -EOPNOTSUPP;
    if (tp->fastopen_req)/* 如果已經有要發送的資料了,傳回錯誤值 */
        return -EALREADY; /* Another Fast Open is in progress */

    tp->fastopen_req = kzalloc(sizeof(struct tcp_fastopen_request),
                   sk->sk_allocation);/* 配置設定空間并将使用者資料塊指派給相應字段 */
    if (unlikely(!tp->fastopen_req))
        return -ENOBUFS;
    tp->fastopen_req->data = msg;
    tp->fastopen_req->size = size;

    flags = (msg->msg_flags & MSG_DONTWAIT) ? O_NONBLOCK : 0;
    /* 由于 fast open 時,連接配接還未建立,是以,這裡直接調用了下面的
    * 函數建立連接配接。這樣資料就可以在連接配接建立的過程中被發送出去了。
     */
    err = __inet_stream_connect(sk->sk_socket, msg->msg_name,
                    msg->msg_namelen, flags);
    *copied = tp->fastopen_req->copied;
    tcp_free_fastopen_req(tp);
    return err;
}      

輸出到 IP 層 ---->tcp_write_xmit

/* This routine writes packets to the network.  It advances the
 * send_head.  This happens as incoming acks open up the remote
 * window for us.
 *
 * LARGESEND note: !tcp_urg_mode is overkill, only frames between
 * snd_up-64k-mss .. snd_up cannot be large. However, taking into
 * account rare use of URG, this is not a big flaw.
 *
 * Send at most one packet when push_one > 0. Temporarily ignore
 * cwnd limit to force at most one packet out when push_one == 2.

 * Returns true, if no segments are in flight and we have queued segments,
 * but cannot send anything now because of SWS or another problem.
 */
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
               int push_one, gfp_t gfp)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *skb;
    unsigned int tso_segs, sent_pkts;
    int cwnd_quota;
    int result;
    bool is_cwnd_limited = false;
    u32 max_segs;
    sent_pkts = 0;

    if (!push_one) {
        /* Do MTU probing. *//* 進行 MTU 探測 */
        result = tcp_mtu_probe(sk);
        if (!result) {
            return false;
        } else if (result > 0) {
            sent_pkts = 1;
        }
    }
        /* 擷取最大的段數 */
    max_segs = tcp_tso_autosize(sk, mss_now);
    while ((skb = tcp_send_head(sk))) {/* 不斷循環發送隊列,進行發送 */
        unsigned int limit;
/*
         * 設定有關tso的資訊,包括GSO類型、GSO分段的大小等。這些
         * 資訊是準備給軟體TSO分段使用的。如果網絡裝置不支援TSO,
         * 但又使用了TSO功能,則段在送出給網絡裝置之前,需進行
         * 軟分段,即由代碼實作TSO分段。用MSS初始化skb中的gso字段,傳回本skb将會被分割成幾個TSO段傳輸
         */
        tso_segs = tcp_init_tso_segs(skb, mss_now);
        BUG_ON(!tso_segs);
 /* 
               * 檢查目前是否可以發送資料,
               * 确認目前發送視窗的大小
               */
        /*
         * 檢測擁塞視窗的大小,如果為0,則說明
         * 擁塞視窗已滿,目前不能發送。
         */
        if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) {
            /* "skb_mstamp" is used as a start point for the retransmit timer */
            skb_mstamp_get(&skb->skb_mstamp);
            goto repair; /* Skip network transmission */
        }

        cwnd_quota = tcp_cwnd_test(tp, skb);
        if (!cwnd_quota) {
            if (push_one == 2)
                /* Force out a loss probe pkt. */
                cwnd_quota = 1;/* 強制發送一個包進行丢包探測。 */
            else
                break;/* 如果視窗大小為 0,則無法發送任何東西。 */
        }
/*
         * 檢測目前段(包括線性區和分散聚合I/O區shinfo)是否完全處在發送視窗内,如果是
         * 則可以發送,否則目前不能發送。--->>檢測發送視窗是否至少允許發送skb中的一個的段。如果不允許,結束發送過程
         */
        if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
            break;

        if (tso_segs == 1) {
            /*//tso_segs為1,說明skb隻有一個段,而且長度可能小于MSS,即是一個小資料包;是以需要檢測nagle算法是否允許發送該skb
             * 如果無需TSO分段,則檢測是否使用Nagle算法,
             * 并确定目前能否立即發送該段。
             */
            if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
                             (tcp_skb_is_last(sk, skb) ?
                              nonagle : TCP_NAGLE_PUSH))))
                break;
        } else {
        /*
             * 如果需要TSO分段,則檢測該段是否應該延時發送,
             * 如果是則目前不能發送。tcp_tso_should_defer()用來檢測
             * GSO段是否需要延時發送。在段中有FIN标志,或者
             * 不處于Open擁塞狀态,或者TSO段延時超過2個時鐘
             * 滴答,或者擁塞視窗和發送視窗的最小值大于64KB
             * 或三倍的目前有效MSS,在這些情況下會立即發送,
             * 而其他情況下會延時發送,這樣主要為了減少軟GSO
             * 分段的次數提高性能。
             */
            if (!push_one &&
                tcp_tso_should_defer(sk, skb, &is_cwnd_limited,
                         max_segs))
                break;
        }

        limit = mss_now;/* 通過上面的擁塞視窗和發送視窗的檢測後,我們知道,目前至少是可以發送一個TCP段的。
當然也有可能還可以發送更多,是以需要根據條件調整limit
根據分段對于包進行分段處理。 如果skb有多個段,需要檢查到底可以發送多少資料*/
        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);
/* skb中資料段長度>分段長度限制,則進行分段,會申請新的skb 
skb的資料量超過了限定值,需要分段。這種情況隻可能發生在TSO情形,因為非TSO場景,skb的長度是不可能超過MSS的。此外,這種分段完全是因為擁塞控制和流量控制算法限制了發包大小
是以才需要分割,和TSO本身沒有任何關系
*/
        if (skb->len > limit &&/* 如果長度超過了分段限制,那麼調用 tso_fragment 進行分段。 */
            unlikely(tso_fragment(sk, skb, limit, mss_now, gfp)))
            break;

        /* TCP Small Queues :
         * Control number of packets in qdisc/devices to two packets / or ~1 ms.
         * This allows for :  控制進入到 qdisc/devices 中的包的數目
         * 該機制帶來了以下的好處 :
             * - 更好的 RTT 估算和 ACK 排程
             * - 更快地恢複
             * - 高資料率
         *  - better RTT estimation and ACK scheduling
         *  - faster recovery
         *  - high rates
         * Alas, some drivers / subsystems require a fair amount
         * of queued bytes to ensure line rate.
         * One example is wifi aggregation (802.11 AMPDU)
         */
         /*
         * 根據條件,可能需要對SKB中的段進行分段處理,分段的
         * 段包括兩種:一種是普通的用MSS分段的段,另一種則是
         * TSO分段的段。能否發送段主要取決于兩個條件:一是段
         * 需完全在發送視窗中,二是擁塞視窗未滿。第一種段,
         * 應該不會再分段了,因為在tcp_sendmsg()中建立段的SKB時已經
         * 根據MSS處理了。而第二種段,則一般情況下都會大于MSS,
         * 是以通過TSO分段的段有可能大于擁塞視窗剩餘空間,如果
         * 是這樣,就需以發送視窗和擁塞視窗的最小值作為段長對
         * 資料包再次分段。
         */
         
        /*
         * limit為再次分段的段長,初始化為目前MSS。
         */
        limit = max(2 * skb->truesize, sk->sk_pacing_rate >> 10);
        limit = min_t(u32, limit, sysctl_tcp_limit_output_bytes);

        if (atomic_read(&sk->sk_wmem_alloc) > limit) {
            set_bit(TSQ_THROTTLED, &tp->tsq_flags);
            /* It is possible TX completion already happened
             * before we set TSQ_THROTTLED, so we must
             * test again the condition.
             */
            smp_mb__after_atomic();
            if (atomic_read(&sk->sk_wmem_alloc) > limit)
                break;
        }
 /* 
               * 使用位址族相關的af_sepcific->queue_xmit函數,
               * 将資料轉發到網絡層。IPv4使用的是
               * ip_queue_xmit
               */
        if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
            break;

repair:
        /* Advance the send_head.  This one is sent out.
         * This call will increment packets_out.
         */
         /* 
         * 處理對統計量的更新。更重要的是,
         * 它會初始化所發送TCP資訊段的重傳
         * 定時器。不必對每個TCP分組都這樣做,
         * 該機制隻用于已經确認的資料區之後的
         * 第一個分組
               */
        tcp_event_new_data_sent(sk, skb);
/*
         * 如果發送的段小于MSS,則更新最近發送的小包的
         * 最後一個位元組序号。
         *//*
         * 更新在函數中已發送的總段數。
         */
        tcp_minshall_update(tp, mss_now, skb);
        sent_pkts += tcp_skb_pcount(skb);

        if (push_one)
            break;
    }

    if (likely(sent_pkts)) {/* 如果發送了資料,那麼就更新相關的統計。 */
        /*
     * 如果本次有資料發送,則對TCP擁塞視窗進行确認,
     * 最後傳回成功。
     */
        if (tcp_in_cwnd_reduction(sk))
            tp->prr_out += sent_pkts;

        /* Send one loss probe per tail loss episode. */
        if (push_one != 2)
            tcp_schedule_loss_probe(sk);
        is_cwnd_limited |= (tcp_packets_in_flight(tp) >= tp->snd_cwnd);
        tcp_cwnd_validate(sk, is_cwnd_limited);
        return false;
    }
    /*
     * 如果本次沒有資料發送,則根據已發送但未确認的段數packets_out和
     * sk_send_head傳回,packets_out不為零或sk_send_head為空都被視為有資料發出,
     * 是以傳回成功。
     */
    return !tp->packets_out && tcp_send_head(sk);
}      
/* Initialize TSO state of a skb.
 * This must be invoked the first time we consider transmitting
 * SKB onto the wire.
 */
static int tcp_init_tso_segs(struct sk_buff *skb, unsigned int mss_now)
{
    int tso_segs = tcp_skb_pcount(skb);
    //cond1: tso_segs為0表示該skb的GSO資訊還沒有被初始化過
        //cond2: MSS發生了變化,需要重新計算GSO資訊

    if (!tso_segs || (tso_segs > 1 && tcp_skb_mss(skb) != mss_now)) {
        tcp_set_skb_tso_segs(skb, mss_now);
        tso_segs = tcp_skb_pcount(skb);
    }
    return tso_segs;
}      

 tcp_init_tso_segs

/* Initialize TSO segments for a packet. */
static void tcp_set_skb_tso_segs(struct sk_buff *skb, unsigned int mss_now)
{
    if (skb->len <= mss_now || skb->ip_summed == CHECKSUM_NONE) {
        /* Avoid the costly divide in the normal
         * non-TSO case.
         *///如果該skb資料量不足一個MSS,或者根本就不支援GSO,那麼就是一個段
        tcp_skb_pcount_set(skb, 1);
        TCP_SKB_CB(skb)->tcp_gso_size = 0;
    } else {
        //計算要切割的段數,就是skb->len除以MSS,結果向上取整
        tcp_skb_pcount_set(skb, DIV_ROUND_UP(skb->len, mss_now));
        TCP_SKB_CB(skb)->tcp_gso_size = mss_now;
    }
}      
/* Due to TSO, an SKB can be composed of multiple actual
 * packets.  To keep these tracked properly, we use this.
 */
static inline int tcp_skb_pcount(const struct sk_buff *skb)
{
    return TCP_SKB_CB(skb)->tcp_gso_segs;
    //gso_segs記錄了網卡在傳輸目前skb時應該将其分割成多少個包進行
}      
/* This is valid iff skb is in write queue and tcp_skb_pcount() > 1. */
static inline int tcp_skb_mss(const struct sk_buff *skb)
{
    return TCP_SKB_CB(skb)->tcp_gso_size;
    //gso_size記錄了該skb應該按照多大的段被切割,即上次的MSS
}      

 tcp_cwnd_test

/* Can at least one segment of SKB be sent right now, according to the
 * congestion window rules?  If so, return how many segments are allowed.
 */
static inline unsigned int tcp_cwnd_test(const struct tcp_sock *tp,
                     const struct sk_buff *skb)
{
    u32 in_flight, cwnd, halfcwnd;

    /* Don't be strict about the congestion window for the final FIN. 
    //如果是FIN段,并且隻有一個段(FIN有可能會攜帶很多資料),
    那麼總是可以發送,不會被擁塞視窗限制*/
    if ((TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) &&
        tcp_skb_pcount(skb) == 1)
        return 1;
        //估算目前還在網絡中傳輸的TCP段的數目
    in_flight = tcp_packets_in_flight(tp);
    cwnd = tp->snd_cwnd;//snd_cwnd就是目前擁塞視窗的大小,以TCP段為機關
    if (in_flight >= cwnd)
        return 0;//擁塞視窗已經好耗盡,傳回0表示不允許發送資料

    /* For better scheduling, ensure we have at least
     * 2 GSO packets in flight.
     */
    halfcwnd = max(cwnd >> 1, 1U);
    //比較擁塞視窗大小和飛行封包數目,餘量就是擁塞控制還允許發送的段數
    //但是為了更好的排程,需要保證至少兩個2GSO
    return min(halfcwnd, cwnd - in_flight);
}
/* left_out = sacked_out + lost_out 
    sacked_out:Packets, which arrived to receiver out of order and hence not ACKed. With SACK this  number is simply amount of SACKed data. 
Even without SACKs it is easy to give pretty reliable  estimate of this number, counting duplicate ACKs.

lost_out:Packets lost by network. TCP has no explicit loss notification feedback from network
        (for now). It means that this number can be only guessed. Actually, it is the heuristics to predict  lossage that distinguishes different algorithms.
        F.e. after RTO, when all the queue is considered as lost, lost_out = packets_out and in_flight = retrans_out.
*/
//該函數估算的是那些已經發送出去(初傳+重傳)并且已經離開
//網絡的段的數目,這些段主要是SACK确認的+已經判定為丢失的段
static inline unsigned int tcp_left_out(const struct tcp_sock *tp)
{
    //sacked_out:啟用SACK時,表示已經被SACK選項确認的段的數量;
    //            不啟用SACK時,記錄了收到的重複ACK的次數,因為重複ACK不會自動發送,一定是對端收到了資料包;
    //lost_out:記錄發送後在傳輸過程中丢失的段的數目,因為TCP沒有一種機制可以準确的知道
    //          發出去的段是否真的丢了,是以這隻是一種算法上的估計值
    //無論如何,這兩種段屬于已經發送,但是可以确定它們在網絡中已經不存在了
    return tp->sacked_out + tp->lost_out;
}
 
/* This determines how many packets are "in the network" to the best
 * of our knowledge.  In many cases it is conservative, but where
 * detailed information is available from the receiver (via SACK
 * blocks etc.) we can make more aggressive calculations.
 *
 * Use this for decisions involving congestion control, use just
 * tp->packets_out to determine if the send queue is empty or not.
 *
 * Read this equation as:
 *
 *    "Packets sent once on transmission queue" MINUS
 *    "Packets left network, but not honestly ACKed yet" PLUS
 *    "Packets fast retransmitted"
 
 packets_out is SND.NXT - SND.UNA counted in packets.
 retrans_out is number of retransmitted segments.
left_out is number of segments left network, but not ACKed yet.

 */
static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
    //packets_out記錄的是已經從發送隊列發出,但是尚未被确認的段的數目(不包括重傳)
    //retrans_out表示的是因為重傳才發送出去,但是還沒有被确認的段的數目
    //tcp_left_out():發出去了但是已經離開了網絡的資料包數目
    return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}      

 tcp_snd_wnd_test

/* Does at least the first segment of SKB fit into the send window? 
判斷目前發送視窗是否至少允許發送一個段,如果允許,傳回1,否則傳回0*/
static bool tcp_snd_wnd_test(const struct tcp_sock *tp,
                 const struct sk_buff *skb,
                 unsigned int cur_mss)
{
    u32 end_seq = TCP_SKB_CB(skb)->end_seq;

    if (skb->len > cur_mss)//如果skb中資料超過了一個段大小,則調整end_seq為一個段大小的序号
        end_seq = TCP_SKB_CB(skb)->seq + cur_mss;
        //檢查一個段的末尾序号是否超過了發送視窗的右邊界
    return !after(end_seq, tcp_wnd_end(tp));
}
/* Returns end sequence number of the receiver's advertised window 
//傳回發送視窗的右邊界*/
static inline u32 tcp_wnd_end(const struct tcp_sock *tp)
{
    //snd_una:已經發送但是還沒有被确認的最小序号
    //snd_wnd:目前發送視窗大小,即接收方剩餘的接收緩沖區
    return tp->snd_una + tp->snd_wnd;
}      

tcp_mss_split_point

/* 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為發送視窗允許目前skb發送的最大位元組數(可能會超過skb->len)
    window = tcp_wnd_end(tp) - TCP_SKB_CB(skb)->seq;
    max_len = mss_now * max_segs;//目前tso 允許發送最大資料

    if (likely(max_len <= window && skb != tcp_write_queue_tail(sk)))
        return max_len;
        //needed為經過發送視窗矯正後的實際要發送的資料量
    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
     *///最終傳回值是MSS的整數倍,當然機關依然是位元組
    if (tcp_nagle_check(partial != 0, tp, nonagle))
        return needed - partial;

    return needed;
}      

tso_fragment:主要作用為:如果skb中資料量過大,超過了發送視窗和擁塞視窗的限定,隻允許發送skb的一部分,那麼就需要将skb拆分成兩段,前半段長度為len,本次可以發送,後半段儲存在新配置設定的skb中,在發送隊列sk_write_queue中将後半段插入到前半段的後面,這樣可以保證資料的順序發送。

tcp_transmit_skb  

  真正的發送操作是在tcp_transmit_skb中完成的。該函數最主要的工作是建構 TCP的首部,并将包傳遞給 IP 層。由于資料報需要等到 ACK 後才能釋放,是以需要在發送隊列中長期保留一份 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.  It is our
 * job to build the TCP header, and pass the packet down to
 * IP so it can do the same plus pass the packet off to the
 * device.
 *
 * We are working here with either a clone of the original
 * SKB, or a fresh unique copy made by the retransmit engine.
 */
 /*
 * 通常要發送一個TCP段都是通過tcp_transmit_skb()的。該函數會給
 * 待發送的段構造TCP首部,然後調用網絡層接口到IP層,最終
 * 抵達網絡裝置。由于在成功發送到網絡裝置後會釋放該
 * SKB,而TCP必須要接到對應的ACK後才能真正釋放資料,是以
 * 在發送前會根據參數确定是克隆還是複制一份SKB用于發送。
 */ //最終的tcp發送都會調用這個  clone_it表示發送發送隊列的第一個SKB的時候,采用克隆skb還是直接使用skb,如果是發送應用層資料則使用克隆的,等待對方應答ack回來才把資料删除。如果是會送ack資訊,則無需克隆
//如果不支援TSO或者GSO這裡的SKB->len為mss,否則如果支援TSO并且有資料再shinfo中,則這裡的SKB長度為shinfo或者擁塞視窗的最小值
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
                gfp_t gfp_mask)
{
    const struct inet_connection_sock *icsk = inet_csk(sk);
    struct inet_sock *inet;
    struct tcp_sock *tp;
    struct tcp_skb_cb *tcb;
    struct tcp_out_options opts;
    unsigned int tcp_options_size, tcp_header_size;
    struct tcp_md5sig_key *md5;
    struct tcphdr *th;
    int err;

    BUG_ON(!skb || !tcp_skb_pcount(skb));

    if (clone_it) {//根據參數clone_it确定是否克隆待發送的資料包。
        skb_mstamp_get(&skb->skb_mstamp);
        /* 這裡收到的 SKB 可能是原始 SKB 的克隆,也可能是
         * 來自重傳引擎的一份拷貝。
         */
        if (unlikely(skb_cloned(skb)))
            skb = pskb_copy(skb, gfp_mask);
        else
            skb = skb_clone(skb, gfp_mask);
        if (unlikely(!skb))
            return -ENOBUFS;
    }

    /*首先判斷該 TCP 包是否是一個 SYN 包。如果是,則
調用tcp_syn_options建構相應的選項。否則,調用 tcp_established_options來建構
相應的選項。注意,這裡僅僅是計算出來具體的選項及其大小,并沒有形成最終 TCP 包中選項的格式。
     * 擷取INET層和TCP層的傳輸控制塊、SKB中的TCP私有控制塊
     * 以及目前TCP首部長度。
     */
    inet = inet_sk(sk);
    tp = tcp_sk(sk);
    tcb = TCP_SKB_CB(skb);
    memset(&opts, 0, sizeof(opts));
 /*
     * 判斷目前TCP段是不是SYN段,因為有些選項隻能出現在SYN段中,需作
     * 特别處理。
     */
    if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
        tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
    else
        tcp_options_size = tcp_established_options(sk, skb, &opts,
                               &md5);
    tcp_header_size = tcp_options_size + sizeof(struct tcphdr);

    /* if no packet is in qdisc/device queue, then allow XPS to select
     * another queue. We can be called from tcp_tsq_handler()
     * which holds one reference to sk_wmem_alloc.
     *
     * TODO: Ideally, in-flight pure ACK packets should not matter here.
     * One way to get this would be to set skb->truesize = 2 on them.
     根據選項的大小,可以進一步推算出 TCP 頭部的大小。之後,調用相關函數在 skb 頭
部為 TCP 頭部流出空間。
     */
    skb->ooo_okay = sk_wmem_alloc_get(sk) < SKB_TRUESIZE(1);
/*
     * 到此為止,SKB已添加到發送隊列中。但從SKB角度去看
     * 還不知道它屬于哪個傳輸控制塊,是以此時需調用
     * skb_set_owner_w()設定該SKB的宿主。
     */
     /*在sk_alloc的時候初始化設定為1
    ,然後在skb_set_owner_w加上SKB長度,當SKB發送出去後
    ,在減去該SKB的長度,是以這個值當資料發送後其值始終是1
    ,不會執行sock_wfree
    */
    skb_push(skb, tcp_header_size);
    skb_reset_transport_header(skb);

    skb_orphan(skb);
    skb->sk = sk;
    skb->destructor = skb_is_tcp_pure_ack(skb) ? __sock_wfree : tcp_wfree;
    skb_set_hash_from_sk(skb, sk);
    atomic_add(skb->truesize, &sk->sk_wmem_alloc);

    /* Build TCP header and checksum it. 建構 TCP 頭部并計算校驗和*/
    th = (struct tcphdr *)skb->data;
    th->source        = inet->inet_sport;
    th->dest        = inet->inet_dport;
    th->seq            = htonl(tcb->seq);
    th->ack_seq        = htonl(tp->rcv_nxt);
    *(((__be16 *)th) + 6)    = htons(((tcp_header_size >> 2) << 12) |
                    tcb->tcp_flags);

    th->check        = 0;
    th->urg_ptr        = 0;
 /*
     * 判斷是否需要設定緊急指針和帶外資料标志。判斷條件有兩個,
     * 一是發送時是否設定了緊急方式,二是緊急指針是否在以該封包
     * 資料序号為起始的65535範圍之内,其中第二個條件主要是判斷緊急
     * 指針的合法性。
     */
    /* The urg_mode check is necessary during a below snd_una win probe */
    if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {
        if (before(tp->snd_up, tcb->seq + 0x10000)) {
            th->urg_ptr = htons(tp->snd_up - tcb->seq);
            th->urg = 1;
        } else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {
            th->urg_ptr = htons(0xFFFF);
            th->urg = 1;
        }
    }
    /*在寫完首部後,開始建構TCP首部選項。
     */
    tcp_options_write((__be32 *)(th + 1), tp, &opts);
    skb_shinfo(skb)->gso_type = sk->sk_gso_type;
    if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
        th->window      = htons(tcp_select_window(sk));
        tcp_ecn_send(sk, skb, th, tcp_header_size);
    } else {
        /* RFC1323: The window in SYN & SYN/ACK segments
         * is never scaled.
         */
        th->window    = htons(min(tp->rcv_wnd, 65535U));
    }
#ifdef CONFIG_TCP_MD5SIG
    /* Calculate the MD5 hash, as we have all we need now */
    if (md5) {
        sk_nocaps_add(sk, NETIF_F_GSO_MASK);
        tp->af_specific->calc_md5_hash(opts.hash_location,
                           md5, sk, skb);
    }
#endif
  /*
     * 調用IPv4執行校驗和接口send_check計算校驗和,并設定到TCP首部中。
     * 在TCP中send_check接口被初始化為tcp_v4_send_check。
     */
    icsk->icsk_af_ops->send_check(sk, skb);

  /* 觸發相關的 TCP 事件,這些會被用于擁塞控制算法。 */

  /*
     * 如果發送出去的段有ACK标志,則需要通知延時确認子產品,遞減
     * 快速發送ACK段的數量,同時停止延時确認定時器。
     */
    if (likely(tcb->tcp_flags & TCPHDR_ACK))
        tcp_event_ack_sent(sk, tcp_skb_pcount(skb));
  /*
     * 如果發送出去的TCP段有負載,則檢測擁塞視窗閑置是否逾時,
     * 并使其失效。同時記錄發送TCP的時間,根據最近接受段的時間
     * 确定本端延時确認是否進入pingpong模式。
     */
    if (skb->len != tcp_header_size) {
        tcp_event_data_sent(tp, sk);
        tp->data_segs_out += tcp_skb_pcount(skb);
    }

    if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
        TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
                  tcp_skb_pcount(skb));

    tp->segs_out += tcp_skb_pcount(skb);
    /* OK, its time to fill skb_shinfo(skb)->gso_{segs|size} */
    skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);
    skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);

    /* Our usage of tstamp should remain private */
    skb->tstamp.tv64 = 0;

    /* Cleanup our debris for IP stacks */
    memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),
                   sizeof(struct inet6_skb_parm)));

    /* 在這裡,我們将包加入到 IP 層的發送隊列 
     * 調用發送接口queue_xmit發送封包,如果失敗則傳回
     * 錯誤碼。在TCP中該接口實作函數為ip_queue_xmit()。
     */
    err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);//ip_queue_xmit
    if (likely(err <= 0))
        return err;

    /*
     * 當發送失敗時,類似接收到顯式擁塞通知,使擁塞
     * 控制進入CWR狀态。最後,根據錯誤資訊,傳回發送
     * 是否成功。
     ----->* 如果發送了丢包(如被主動隊列管理丢棄),那麼進入到擁塞控制狀态。 */
    tcp_enter_cwr(sk);

    return net_xmit_eval(err);
}      

視窗的選擇

/* Chose a new window to advertise, update state in tcp_sock for the
 * socket, and return result with RFC1323 scaling applied.  The return
 * value can be stuffed directly into th->window for an outgoing
 * frame.這個函數的作用是選擇一個新的視窗大小以用于更新tcp_sock。傳回的結果根據
RFC1323進行了縮放
 */
static u16 tcp_select_window(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    u32 old_win = tp->rcv_wnd;
    u32 cur_win = tcp_receive_window(tp);
    u32 new_win = __tcp_select_window(sk);
    /* old_win 是接收方視窗的大小。
     * cur_win 目前的接收視窗剩餘  剩餘 剩餘 大小。
      * new_win 是新選擇出來的視窗大小。根據剩餘緩存計算 新的接收視窗大小
      */
        /* 當新視窗的大小小于目前視窗的大小時,不能縮減視窗大小。
   * 這是 IEEE 強烈不建議的一種行為。
   */
    /* Never shrink the offered window */
    if (new_win < cur_win) {
        /* Danger Will Robinson!
         * Don't update rcv_wup/rcv_wnd here or else
         * we will not be able to advertise a zero
         * window in time.  --DaveM
         *
         * Relax Will Robinson.
         */
        if (new_win == 0)
            NET_INC_STATS(sock_net(sk),
                      LINUX_MIB_TCPWANTZEROWINDOWADV);
        new_win = ALIGN(cur_win, 1 << tp->rx_opt.rcv_wscale);
    }
    tp->rcv_wnd = new_win;/* 将目前的接收視窗設定為新的視窗大小。 */
    tp->rcv_wup = tp->rcv_nxt;

    /* Make sure we do not exceed the maximum possible
     * scaled window.
     *//* 判斷目前視窗未越界。 */
    if (!tp->rx_opt.rcv_wscale && sysctl_tcp_workaround_signed_windows)
        new_win = min(new_win, MAX_TCP_WINDOW);
    else
        new_win = min(new_win, (65535U << tp->rx_opt.rcv_wscale));

    /* RFC1323 scaling applied *//* RFC1323 縮放視窗大小。這裡之是以是右移,是因為此時的 new_win 是
    * 視窗的真正大小。是以傳回時需要傳回正常的可以放在 16 位整型中的視窗大小。
    * 是以需要右移。*/
    new_win >>= tp->rx_opt.rcv_wscale;

    /* If we advertise zero window, disable fast path. */
    if (new_win == 0) {
        tp->pred_flags = 0;
        if (old_win)
            NET_INC_STATS(sock_net(sk),
                      LINUX_MIB_TCPTOZEROWINDOWADV);
    } else if (old_win == 0) {
        NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPFROMZEROWINDOWADV);
    }

    return new_win;
}      

 每次發送一個TCP資料段,都要建構TCP首部,這時會調用tcp_select_window選擇接收窗

視窗大小選擇的基本算法:

1. 計算目前接收視窗的剩餘大小cur_win。

2. 計算新的接收視窗大小new_win,這個值為剩餘接收緩存的3/4,且不能超過rcv_ssthresh。

3. 取cur_win和new_win中值較大者作為接收視窗大小

/* Compute the actual receive window we are currently advertising.
 * Rcv_nxt can be after the window if our peer push more data
 * than the offered window.
 */
static inline u32 tcp_receive_window(const struct tcp_sock *tp)
{
    s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt;

    if (win < 0)
        win = 0;
    return (u32) win;
}      

This is calculated as the last advertised window minus unacknowledged data length:

tp->rcv_wnd - (tp->rcv_nxt - tp->rcv_wup)

tp->rcv_wup is synced with next byte to be received (tp->rcv_nxt) only when we are sending ACK in

tcp_select_window(). If there is no unacknowledged bytes, the routine returns the exact receive

/* This function returns the amount that we can raise the
 * usable window based on the following constraints
 *
 * 1. The window can never be shrunk once it is offered (RFC 793)
 * 2. We limit memory per socket
 *
 * RFC 1122:
 * "the suggested [SWS] avoidance algorithm for the receiver is to keep
 *  RECV.NEXT + RCV.WIN fixed until:
 *  RCV.BUFF - RCV.USER - RCV.WINDOW >= min(1/2 RCV.BUFF, MSS)"
 *推薦的用于接收方的糊塗視窗綜合症的避免算法是保持 recv.next+rcv.win
不變,直到:RCV.BUFF - RCV.USER - RCV.WINDOW >= min(1/2 RCV.BUFF,
MSS)
換句話說,就是除非緩存的大小多出來至少一個 MSS 那麼多位元組,否則不要增長
視窗右邊界的大小。
 * i.e. don't raise the right edge of the window until you can raise
 * it at least MSS bytes.
 *
 * Unfortunately, the recommended algorithm breaks header prediction,
 * since header prediction assumes th->window stays fixed.
 *
 * Strictly speaking, keeping th->window fixed violates the receiver
 * side SWS prevention criteria. The problem is that under this rule
 * a stream of single byte packets will cause the right side of the
 * window to always advance by a single byte.
 *
 * Of course, if the sender implements sender side SWS prevention
 * then this will not be a problem.
 *
 * BSD seems to make the following compromise:
 *
 *    If the free space is less than the 1/4 of the maximum
 *    space available and the free space is less than 1/2 mss,
 *    then set the window to 0.
 *    [ Actually, bsd uses MSS and 1/4 of maximal _window_ ]
 *    Otherwise, just prevent the window from shrinking
 *    and from being larger than the largest representable value.
 *
 * This prevents incremental opening of the window in the regime
 * where TCP is limited by the speed of the reader side taking
 * data out of the TCP receive queue. It does nothing about
 * those cases where the window is constrained on the sender side
 * because the pipeline is full.
 *
 * BSD also seems to "accidentally" limit itself to windows that are a
 * multiple of MSS, at least until the free space gets quite small.
 * This would appear to be a side effect of the mbuf implementation.
 * Combining these two algorithms results in the observed behavior
 * of having a fixed window size at almost all times.
 *
 * Below we obtain similar behavior by forcing the offered window to
 * a multiple of the mss when it is feasible to do so.
 *
 * Note, we don't "adjust" for TIMESTAMP or SACK option bytes.
 * Regular options like TIMESTAMP are taken into account.
 然而,根據 Linux 注釋中的說法,被推薦的這個算法會破壞頭預測 (header prediction),
 因為頭預測會假定th->window不變。嚴格地說,保持th->window固定不變會違背
接收方的用于防止糊塗視窗綜合症的準則。在這種規則下,一個單位元組的包的流會引發
視窗的右邊界總是提前一個位元組。當然,如果發送方實作了預防糊塗視窗綜合症的方法,
那麼就不會出現問題。
Linux 的 TCP 部分的作者們參考了 BSD 的實作方法。 BSD 在這方面的做法是是,
如果空閑空間小于最大可用空間的 1
4,且空閑空間小于 mss 的 1 2,那麼就把視窗設定為
0。否則,隻是單純地阻止視窗縮小,或者阻止視窗大于最大可表示的範圍 (the largest
representable value)。 BSD 的方法似乎“意外地”使得視窗基本上都是 MSS 的整倍數。
且很多情況下視窗大小都是固定不變的。是以, Linux 采用強制視窗為 MSS 的整倍數,
以獲得相似的行為
 */
u32 __tcp_select_window(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    /* MSS for the peer's data.  Previous versions used mss_clamp
     * here.  I don't know if the value based on our guesses
     * of peer's MSS is better for the performance.  It's more correct
     * but may be worse for the performance because of rcv_mss
     * fluctuations.  --SAW  1998/11/1
     */
    int mss = icsk->icsk_ack.rcv_mss;
    int free_space = tcp_space(sk);
    int allowed_space = tcp_full_space(sk);
    int full_space = min_t(int, tp->window_clamp, allowed_space);
    int window;

    if (mss > full_space)
        mss = full_space;/* 如果 mss 超過了總共的空間大小,那麼把 mss 限制在允許的空間範圍内。 */

    if (free_space < (full_space >> 1)) {
        icsk->icsk_ack.quick = 0;/* 當空閑空間小于允許空間的一半時。 */

        if (tcp_under_memory_pressure(sk))
            tp->rcv_ssthresh = min(tp->rcv_ssthresh,
                           4U * tp->advmss);

        /* free_space might become our new window, make sure we don't
         * increase it due to wscale.
         *//* free_space 有可能成為新的視窗的大小,是以,需要考慮
    * 視窗擴充的影響。 */
        free_space = round_down(free_space, 1 << tp->rx_opt.rcv_wscale);

        /* if free space is less than mss estimate, or is below 1/16th
         * of the maximum allowed, try to move to zero-window, else
         * tcp_clamp_window() will grow rcv buf up to tcp_rmem[2], and
         * new incoming data is dropped due to memory limits.
         * With large window, mss test triggers way too late in order
         * to announce zero window in time before rmem limit kicks in.
         *//* 如果空閑空間小于 mss 的大小,或者低于最大允許空間的的 1/16,那麼,
      * 傳回 0 視窗。否則, tcp_clamp_window() 會增長接收緩存到 tcp_rmem[2]。
         * 新進入的資料會由于内醋限制而被丢棄。對于較大的視窗,單純地探測 mss 的
         * 大小以宣告 0 視窗有些太晚了(可能會超過限制)。
        */
        if (free_space < (allowed_space >> 4) || free_space < mss)
            return 0;
    }

    if (free_space > tp->rcv_ssthresh)
        free_space = tp->rcv_ssthresh;

    /* Don't do rounding if we are using window scaling, since the
     * scaled window will not line up with the MSS boundary anyway.
     *//* 這裡處理一個例外情況,就是如果開啟了視窗縮放,那麼就沒法對齊 mss 了。
      * 是以就保持視窗是對齊 2 的幂的。 */
    window = tp->rcv_wnd;
    if (tp->rx_opt.rcv_wscale) {
        window = free_space;

        /* Advertise enough space so that it won't get scaled away.
         * Import case: prevent zero window announcement if
         * 1<<rcv_wscale > mss.
         */
        if (((window >> tp->rx_opt.rcv_wscale) << tp->rx_opt.rcv_wscale) != window)
            window = (((window >> tp->rx_opt.rcv_wscale) + 1)
                  << tp->rx_opt.rcv_wscale);
    } else {
        /* Get the largest window that is a nice multiple of mss.
         * Window clamp already applied above.
         * If our current window offering is within 1 mss of the
         * free space we just keep it. This prevents the divide
         * and multiply from happening most of the time.
         * We also don't do any window rounding when the free space
         * is too small.
         *//* 如果記憶體條件允許,那麼就把視窗設定為 mss 的整倍數。 * 或者如果 free_space > 目前視窗大小加上全部允許的空間的一半,
         * 那麼,就将視窗大小設定為 free_space */
        if (window <= free_space - mss || window > free_space)
            window = (free_space / mss) * mss;
        else if (mss == full_space &&
             free_space > window + (full_space >> 1))
            window = free_space;
    }

    return window;
}