概述
tcp_write_xmit函數完成對待發送數據的分段發送,過程中會遍歷發送隊列,進行窗口檢查,需要TSO分段則分段,然后調用tcp_transmit_skb發送數據段;
源碼分析
1 static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, 2 int push_one, gfp_t gfp) 3 { 4 struct tcp_sock *tp = tcp_sk(sk); 5 struct sk_buff *skb; 6 unsigned int tso_segs, sent_pkts; 7 int cwnd_quota; 8 int result; 9 bool is_cwnd_limited = false, is_rwnd_limited = false; 10 u32 max_segs; 11 12 /* 已發送數據段數量 */ 13 sent_pkts = 0; 14 15 /* 發送多個數據段 */ 16 if (!push_one) { 17 /* Do MTU probing. */ 18 /* 發送路徑mtu探測 */ 19 result = tcp_mtu_probe(sk); 20 /* 失敗 */ 21 if (!result) { 22 return false; 23 } 24 /* 成功,設置已發送數據段數為1 */ 25 else if (result > 0) { 26 sent_pkts = 1; 27 } 28 } 29 30 /* 獲取最大tso分段 */ 31 max_segs = tcp_tso_segs(sk, mss_now); 32 33 /* 有數據段要發送 */ 34 while ((skb = tcp_send_head(sk))) { 35 unsigned int limit; 36 37 /* 初始化tso分段相關 */ 38 tso_segs = tcp_init_tso_segs(skb, mss_now); 39 BUG_ON(!tso_segs); 40 41 if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) { 42 /* "skb_mstamp" is used as a start point for the retransmit timer */ 43 skb_mstamp_get(&skb->skb_mstamp); 44 goto repair; /* Skip network transmission */ 45 } 46 47 /* 檢測擁塞窗口大小 */ 48 cwnd_quota = tcp_cwnd_test(tp, skb); 49 /* 為0 */ 50 if (!cwnd_quota) { 51 /* 尾部丟失探測段,設置為1 */ 52 if (push_one == 2) 53 /* Force out a loss probe pkt. */ 54 cwnd_quota = 1; 55 /* 其他情況,跳出 */ 56 else 57 break; 58 } 59 60 /* 檢查tcp的數據段是否在發送窗口之內 */ 61 if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now))) { 62 /* 不在,標記,跳出 */ 63 is_rwnd_limited = true; 64 break; 65 } 66 67 /* 不需要tso分段 */ 68 if (tso_segs == 1) { 69 /* 檢查nagle算法是否允許發送數據段 */ 70 if (unlikely(!tcp_nagle_test(tp, skb, mss_now, 71 (tcp_skb_is_last(sk, skb) ? 72 nonagle : TCP_NAGLE_PUSH)))) 73 break; 74 } 75 /* 需要tso分段 */ 76 else { 77 /* 檢查是否可以延遲發送 */ 78 if (!push_one && 79 tcp_tso_should_defer(sk, skb, &is_cwnd_limited, 80 max_segs)) 81 break; 82 } 83 84 /* 設置分段長度限制為mss */ 85 limit = mss_now; 86 87 /* 需要分段 && 非緊急模式,重新確定分段長度限制 */ 88 if (tso_segs > 1 && !tcp_urg_mode(tp)) 89 limit = tcp_mss_split_point(sk, skb, mss_now, 90 min_t(unsigned int, 91 cwnd_quota, 92 max_segs), 93 nonagle); 94 95 /* skb中數據段長度>分段長度限制,則進行分段,會申請新的skb */ 96 if (skb->len > limit && 97 unlikely(tso_fragment(sk, skb, limit, mss_now, gfp))) 98 break; 99 100 if (test_bit(TCP_TSQ_DEFERRED, &sk->sk_tsq_flags)) 101 clear_bit(TCP_TSQ_DEFERRED, &sk->sk_tsq_flags); 102 if (tcp_small_queue_check(sk, skb, 0)) 103 break; 104 105 /* 發送分段數據 */ 106 if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp))) 107 break; 108 109 repair: 110 /* Advance the send_head. This one is sent out. 111 * This call will increment packets_out. 112 */ 113 /* 進行發送之后的數據更新,包括統計計數和定時器等 */ 114 tcp_event_new_data_sent(sk, skb); 115 116 /* 更新最新發送小包的結束序號 */ 117 tcp_minshall_update(tp, mss_now, skb); 118 119 /* 更新發送數據段數量 */ 120 sent_pkts += tcp_skb_pcount(skb); 121 122 /* 只發送一個段,則跳出 */ 123 if (push_one) 124 break; 125 } 126 127 if (is_rwnd_limited) 128 tcp_chrono_start(sk, TCP_CHRONO_RWND_LIMITED); 129 else 130 tcp_chrono_stop(sk, TCP_CHRONO_RWND_LIMITED); 131 132 /* 本次有數據發送,擁塞相關數據更新 */ 133 if (likely(sent_pkts)) { 134 if (tcp_in_cwnd_reduction(sk)) 135 tp->prr_out += sent_pkts; 136 137 /* Send one loss probe per tail loss episode. */ 138 /* 每次發送一個尾部丟失探測 */ 139 if (push_one != 2) 140 tcp_schedule_loss_probe(sk); 141 142 /* 擁塞窗口校驗 */ 143 is_cwnd_limited |= (tcp_packets_in_flight(tp) >= tp->snd_cwnd); 144 tcp_cwnd_validate(sk, is_cwnd_limited); 145 return false; 146 } 147 148 149 /* 本次無數據發送,已發出未確認的數據段不為0或者發送隊列為空,認為成功 */ 150 return !tp->packets_out && tcp_send_head(sk); 151 }