Linux 核心網路協議棧 ------ tcp_ack 函式處理接收到的ACK包之後
注意 tcp_ack 是來處理接收到的ACK的,那麼到底怎麼去做呢?看下面:
先還上把tcp_sock的結構放在這裡,下面一些資料的分析需要用到:
struct tcp_sock { /* inet_connection_sock has to be the first member of tcp_sock */ struct inet_connection_sock inet_conn; u16 tcp_header_len; /* Bytes of tcp header to send */ // tcp頭部長度 u16 xmit_size_goal_segs; /* Goal for segmenting output packets */// 分段資料包的數量 /* * Header prediction flags * 0x5?10 << 16 + snd_wnd in net byte order */ __be32 pred_flags; // 頭部預置位(用於檢測頭部標識位處理ACK和PUSH之外還有沒有其他位,從而判斷是不是可以使用快速路徑處理資料) /* * RFC793 variables by their proper names. This means you can * read the code and the spec side by side (and laugh ...) * See RFC793 and RFC1122. The RFC writes these in capitals. */ u32 rcv_nxt; /* What we want to receive next */ // 下一個想要收到的第一個資料的位元組編號 u32 copied_seq; /* Head of yet unread data */ // 沒還有讀出的資料的頭 u32 rcv_wup; /* rcv_nxt on last window update sent */ // rcv_nxt在最後一個視窗更新時的值 u32 snd_nxt; /* Next sequence we send */ // 下一個傳送的第一個位元組編號 u32 snd_una; /* First byte we want an ack for */ // 對於發出的資料,都需要對方的ACK,這裡標示當前需要被確認的第一個位元組 u32 snd_sml; /* Last byte of the most recently transmitted small packet */ // 最近傳送的小資料包的最後一個位元組 u32 rcv_tstamp; /* timestamp of last received ACK (for keepalives) */ // 最後一次接收到ACK的時間戳 u32 lsndtime; /* timestamp of last sent data packet (for restart window) */ // 最後一次傳送資料包時間戳 u32 tsoffset; /* timestamp offset */ // 時間戳偏移 struct list_head tsq_node; /* anchor in tsq_tasklet.head list */ // unsigned long tsq_flags; // 注意下面這個ucopy:就是將使用者資料從skb中拿出來放進去,然後傳給應用程序!!! /* Data for direct copy to user */ struct { struct sk_buff_head prequeue; // 預處理佇列 struct task_struct *task; // 預處理程序 struct iovec *iov; // 使用者程式(應用程式)接收資料的緩衝區 int memory; // 用於預處理計數 int len; // 預處理長度 #ifdef CONFIG_NET_DMA /* members for async copy */ struct dma_chan *dma_chan; int wakeup; struct dma_pinned_list *pinned_list; dma_cookie_t dma_cookie; #endif } ucopy; // snd_wl1:記錄傳送視窗更新時,造成視窗更新的那個資料報的第一個序號。 它主要用於在下一次判斷是否需要更新發送視窗。 u32 snd_wl1; /* Sequence for window update */ // 視窗更新序列號( 每一次收到確認之後都會改變 ) u32 snd_wnd; /* The window we expect to receive */ // 我們期望收到的視窗 u32 max_window; /* Maximal window ever seen from peer */ // 從對方接收到的最大視窗 u32 mss_cache; /* Cached effective mss, not including SACKS */ // 有效的MSS,SACKS不算 u32 window_clamp; /* Maximal window to advertise */ // 對外公佈的最大的視窗 u32 rcv_ssthresh; /* Current window clamp */ // 當前視窗值 u16 advmss; /* Advertised MSS */ // 對外公佈的MSS u8 unused; u8 nonagle : 4,/* Disable Nagle algorithm? */ // Nagle演算法是否有效 thin_lto : 1,/* Use linear timeouts for thin streams */ // 使用線性超時處理 thin_dupack : 1,/* Fast retransmit on first dupack */ // 收到第一個重複的ACK的時候是否快速重傳 repair : 1, frto : 1;/* F-RTO (RFC5682) activated in CA_Loss */ u8 repair_queue; u8 do_early_retrans:1,/* Enable RFC5827 early-retransmit */ // 是否可以使用之前的重傳 syn_data:1, /* SYN includes data */ // data中是否包含SYN syn_fastopen:1, /* SYN includes Fast Open option */ // SYN選項 syn_data_acked:1;/* data in SYN is acked by SYN-ACK */ // SYN回覆 u32 tlp_high_seq; /* snd_nxt at the time of TLP retransmit. */ // tlp重傳時候snd_nxt的值 /* RTT measurement */ u32 srtt; /* smoothed round trip time << 3 */ // 往返時間 u32 mdev; /* medium deviation */ // u32 mdev_max; /* maximal mdev for the last rtt period */ // 最大mdev u32 rttvar; /* smoothed mdev_max */ u32 rtt_seq; /* sequence number to update rttvar */ u32 packets_out; /* Packets which are "in flight" */ // 已經發出去的尚未收到確認的包 u32 retrans_out; /* Retransmitted packets out */ // 重傳的包 u16 urg_data; /* Saved octet of OOB data and control flags */ // OOB資料和控制位 u8 ecn_flags; /* ECN status bits. */ // ECN狀態位 u8 reordering; /* Packet reordering metric. */ // 包重排度量 u32 snd_up; /* Urgent pointer */ // 緊急指標 u8 keepalive_probes; /* num of allowed keep alive probes */ /* * Options received (usually on last packet, some only on SYN packets). */ struct tcp_options_received rx_opt; // tcp接收選項 /* * Slow start and congestion control (see also Nagle, and Karn & Partridge) */ u32 snd_ssthresh; /* Slow start size threshold */ // 慢啟動的開始大小 u32 snd_cwnd; /* Sending congestion window */ // 傳送阻塞視窗 u32 snd_cwnd_cnt; /* Linear increase counter */ // 線性增長計數器(為了視窗的擴大) u32 snd_cwnd_clamp; /* Do not allow snd_cwnd to grow above this */ // snd_cwnd值不可以超過這個門限 u32 snd_cwnd_used; u32 snd_cwnd_stamp; u32 prior_cwnd; /* Congestion window at start of Recovery. */ // 在剛剛恢復時候的阻塞視窗大小 u32 prr_delivered; /* Number of newly delivered packets to // 在恢復期間接收方收到的包 * receiver in Recovery. */ u32 prr_out; /* Total number of pkts sent during Recovery. */ // 在恢復期間發出去的包 u32 rcv_wnd; /* Current receiver window */ // 當前接收視窗 u32 write_seq; /* Tail(+1) of data held in tcp send buffer */ // tcp傳送buf中資料的尾部 u32 notsent_lowat; /* TCP_NOTSENT_LOWAT */ u32 pushed_seq; /* Last pushed seq, required to talk to windows */ // push序列 u32 lost_out; /* Lost packets */ // 丟失的包 u32 sacked_out; /* SACK'd packets */ // SACK包 u32 fackets_out; /* FACK'd packets */ // FACK包 u32 tso_deferred; /* from STCP, retrans queue hinting */ struct sk_buff* lost_skb_hint; // 用於丟失的包 struct sk_buff *retransmit_skb_hint; // 用於重傳的包 struct sk_buff_head out_of_order_queue; /* Out of order segments go here */ // 接收到的無序的包儲存 /* SACKs data, these 2 need to be together (see tcp_options_write) */ struct tcp_sack_block duplicate_sack[1]; /* D-SACK block */ struct tcp_sack_block selective_acks[4]; /* The SACKS themselves*/ struct tcp_sack_block recv_sack_cache[4]; struct sk_buff *highest_sack; /* skb just after the highest * skb with SACKed bit set * (validity guaranteed only if * sacked_out > 0) */ int lost_cnt_hint; u32 retransmit_high; /* L-bits may be on up to this seqno */ u32 lost_retrans_low; /* Sent seq after any rxmit (lowest) */ u32 prior_ssthresh; /* ssthresh saved at recovery start */ u32 high_seq; /* snd_nxt at onset of congestion */ u32 retrans_stamp; /* Timestamp of the last retransmit, * also used in SYN-SENT to remember stamp of * the first SYN. */ u32 undo_marker; /* tracking retrans started here. */ int undo_retrans; /* number of undoable retransmissions. */ u32 total_retrans; /* Total retransmits for entire connection */ u32 urg_seq; /* Seq of received urgent pointer */ unsigned int keepalive_time; /* time before keep alive takes place */ unsigned int keepalive_intvl; /* time interval between keep alive probes */ int linger2; /* Receiver side RTT estimation */ struct { u32 rtt; u32 seq; u32 time; } rcv_rtt_est; /* Receiver queue space */ struct { int space; u32 seq; u32 time; } rcvq_space; /* TCP-specific MTU probe information. */ struct { u32 probe_seq_start; u32 probe_seq_end; } mtu_probe; u32 mtu_info; /* We received an ICMP_FRAG_NEEDED / ICMPV6_PKT_TOOBIG * while socket was owned by user. */ #ifdef CONFIG_TCP_MD5SIG /* TCP AF-Specific parts; only used by MD5 Signature support so far */ const struct tcp_sock_af_ops *af_specific; /* TCP MD5 Signature Option information */ struct tcp_md5sig_info __rcu *md5sig_info; #endif /* TCP fastopen related information */ struct tcp_fastopen_request *fastopen_req; /* fastopen_rsk points to request_sock that resulted in this big * socket. Used to retransmit SYNACKs etc. */ struct request_sock *fastopen_rsk; };
關於視窗的操作值snd_una,snd_wnd等可以看下面的圖形:
先看傳送視窗:
再看接收視窗:
還有tcp_skb_cb結構:
struct tcp_skb_cb { union { struct inet_skb_parm h4; #if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) struct inet6_skb_parm h6; #endif } header; /* For incoming frames */ __u32 seq; // 當前tcp包的第一個序列號 __u32 end_seq; // 表示結束的序列號:seq + FIN + SYN + 資料長度len __u32 when; // 用於計算RTT __u8 flags; // tcp頭的flag __u8 sacked; // SACK/FACK的狀態flag或者是sack option的偏移 __u32 ack_seq; // ack(確認)的序列號 };
關於tcp頭的標識(flags)可以取:
#define TCPCB_FLAG_FIN 0x01 // FIN 結束符 #define TCPCB_FLAG_SYN 0x02 // SYN 握手 #define TCPCB_FLAG_RST 0x04 // RST 重置 #define TCPCB_FLAG_PSH 0x08 // PSH 接收方要立即處理 #define TCPCB_FLAG_ACK 0x10 // ACK ack段有效 #define TCPCB_FLAG_URG 0x20 // URG 緊急指標 #define TCPCB_FLAG_ECE 0x40 // ECE 有擁塞情況(可能是傳播線路上的擁塞,例如路由器提供的資訊) #define TCPCB_FLAG_CWR 0x80 // CWR (發生某種擁塞,例如ICMP源抑制、本地裝置擁塞)
sack的標識:
#define TCPCB_SACKED_ACKED 0x01 // tcp的cb結構上是被sack確認的
#define TCPCB_SACKED_RETRANS 0x02 // 重傳幀
#define TCPCB_LOST 0x04 // 丟失
#define TCPCB_TAGBITS 0x07 // tag bits ?
#define TCPCB_EVER_RETRANS 0x80
#define TCPCB_RETRANS (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
OK,回到tcp_ack函式,函式的主要功能是:
1 更新重傳佇列。
2 更新發送視窗。
3 從sack的資訊或者重複ack來決定是否進入擁塞模式。
/* This routine deals with incoming acks, but not outgoing ones. */
static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
{
struct inet_connection_sock *icsk = inet_csk(sk); // 獲得連線sock
struct tcp_sock *tp = tcp_sk(sk); // 獲得tcp_sock
u32 prior_snd_una = tp->snd_una; // 獲得未傳送確認的序號
u32 ack_seq = TCP_SKB_CB(skb)->seq; // 獲得資料序號
u32 ack = TCP_SKB_CB(skb)->ack_seq; // 獲得ack序號(用於確認的序號)
u32 prior_in_flight;
u32 prior_fackets;
int prior_packets;
int frto_cwnd = 0;
/* If the ack is newer than sent or older than previous acks
* then we can probably ignore it.
*/
if (after(ack, tp->snd_nxt)) // 如果確認序號比我還下一個準備傳送的序號還要大,即確認了我們尚未傳送的資料,那麼顯然不合理
goto uninteresting_ack; // 沒有意義的ACK
if (before(ack, prior_snd_una)) // 如果ack確認比我期望的確認序號小,那麼可能是以前老的ack,丟棄!!!
goto old_ack; // 老的ack
if (after(ack, prior_snd_una)) // 如果ack確認比我期望的第一個ack要大,但是經過上面我們還知道沒有超過我沒有傳送的資料序號,範圍
flag |= FLAG_SND_UNA_ADVANCED; // 那麼設定標識~
if (sysctl_tcp_abc) { // 是否設定了tcp_abc,若有則我們不需要對每個ack確認都要擁塞避免,所以我們需要計算已經ack(確認)的位元組數。
if (icsk->icsk_ca_state < TCP_CA_CWR)
tp->bytes_acked += ack - prior_snd_una; // 已經(確定)ack的位元組數增大了( ack - prior_snd_una )大小
else if (icsk->icsk_ca_state == TCP_CA_Loss)
/* we assume just one segment left network */
tp->bytes_acked += min(ack - prior_snd_una,
tp->mss_cache);
}
prior_fackets = tp->fackets_out; // 得到fack的資料包的位元組數
prior_in_flight = tcp_packets_in_flight(tp); // 計算還在傳輸的資料段的位元組數,下面會說手這個函式!( 1 )
if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) { // 如果不是“慢路徑” && ack確認比其需要的第一個大(正確的確認序號)
/* Window is constant, pure forward advance.
* No more checks are required.
* Note, we use the fact that SND.UNA>=SND.WL2.
*/
tcp_update_wl(tp, ack, ack_seq); // 需要更新sock中的snd_wl1欄位:tp->snd_wl1 = ack_seq;( 記錄造成傳送視窗更新的第一個資料 )
tp->snd_una = ack; // snd_una更新為已經確認的序列號!下一次期待從這裡開始的確認!!!
flag |= FLAG_WIN_UPDATE; // 視窗更新標識
tcp_ca_event(sk, CA_EVENT_FAST_ACK); // 重要函式!!!進入擁塞操作!這個函式最後看,這裡處理的是“正常的ACK”事件(999999)
NET_INC_STATS_BH(LINUX_MIB_TCPHPACKS);
} else {
if (ack_seq != TCP_SKB_CB(skb)->end_seq) // 如果不相等,那麼說明還是帶有資料一起的~不僅僅是一個ACK的包
flag |= FLAG_DATA; // 說明還是有資料的~
else
NET_INC_STATS_BH(LINUX_MIB_TCPPUREACKS); // 否則僅僅是ACK的包
flag |= tcp_ack_update_window(sk, skb, ack, ack_seq); // 下面需要更新發送視窗~(2)
if (TCP_SKB_CB(skb)->sacked) // 然後判斷是否有sack段,有的話,我們進入sack段的處理。
flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una); // ~~~~~處理SACK(選擇確認),以後單獨解釋
if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb))) // 判斷是否有ecn標記,如果有的話,設定ecn標記。
flag |= FLAG_ECE; // ECE 也是用於判斷是否阻塞情況
tcp_ca_event(sk, CA_EVENT_SLOW_ACK); // 重要函式!!!進入擁塞操作!這個函式最後看,這裡處理“其他ACK”事件(999999)
}
/* We passed data and got it acked, remove any soft error
* log. Something worked...
*/
sk->sk_err_soft = 0;
tp->rcv_tstamp = tcp_time_stamp;
prior_packets = tp->packets_out; // 獲得發出去沒有收到確認的包數量
if (!prior_packets) // 如果為0,則可能是0視窗探測包
goto no_queue;
/* See if we can take anything off of the retransmit queue. */
flag |= tcp_clean_rtx_queue(sk, prior_fackets); // 清理重傳佇列中的已經確認的資料段。(3)
if (tp->frto_counter) // 處理F-RTO (4)
frto_cwnd = tcp_process_frto(sk, flag); // 處理超時重傳,暫時先不多說
/* Guarantee sacktag reordering detection against wrap-arounds */
if (before(tp->frto_highmark, tp->snd_una))
tp->frto_highmark = 0;
if (tcp_ack_is_dubious(sk, flag)) { // 判斷ack是否可疑,其實本質就是判斷可不可以增大擁塞視窗,下面會有詳細解釋(5)
/* Advance CWND, if state allows this. */
if ((flag & FLAG_DATA_ACKED) && !frto_cwnd && // 如果是資料確認包
tcp_may_raise_cwnd(sk, flag)) // 檢測flag以及是否需要update擁塞視窗的大小!!!(6)--->被懷疑也有可能增大視窗哦~~~
tcp_cong_avoid(sk, ack, prior_in_flight); // 為真則更新擁塞視窗,擁塞避免演算法(7)--->如果允許增大視窗,那麼擁塞演算法處理視窗
tcp_fastretrans_alert(sk, prior_packets - tp->packets_out, // 這裡進入擁塞狀態的處理,非常重要的函式(對於擁塞處理來說)!!!(8)
flag); // 處理完視窗變化之後,進入快速重傳處理!!!
} else { // 沒有被懷疑(說明是正常的確認ACK)
if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)
tcp_cong_avoid(sk, ack, prior_in_flight); // 如果沒有被懷疑,直接擁塞演算法處理視窗變化
}
if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP))
dst_confirm(sk->sk_dst_cache);
return 1;
no_queue:
icsk->icsk_probes_out = 0;
/* If this ack opens up a zero window, clear backoff. It was
* being used to time the probes, and is probably far higher than
* it needs to be for normal retransmission.
*/
if (tcp_send_head(sk)) // 這裡判斷髮送緩衝區是否為空,如果不為空,則進入判斷需要重啟keepalive定時器還是關閉定時器
tcp_ack_probe(sk); // 處理定時器函式~(暫時不看)
return 1;
old_ack:
if (TCP_SKB_CB(skb)->sacked) // 如果有sack標識
tcp_sacktag_write_queue(sk, skb, prior_snd_una); // 進入sack段處理(暫時不看)
uninteresting_ack:
SOCK_DEBUG(sk, "Ack %u out of %u:%u\n", ack, tp->snd_una, tp->snd_nxt);// 沒有意義的包~
return 0;
}
-----------------------------------------------------------------------------------------------------------------------------------------
看看這個函式:tcp_packets_in_flight,就是計算還在傳輸中的位元組數:
static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}
static inline unsigned int tcp_left_out(const struct tcp_sock *tp)
{
return tp->sacked_out + tp->lost_out;
}
我們從tcp_sock可以知道:其實就是返回 packets_out - sacked_out - lost_out + retrans_out,就是還沒有到達對方的資料段的位元組數
-----------------------------------------------------------------------------------------------------------------------------------------
下面我們看一下“傳送視窗”的更新tcp_ack_update_window:
static int tcp_ack_update_window(struct sock *sk, struct sk_buff *skb, u32 ack,
u32 ack_seq)
{
struct tcp_sock *tp = tcp_sk(sk); // 獲得tcp_sock
int flag = 0;
u32 nwin = ntohs(tcp_hdr(skb)->window); // 獲得skb傳送方的可以接收的視窗值
if (likely(!tcp_hdr(skb)->syn)) // 如果不是建立連線時候,即是普通傳遞資料時候,視窗縮放
nwin <<= tp->rx_opt.snd_wscale; // 接收方要求對視窗進行縮放
// 下面正式更新視窗
if (tcp_may_update_window(tp, ack, ack_seq, nwin)) { // 可能需要更新視窗大小,在函式tcp_may_update_window中實際處理(1)
flag |= FLAG_WIN_UPDATE; // 視窗更新成功
tcp_update_wl(tp, ack, ack_seq); // 更新snd_wl1
if (tp->snd_wnd != nwin) { // 如果傳送視窗!=縮放後的新視窗(注意skb傳送方的接收視窗和本tp的傳送視窗應該一致)
tp->snd_wnd = nwin; // 改變視窗值
/* Note, it is the only place, where
* fast path is recovered for sending TCP.
*/
tp->pred_flags = 0;
tcp_fast_path_check(sk); // 檢驗是否能夠開啟“快速路徑”(2)看下面
if (nwin > tp->max_window) { // 如果調整之後的視窗大於從對方接收到的最大的視窗值
tp->max_window = nwin; // 調整為小的
tcp_sync_mss(sk, inet_csk(sk)->icsk_pmtu_cookie); // 改變mss_cache
}
}
}
tp->snd_una = ack; // 下一個第一個需要確認就是所有當前已經確認序號之後~~~~
return flag;
}
下面看這個函式:tcp_may_update_window
/* Check that window update is acceptable.
* The function assumes that snd_una<=ack<=snd_next.
*/ // 這個函式是檢查視窗是否可變,只要確認ack在snd_una~下一個需要傳送的資料之間,就是需要改變視窗的!
static inline int tcp_may_update_window(const struct tcp_sock *tp,
const u32 ack, const u32 ack_seq,
const u32 nwin)
{
return (after(ack, tp->snd_una) || // snd_una<=ack
after(ack_seq, tp->snd_wl1) || // 看上面的視窗圖可以知道
(ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd)); // 調整的新視窗大於原始傳送視窗
}
看一下檢測是否可以進入“快速路徑”處理函式:tcp_fast_path_check
static inline void tcp_fast_path_check(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
if (skb_queue_empty(&tp->out_of_order_queue) &&
tp->rcv_wnd &&
atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
!tp->urg_data)
tcp_fast_path_on(tp);
}
能夠進入“快速路徑”處理的基本條件是:
1 :是否ofo(亂序包佇列)佇列為空,如果不為空也就是說有亂序資料不可以進入快速路徑。
2: 當前的接收視窗是否大於0.,如果不是,不可以進入。
3 :當前的已經提交的資料包大小是否小於接收緩衝區的大小,能夠放的下才可以進入快速路徑。
4: 是否含有urgent 資料,不含有才可以進入快速路徑。
再看看 為tp開啟快速路徑函式tcp_fast_path_on:
static inline void tcp_fast_path_on(struct tcp_sock *tp)
{
__tcp_fast_path_on(tp, tp->snd_wnd >> tp->rx_opt.snd_wscale);
}
static inline void __tcp_fast_path_on(struct tcp_sock *tp, u32 snd_wnd)
{
tp->pred_flags = htonl((tp->tcp_header_len << 26) | // 看見沒有。本質就是設定pred_flags變數
ntohl(TCP_FLAG_ACK) |
snd_wnd);
}
其實就是就是說標誌位除了ACK和PSH外,如果其他的存在的話,就不能用快速路徑!
-----------------------------------------------------------------------------------------------------------------------------------------
下面看進入擁塞操作函式tcp_ca_event:
有必要先看看擁塞控制結構體:
struct tcp_congestion_ops {
struct list_head list;
unsigned long flags;
/* initialize private data (optional) */
void (*init)(struct sock *sk); // 初始化
/* cleanup private data (optional) */
void (*release)(struct sock *sk); // 清除資料
/* return slow start threshold (required) */
u32 (*ssthresh)(struct sock *sk); // 返回慢開始門限
/* lower bound for congestion window (optional) */
u32 (*min_cwnd)(const struct sock *sk); // 擁塞視窗最小值
/* do new cwnd calculation (required) */
void (*cong_avoid)(struct sock *sk, u32 ack, u32 in_flight); // 計算新的擁塞視窗
/* call before changing ca_state (optional) */
void (*set_state)(struct sock *sk, u8 new_state); // 設定擁塞狀態
/* call when cwnd event occurs (optional) */
void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev); // 擁塞事件發生時候處理
/* new value of cwnd after loss (optional) */
u32 (*undo_cwnd)(struct sock *sk); // 丟包之後,擁塞視窗新的值
/* hook for packet ack accounting (optional) */
void (*pkts_acked)(struct sock *sk, u32 num_acked, s32 rtt_us); // 包的ack計數器
/* get info for inet_diag (optional) */
void (*get_info)(struct sock *sk, u32 ext, struct sk_buff *skb); //
char name[TCP_CA_NAME_MAX];
struct module *owner;
};
看一下TCP擁塞事件:
/* Events passed to congestion control interface */
enum tcp_ca_event {
CA_EVENT_TX_START, /* first transmit when no packets in flight */
CA_EVENT_CWND_RESTART, /* congestion window restart */
CA_EVENT_COMPLETE_CWR, /* end of congestion recovery */
CA_EVENT_FRTO, /* fast recovery timeout */
CA_EVENT_LOSS, /* loss timeout */
CA_EVENT_FAST_ACK, /* in sequence ack */
CA_EVENT_SLOW_ACK, /* other ack */
};
static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) // 第三個引數在上面的來說分別是:CA_EVENT_SLOW_ACK和CA_EVENT_FAST_ACK
{
const struct inet_connection_sock *icsk = inet_csk(sk); // 獲得連線sock
if (icsk->icsk_ca_ops->cwnd_event) // 注意這是一個函式指標:結構體struct tcp_congestion_ops中的!當擁塞事件發生時候執行
icsk->icsk_ca_ops->cwnd_event(sk, event); // 執行這個事件
}
擁塞視窗事件初始化在這:
if (icsk->icsk_ca_ops == &tcp_init_congestion_ops) { //////////////////////////// 初始化結構體
rcu_read_lock();
list_for_each_entry_rcu(ca, &tcp_cong_list, list) {
if (try_module_get(ca->owner)) {
icsk->icsk_ca_ops = ca;
break;
}
/* fallback to next available */
}
rcu_read_unlock();
}
struct tcp_congestion_ops tcp_init_congestion_ops = {
.name = "",
.owner = THIS_MODULE,
.ssthresh = tcp_reno_ssthresh,
.cong_avoid = tcp_reno_cong_avoid,
.min_cwnd = tcp_reno_min_cwnd,
};
這有點問題!對於這個函式沒有進行實現?還是我沒有找到處理賦值的地方?無語了~~~~~~~~~~~~~~~~~~~~~~~~
或許這裡面初始化:下面是關於cwnd_event所有的初始化
static struct tcp_congestion_ops tcp_veno = {
.flags = TCP_CONG_RTT_STAMP,
.init = tcp_veno_init,
.ssthresh = tcp_veno_ssthresh,
.cong_avoid = tcp_veno_cong_avoid,
.pkts_acked = tcp_veno_pkts_acked,
.set_state = tcp_veno_state,
.cwnd_event = tcp_veno_cwnd_event, ///////////////////////////////////////////////
.owner = THIS_MODULE,
.name = "veno",
};
static struct tcp_congestion_ops tcp_vegas = {
.flags = TCP_CONG_RTT_STAMP,
.init = tcp_vegas_init,
.ssthresh = tcp_reno_ssthresh,
.cong_avoid = tcp_vegas_cong_avoid,
.min_cwnd = tcp_reno_min_cwnd,
.pkts_acked = tcp_vegas_pkts_acked,
.set_state = tcp_vegas_state,
.cwnd_event = tcp_vegas_cwnd_event, ///////////////////////////////////////////////
.get_info = tcp_vegas_get_info,
.owner = THIS_MODULE,
.name = "vegas",
};
static struct tcp_congestion_ops tcp_yeah = {
.flags = TCP_CONG_RTT_STAMP,
.init = tcp_yeah_init,
.ssthresh = tcp_yeah_ssthresh,
.cong_avoid = tcp_yeah_cong_avoid,
.min_cwnd = tcp_reno_min_cwnd,
.set_state = tcp_vegas_state,
.cwnd_event = tcp_vegas_cwnd_event, ////////////////////////////////////////////////
.get_info = tcp_vegas_get_info,
.pkts_acked = tcp_yeah_pkts_acked,
.owner = THIS_MODULE,
.name = "yeah",
};
-------------------------------------------------------------------------------------------------------------------------------------------
下面清理重傳佇列中已經確認的資料,看函式tcp_clean_rtx_queue:
---------------------------------------------------------------------------------------------------------------------------------現在可以看一下tcp_ack_is_dubious函式,來判斷是不是進入了擁塞狀態:
先可以看一下狀態的定義:
#define FLAG_DATA 0x01 /* Incoming frame contained data. */ // 來了一個包含資料的包
#define FLAG_WIN_UPDATE 0x02 /* Incoming ACK was a window update. */ // 來了一個ACK用於更新視窗
#define FLAG_DATA_ACKED 0x04 /* This ACK acknowledged new data. */ // 對於資料的確認
#define FLAG_RETRANS_DATA_ACKED 0x08 /* "" "" some of which was retransmitted. */ // 對於重傳資料的確認
#define FLAG_SYN_ACKED 0x10 /* This ACK acknowledged SYN. */ // 對於SYN的確認
#define FLAG_DATA_SACKED 0x20 /* New SACK. */ // 這是對資料的一個選擇確認
#define FLAG_ECE 0x40 /* ECE in this ACK */ // 確認中旅帶有ECE資訊
#define FLAG_DATA_LOST 0x80 /* SACK detected data lossage. */ // SACK檢測到資料丟失
#define FLAG_SLOWPATH 0x100 /* Do not skip RFC checks for window update.*/ // slowpath,需要做一些檢查
#define FLAG_ONLY_ORIG_SACKED 0x200 /* SACKs only non-rexmit sent before RTO */
#define FLAG_SND_UNA_ADVANCED 0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED) */ // snd-una改變
#define FLAG_DSACKING_ACK 0x800 /* SACK blocks contained D-SACK info */ // 包含DSACK資訊
#define FLAG_NONHEAD_RETRANS_ACKED 0x1000 /* Non-head rexmitted data was ACKed */
#define FLAG_SACK_RENEGING 0x2000 /* snd_una advanced to a sacked seq */ // snd_una移動到一個sack中的一個位置
#define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED) // 表示資料確認或者SYN確認
#define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED) // 表示ACK是不重複的
#define FLAG_CA_ALERT (FLAG_DATA_SACKED|FLAG_ECE) // 表示是否在進入擁塞狀態的時候被alert(原因可能是SACK丟包或者路由器提示擁塞)
#define FLAG_FORWARD_PROGRESS (FLAG_ACKED|FLAG_DATA_SACKED) // 選擇確認
再看一下:
TCP_CA_Open:TCP連線的初始化的狀態。TCP連線會在慢啟動和擁塞避免階段(呼叫tcp_cong_avoid)增加擁塞視窗。每個接收到的ACK都要呼叫tcp_ack_is_dubious,檢查它是否可疑。如果是ACK可疑,就呼叫 tcp_fastretrans_alert()就切換到其他CA擁塞狀態。但是對於可疑的ACK,若視窗也允許增大(tcp_may_raise_cwnd),那麼(tcp_fastretrans_alert)仍然可能增大擁塞視窗。
TCP_CA_Disorder:注意如果收到重複的ACK或者SACK,那麼可能出現亂序情況,進入這個狀態處理。
TCP_CA_CWR:表示發生某些道路擁塞,需要減慢傳送速度。
TCP_CA_Recovery:正在進行快速重傳丟失的資料包。
TCP_CA_Loss:超時重傳情況下,如果接收到的ACK與SACK資訊不一樣,則阻塞丟包狀態。
static inline int tcp_ack_is_dubious(const struct sock *sk, const int flag)
{
return (!(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) || // 是重複的ACK 或者 在進入擁塞狀態的時候出現警告
inet_csk(sk)->icsk_ca_state != TCP_CA_Open); // 或者擁塞狀態不是“增大擁塞視窗”狀態
} // 則這個ACK是可疑的,其實意思就是,不是一個正常的ACK,不能隨便增大擁塞視窗
下面就兩條路:
1:如果被懷疑
2:如果沒有被懷疑
先看如果被懷疑了,那麼:
先看函式:tcp_may_raise_cwnd
static inline int tcp_may_raise_cwnd(const struct sock *sk, const int flag)
{
const struct tcp_sock *tp = tcp_sk(sk);
return (!(flag & FLAG_ECE) || tp->snd_cwnd < tp->snd_ssthresh) && // 沒有其他阻塞 或者 (傳送視窗小於門限&&不是Recovery ,也不是CWR)
!((1 << inet_csk(sk)->icsk_ca_state) & (TCPF_CA_Recovery | TCPF_CA_CWR)); // 那麼這樣還是可以增大視窗的嘛~~~~~ ^_^
}
如果可以增大視窗,那麼就需要使用tcp_cong_avoid執行這個函式用來實現慢啟動和快速重傳擁塞避免演算法:
這個函式也是在“沒有被懷疑”的情況下執行的函式,所以
如果沒有被懷疑,執行的也是tcp_cong_avoid,一起解釋:
static void tcp_cong_avoid(struct sock *sk, u32 ack, u32 in_flight)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
icsk->icsk_ca_ops->cong_avoid(sk, ack, in_flight); // 這才是重要處理函式
tcp_sk(sk)->snd_cwnd_stamp = tcp_time_stamp; // 傳送視窗改變時間戳
}
我們看到上面說的擁塞結構體的初始化:
struct tcp_congestion_ops tcp_init_congestion_ops = {
.name = "",
.owner = THIS_MODULE,
.ssthresh = tcp_reno_ssthresh,
.cong_avoid = tcp_reno_cong_avoid, //////////////////////這個函式
.min_cwnd = tcp_reno_min_cwnd,
};
那麼實際執行的就是tcp_reno_cong_avoid函式!!!
----------------------------------------------------------------------------------------------------------------------------------
OK,下面再看看tcp_fastretrans_alert函式:TCP擁塞狀態機主要是在tcp_fastretrans_alert()中實現的,只有在ACK被懷疑的時候才會執行這個提醒函式
此函式被呼叫的條件也就是懷疑的條件:
1:進來一個ACK,但是狀態不是 Open
2:收到的是 SACK 、Duplicate ACK、ECN、ECE 等警告資訊
到此為止,處理接收到的ACK基本結束。。。。