Linux内核TCP收到ACK的处理

TCP层每次收到一个ACK的报文就会进入这个函数做决策。

先直接上注释的代码:

static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	//这两行是几乎每个函数都有的,获取tcp_sock结构体,得到当前tcp流的信息
	u32 prior_snd_una = tp->snd_una;
	//将最小未确认的序号保存到prior_snd_una
	u32 ack_seq = TCP_SKB_CB(skb)->seq;
	//ack包的序号
	u32 ack = TCP_SKB_CB(skb)->ack_seq;
	//ack确认的序号
	u32 prior_in_flight;
	//之前的in_flight包
	u32 prior_fackets;
	int prior_packets;
	int frto_cwnd = 0;

	/* If the ack is older than previous acks
	 * then we can probably ignore it.
	 */

	if (before(ack, prior_snd_una))
		goto old_ack;
	 //ack包的序号比最小未确认的序号还要小的话,则认为则是老的ack

	/* If the ack includes data we haven't sent yet, discard
	 * this segment (RFC793 Section 3.9).
	 */

	if (after(ack, tp->snd_nxt))
		goto invalid_ack;
	 //如果ack序号比要发的数据包序号还大,则丢弃
	if (after(ack, prior_snd_una))
		flag |= FLAG_SND_UNA_ADVANCED;
	//如果序号>=最小为确认的包,则flag置FLAG_SND_UNA_ADVANCED,这里使用的是flag与FLAG_SND_UNA_ADVANCED相或,实际就是对应位置一

	if (sysctl_tcp_abc) {//如果启用abc机制
		if (icsk->icsk_ca_state < TCP_CA_CWR)//OPEN和DISORDER状态
			tp->bytes_acked += ack - prior_snd_una;
			//这个ack取人的字节数
		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;
	prior_in_flight = tcp_packets_in_flight(tp);
	//packets_out - sacked_out- lost_out + retrans_out 
	飞行中的包 = 发出的未确认的 - 接收方通知丢掉的 - 超时丢掉的 + 重传发出的

	//SLOWPATH处理 不太懂
	if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) {
		/* 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_seq);
		tp->snd_una = ack;
		flag |= FLAG_WIN_UPDATE;

		tcp_ca_event(sk, CA_EVENT_FAST_ACK);

		NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPACKS);
	} else {
		if (ack_seq != TCP_SKB_CB(skb)->end_seq)
			flag |= FLAG_DATA;
		else
			NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPUREACKS);

		flag |= tcp_ack_update_window(sk, skb, ack, ack_seq);

		if (TCP_SKB_CB(skb)->sacked)
			flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una);

		if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb)))
			flag |= FLAG_ECE;

		tcp_ca_event(sk, CA_EVENT_SLOW_ACK);
	}

	/* We passed data and got it acked, remove any soft error
	 * log. Something worked...
	 */
	sk->sk_err_soft = 0;
	icsk->icsk_probes_out = 0;
	tp->rcv_tstamp = tcp_time_stamp;
	prior_packets = tp->packets_out;
	if (!prior_packets)
		goto no_queue;//如果没发数据却收到了ACK,则证明是零窗口通知,跳转到no_queue

	/* See if we can take anything off of the retransmit queue. */
	flag |= tcp_clean_rtx_queue(sk, prior_fackets, prior_snd_una);
	//尝试清除重传队列里面已经确认的包

	if (tp->frto_counter)
		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一定是重复的、SACKed或ECE、或者不在TCP_CA_OPEN阶段就可以说是dubious 可疑的
		/* Advance CWND, if state allows this. */
		if ((flag & FLAG_DATA_ACKED) && !frto_cwnd &&
		    tcp_may_raise_cwnd(sk, flag))
                    //tcp_may_raise_cwnd是判断是否可以增窗,在后面再详细解释
			tcp_cong_avoid(sk, ack, prior_in_flight);//调用增窗函数
			tcp_fastretrans_alert(sk, prior_packets - tp->packets_out,flag);
                        //这个函数是拥塞控制状态机的核心
	} else {
		if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)
                //不可疑的ACK,并且是确认新数据,则增窗,不进入tcp_fastretrans_alert
			tcp_cong_avoid(sk, ack, prior_in_flight);
	}
	//neigh算法
	if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP))
		dst_confirm(sk->sk_dst_cache);

	return 1;

no_queue:
	/* 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))
		tcp_ack_probe(sk);
	return 1;

invalid_ack:
	SOCK_DEBUG(sk, "Ack %u after %u:%u\n", ack, tp->snd_una, tp->snd_nxt);
	return -1;

old_ack:
	if (TCP_SKB_CB(skb)->sacked) {
		tcp_sacktag_write_queue(sk, skb, prior_snd_una);
		if (icsk->icsk_ca_state == TCP_CA_Open)
			tcp_try_keep_open(sk);
	}

	SOCK_DEBUG(sk, "Ack %u before %u:%u\n", ack, tp->snd_una, tp->snd_nxt);
	return 0;
}

下面来看看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) &&
		!((1 << inet_csk(sk)->icsk_ca_state) & (TCPF_CA_Recovery | TCPF_CA_CWR));
}

实际上就是在判断下列条件为真时return 1

  • 非ECE包
  • 处于慢启动并且非CWR或Recovery状态

再来看看负责增窗的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);
        //调用当前拥塞协议的cong_avoid函数,在本身协议里面没有具体实现
	tcp_sk(sk)->snd_cwnd_stamp = tcp_time_stamp;
        //记录一下增窗时间
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值