1.排隊機制 接收輸入TCP報文時,有三個隊列: ● 待處理隊列 ● 預排隊隊列 ● 接收隊列 接收隊列包含了處理過的TCP數據段,也就是說,去除了全部的協議頭,正準備將數據複製到用戶應用程式。接收隊列包含了所有按順序接收的數據段,在其他兩個隊列中的TCP數據段則需要進一步處理。 TCP報文首先由t ...
1.排隊機制
接收輸入TCP報文時,有三個隊列:
● 待處理隊列
● 預排隊隊列
● 接收隊列
接收隊列包含了處理過的TCP數據段,也就是說,去除了全部的協議頭,正準備將數據複製到用戶應用程式。接收隊列包含了所有按順序接收的數據段,在其他兩個隊列中的TCP數據段則需要進一步處理。
TCP報文首先由tcp_v4_rcv()進行處理。該函數要決定是否需要處理報文或者在待處理隊列和預排隊隊列中排隊。
/* 傳輸層報文處理入口 */ int tcp_v4_rcv(struct sk_buff *skb) { ... /*首先獲取一個套接字旋轉鎖,當進入該常式時,要禁用下半部功能,因為該 常式是從NET SoftIRQ中斷調用的。*/ bh_lock_sock(sk);/* 在軟中斷中對套介面加鎖 */ ret = 0; /* 如果進程沒有訪問傳輸控制塊,則進行正常接收 */ /*接著檢查套接字是否處於使用狀態。當有人在使用該套接字時,(sk)->sk_lock.owner 為1.當對套接字執行讀、寫、修改操作時,套接字就會處於使用狀態。*/ if (!sock_owned_by_user(sk)) { /*調用tcp_prequeue將該TCP報文發往預排隊隊列。*/ if (!tcp_prequeue(sk, skb)) /*如果無法將TCP排隊,就直接處理該數據段。*/ ret = tcp_v4_do_rcv(sk, skb); } else /* 將報文添加到後備隊列中,待用戶進程解鎖控制塊時處理 */ sk_add_backlog(sk, skb); bh_unlock_sock(sk); ... }
2.tcp_rcv_established()的處理
這裡不介紹全部處理細節,僅介紹處理和排隊機制。首先討論直接將數據複製到用戶緩衝區的可能性。如果不可能,就去除TCP頭,將數據段發往接收隊列排隊。
/* 當連接已經正常建立時,處理接收到的TCP報文 */ int tcp_rcv_established(struct sock *sk, struct sk_buff *skb, struct tcphdr *th, unsigned len) { struct tcp_sock *tp = tcp_sk(sk); ... } else {/* 有數據負荷 */ int eaten = 0; /* 判斷正在接收的段是否可以直接複製到用戶空間 */ /* 正在接收的段的序號是否與尚未從內核空間複製到用戶空間的段最 前面的序號相等,即接收隊列應當是空的 */ /* TCP段中的用戶數據長度小於用戶空間緩存的剩餘可用量 */ if (tp->ucopy.task == current && tp->copied_seq == tp->rcv_nxt && len - tcp_header_len <= tp->ucopy.len && sock_owned_by_user(sk)) {/* 鎖被當前進程持有 */ __set_current_state(TASK_RUNNING); /* 將SKB的數據複製到用戶緩衝區 */ if (!tcp_copy_to_iovec(sk, skb, tcp_header_len)) { if (tcp_header_len == (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) && tp->rcv_nxt == tp->rcv_wup)/* 更新時間戳 */ tcp_store_ts_recent(tp); tcp_rcv_rtt_measure_ts(tp, skb);/* 更新往返時間 */ __skb_pull(skb, tcp_header_len); /* 下一個預期接收的段序號 */ /*將tp->rcv_nxt更新為已處理報文的結束序列號。*/ tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq; NET_INC_STATS_BH(LINUX_MIB_TCPHPHITSTOUSER); eaten = 1; } } if (!eaten) { ... /* 移動指針,跳過TCP頭部,也就是去除TCP頭 */ __skb_pull(skb,tcp_header_len); /* 將數據包添加到接收隊列中緩存起來,等待進程主動讀取 */ __skb_queue_tail(&sk->sk_receive_queue, skb); /* 設置skb的屬主為當前套口,更新使用的接收緩存總量及預分配緩存長度 */ sk_stream_set_owner_r(skb, sk); /* 更新rcv_nxt為該分段的結束序列號 */ tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq; } ... return 0; }
3.
內容太多了,未完待續