linux内核协议栈 TCP数据接收之清除发送队列 + RTT采样

目录

1 清除发送队列 tcp_clean_rtx_queue()

1.1 处理TSO段部分确认 tcp_tso_acked


1 清除发送队列 tcp_clean_rtx_queue()

在ACK的确认过程中,需要做的非常重要的一件事就是将已经确认的数据从发送队列(以及重传队列)中删除,这是通过tcp_clean_rtx_queue()完成的。此外由于TCP在实现过程中,发送队列和重传队列都是sk_write_queue,所以这两个队列是一并处理的。

/* Remove acknowledged frames from the retransmission queue. If our packet
 * is before the ack sequence we can discard it as it's confirmed to have
 * arrived at the other end.
 */
static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets)
{
	struct tcp_sock *tp = tcp_sk(sk);
	const struct inet_connection_sock *icsk = inet_csk(sk);
	struct sk_buff *skb;
	u32 now = tcp_time_stamp;
	//如果为1,表示skb中所有序号都被确认了,否则只是部分确认
	int fully_acked = 1;
	int flag = 0;
	//累计本次清除操作清除的TSO段数
	u32 pkts_acked = 0;
	u32 reord = tp->packets_out;
	s32 seq_rtt = -1;
	s32 ca_seq_rtt = -1;
	ktime_t last_ackt = net_invalid_timestamp();
 
	//遍历发送队列中所有已发送的数据段
	while ((skb = tcp_write_queue_head(sk)) && skb != tcp_send_head(sk)) {
		struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
		//记录当前skb能够被确认的最大序号(的下一个序号)
		u32 end_seq;
		//记录当前skb中能够被ACK的TSO段数
		u32 acked_pcount;
		//sacked是当前skb的记分牌,它记录了skb的重传、SACK、丢失信息
		u8 sacked = scb->sacked;
 
		//如果当前skb的右边界大于等于snd_una(该变量已经更新),
		//说明当前skb最多只能被确认一部分
		if (after(scb->end_seq, tp->snd_una)) {
			//如果当前skb只有一个TSO段(如果是大包那么tcp_gso_segs肯定不为1),或者这一个段的左边界也大于等于snd_una,
			//说明当前skb的所有数据都不能被确认,进而队列中后续skb也不用判断了,结束遍历
			if (tcp_skb_pcount(skb) == 1 || !after(tp->snd_una, scb->seq))
				break;
			//调用tcp_tso_acked()将当前skb中能够确认的数据删除,返回能被确认的TSO段的个数
			acked_pcount = tcp_tso_acked(sk, skb);
			//如果一个TSO段都没有被确认,没有必要继续下去了,结束遍历过程
			if (!acked_pcount)
				break;
			//只能确认当前skb前面的一部分数据,所以fully_akced设置为0
			fully_acked = 0;
			end_seq = tp->snd_una;
		} else {
			//当前skb的所有数据都能被确认,acked_pcount为skb的TSO段数
			acked_pcount = tcp_skb_pcount(skb);
			end_seq = scb->end_seq;
		}
 
		/* MTU probing checks */
		//PMTU相关
		if (fully_acked && icsk->icsk_mtup.probe_size &&
		    !after(tp->mtu_probe.probe_seq_end, scb->end_seq)) {
			tcp_mtup_probe_success(sk, skb);
		}
 
		//下面这段是用于RTT采样的,不在这里详述
		if (sacked & TCPCB_RETRANS) {
			//如果skb被重传过,递减重传计数器
			if (sacked & TCPCB_SACKED_RETRANS)
				tp->retrans_out -= acked_pcount;
			flag |= FLAG_RETRANS_DATA_ACKED;
			ca_seq_rtt = -1;
			seq_rtt = -1;
			if ((flag & FLAG_DATA_ACKED) || (acked_pcount > 1))
				flag |= FLAG_NONHEAD_RETRANS_ACKED;
		} else {
			ca_seq_rtt = now - scb->when;
			last_ackt = skb->tstamp;
			if (seq_rtt < 0) {
				seq_rtt = ca_seq_rtt;
			}
			if (!(sacked & TCPCB_SACKED_ACKED))
				reord = min(pkts_acked, reord);
		}
 
		//如果当前skb被SACK确认过,现在又被累计ACK确认了,它的被确认部分已不
		//在[snd_una, snd_nxt)之间,所以可以递减sacked_out。下面同理递减
		//lost_out、packets_out.
		if (sacked & TCPCB_SACKED_ACKED)
			tp->sacked_out -= acked_pcount;
		if (sacked & TCPCB_LOST)
			tp->lost_out -= acked_pcount;
 
		if (unlikely(tp->urg_mode && !before(end_seq, tp->snd_up)))
			tp->urg_mode = 0;
		tp->packets_out -= acked_pcount;
		//累加已确认TSO段数
		pkts_acked += acked_pcount;
 
		/* Initial outgoing SYN's get put onto the write_queue
		 * just like anything else we transmit.  It is not
		 * true data, and if we misinform our callers that
		 * this ACK acks real data, we will erroneously exit
		 * connection startup slow start one packet too
		 * quickly.  This is severely frowned upon behavior.
		 */
		//如注释所述,对数据和SYN段的确认要区分,否则会影响慢启动时的初始窗口大小
		if (!(scb->flags & TCPCB_FLAG_SYN)) {
			flag |= FLAG_DATA_ACKED;
		} else {
			flag |= FLAG_SYN_ACKED;
			tp->retrans_stamp = 0;
		}
		//如果当前skb只确认了部分TSO段,结束遍历
		if (!fully_acked)
			break;
		//当前skb的所有数据已经被确认,将其从发送队列移除(这并不意味着skb已经被释放,
		tcp_unlink_write_queue(skb, sk);

		//释放skb内存
		sk_wmem_free_skb(sk, skb);
		tcp_clear_all_retrans_hints(tp);
	}//end of while
 
	//下面的逻辑已经跳出了循环
 
	//如果之前收到过最后一个skb的SACK确认信息,但是此时累加确认号却没有涵盖该skb,那么
	//说明对端在发送SACK之后又将其删除了,这种属于假的SACK,设置FLAG_SACK_RENEGING
	if (skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
		flag |= FLAG_SACK_RENEGING;
 
	//下面是RTT采样和拥塞控制相关内容,不在这里详述
	if (flag & FLAG_ACKED) {
		const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops;
 
		tcp_ack_update_rtt(sk, flag, seq_rtt);
		tcp_rearm_rto(sk);
 
		if (tcp_is_reno(tp)) {
			tcp_remove_reno_sacks(sk, pkts_acked);
		} else {
			/* Non-retransmitted hole got filled? That's reordering */
			if (reord < prior_fackets)
				tcp_update_reordering(sk, tp->fackets_out - reord, 0);
		}
 
		tp->fackets_out -= min(pkts_acked, tp->fackets_out);
 
		if (ca_ops->pkts_acked) {
			s32 rtt_us = -1;
 
			/* Is the ACK triggering packet unambiguous? */
			if (!(flag & FLAG_RETRANS_DATA_ACKED)) {
				/* High resolution needed and available? */
				if (ca_ops->flags & TCP_CONG_RTT_STAMP &&
				    !ktime_equal(last_ackt, net_invalid_timestamp()))
					rtt_us = ktime_us_delta(ktime_get_real(),
								last_ackt);
				else if (ca_seq_rtt > 0)
					rtt_us = jiffies_to_usecs(ca_seq_rtt);
			}
 
			ca_ops->pkts_acked(sk, pkts_acked, rtt_us);
		}
	}
	return flag;
}

综上,可以看出,tcp_clean_rtx_queue()实际上做了两件事:

  1. 清除发送队列和重传队列;
  2. RTT采样。

1.1 处理TSO段部分确认 tcp_tso_acked

当发现一个TSO数据段没有被全部确认时,需要调用tcp_tso_acked()进行已确认数据的删除,最后返回已处理数据的段数。

/* If we get here, the whole TSO packet has not been acked. */
static u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	u32 packets_acked;

	//必须是部分确认
	BUG_ON(!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una));

	//初始化为数据段总数
	packets_acked = tcp_skb_pcount(skb);
	//tcp-trim_head()的核心工作是将skb的首部删除指定长度的数据。同时还要更新skb中
	//的TSO信息,以及TCB的内存记账信息,这里不再展开。该函数有可能会因为内存操作失败,
	//这种情况则无法将该skb的部分数据删除,只能等到该skb的所有数据都确认后统一删除
	if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))
		return 0;
	//减去新的skb中遗留的数据段数,就是本次已经确认的段数
	packets_acked -= tcp_skb_pcount(skb);

	if (packets_acked) {
		BUG_ON(tcp_skb_pcount(skb) == 0);
		BUG_ON(!before(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq));
	}
	//返回已确认的段数
	return packets_acked;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值