El procesamiento ACK del protocolo del kernel de linux apila la recepción de datos TCP

Tabla de contenido

1 Descripción general de la respuesta ACK

2 ACK procesando tcp_ack ()

2.1 Bandera de parámetro

2.2 flujo de procesamiento de tcp_ack ()

2.2.1 Actualizar ventana de envío

2.2.2 Eliminar el segmento de datos confirmado + muestreo RTT

2.2.3 Control de congestión


1 Descripción general de la respuesta ACK

Durante el procesamiento del segmento de datos de entrada de TCP, si se encuentra que el segmento de entrada transporta información ACK, se llamará a tcp_ack () para el procesamiento relacionado con ACK. En la práctica, la información ACK siempre será transportada, porque la transmisión ACK no requiere ninguna sobrecarga adicional, por lo que para cada segmento de la entrada (excepto para segmentos especiales como RST [desconexión de enlace directo]), este proceso siempre se realiza. Echemos un vistazo al procesamiento TCP de la confirmación ACK en esta nota.

2 ACK procesando tcp_ack ()

2.1 Bandera de parámetro

tcp_ack () tiene una bandera de parámetro muy importante, que recorre todo el proceso de procesamiento de ACK. Registra cualquier información que se pueda obtener de la sección de entrada (como si se llevan datos, si ACK se repite, si es SACK, etc.). Consulte las siguientes operaciones, como control de congestión y muestreo RTT. La bandera puede ser una combinación de los siguientes valores.

bandera valor descripción
FLAG_DATA 0x01 El segmento ACK transporta datos
FLAG_WIN_UPDATE 0x02 Después de recibir el segmento ACK, la ventana de envío se actualiza, el límite izquierdo se puede actualizar o el límite derecho se puede actualizar (la ventana de notificación se agranda)
FLAG_DATA_ACKED 0x04 El segmento ACK confirma nuevos datos
FLAG_RETRANS_DATA_ACKED 0x08 Se han recibido los datos transportados en el segmento ACK
FLAG_SYN_ACKED 0x10 El segmento ACK confirma el segmento SYN
FLAG_DATA_SACKED 0x20 El segmento ACK confirma los nuevos datos
FLAG_ECE 0x40 El segmento ACK lleva la bandera ECE
FLAG_DATA_LOST 0x80 SACK detectó pérdida de datos
FLAG_SLOWPATH 0x100 El segmento ACK es procesado por la ruta lenta
FLAG_ONLY_ORIG_SACKED 0x200  
FLAG_SND_UNA_ADVANCED 0x400 El segmento ACK ha actualizado snd_una, es decir, después de recibir el ACK, el límite izquierdo de la ventana de envío se puede mover hacia la derecha.
FLAG_DSACKING_ACK 0x800 El segmento ACK contiene información DSACK
FLAG_NONHEAD_RETRANS_ACKED 0x1000  
FLAG_SACK_RENEGING 0x2000 Se detecta que el segmento de datos confirmado por el SACK anterior es descartado por el extremo opuesto (esto lo permite el protocolo)

Además, se definen algunas combinaciones básicas de banderas:

#define FLAG_ACKED		(FLAG_DATA_ACKED|FLAG_SYN_ACKED)
//用于判断输入的数据段是否为重复段
#define FLAG_NOT_DUP		(FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)
#define FLAG_CA_ALERT		(FLAG_DATA_SACKED|FLAG_ECE)
#define FLAG_FORWARD_PROGRESS	(FLAG_ACKED|FLAG_DATA_SACKED)
#define FLAG_ANY_PROGRESS	(FLAG_FORWARD_PROGRESS|FLAG_SND_UNA_ADVANCED)

2.2 flujo de procesamiento de tcp_ack ()

La operación principal de tcp_ack () hace tres cosas:

  1. Actualice la ventana de envío;
  2. Borre los datos confirmados (incluidos los datos de retransmisión) en la cola de envío y realice un muestreo RTT;
  3. Realizar control de congestión.
/* 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);
	struct tcp_sock *tp = tcp_sk(sk);
	//TCB中尚未被确认的最小序号
	u32 prior_snd_una = tp->snd_una;
	//ACK段中的序号
	u32 ack_seq = TCP_SKB_CB(skb)->seq;
	//ACK段中的确认号
	u32 ack = TCP_SKB_CB(skb)->ack_seq;
	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,也有可能是正常的,比如该AC段有延时。
	//这种ACK有可能还携带了有效的SACK信息
	if (before(ack, prior_snd_una))
		goto old_ack;

	//到这里,说明确认号在期望的范围内[snd_una, snd_nxt],

	//确认号确认了新数据,设置FLAG_SND_UNA_ADVANCED标记。
	//if判断是为了排除ack==prior_snd_una的情况
	if (after(ack, prior_snd_una))
		flag |= FLAG_SND_UNA_ADVANCED;

	//tcp_abc特性相关
	if (sysctl_tcp_abc) {
		if (icsk->icsk_ca_state < TCP_CA_CWR)
			tp->bytes_acked += 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;
	prior_in_flight = tcp_packets_in_flight(tp);

	//下面是更新发送窗口,按照快速路径和慢速路径分别处理
	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.
		 */
		//记录最近一次导致发送窗口更新的ACK段的序号,即tp->snd_wl1=ack_seq
		tcp_update_wl(tp, ack, ack_seq);
		//更新发送窗口左边界
		tp->snd_una = ack;
		//设置发送窗口更新标记
		flag |= FLAG_WIN_UPDATE;
		//通知拥塞控制算法,发生了CA_EVENT_FAST_ACK事件
		tcp_ca_event(sk, CA_EVENT_FAST_ACK);

		NET_INC_STATS_BH(LINUX_MIB_TCPHPACKS);
	} else {
		//慢速路径处理

		//ACK段还携带了数据,设置FLAG_DATA标记
		if (ack_seq != TCP_SKB_CB(skb)->end_seq)
			flag |= FLAG_DATA;
		else
			NET_INC_STATS_BH(LINUX_MIB_TCPPUREACKS);
		//更新发送窗口
		flag |= tcp_ack_update_window(sk, skb, ack, ack_seq);

		//SACK相关处理
		if (TCP_SKB_CB(skb)->sacked)
			flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una);
		//ECN相关处理
		if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb)))
			flag |= FLAG_ECE;
		//通知拥塞控制算法,发生了CA_EVENT_SLOW_ACK事件
		tcp_ca_event(sk, CA_EVENT_SLOW_ACK);
	}

	//清除软件错误
	sk->sk_err_soft = 0;
	//更新最近一次接收到ACK段的时间戳
	tp->rcv_tstamp = tcp_time_stamp;
	//如果之前根本就没有待确认的段,那么无需后续的重传队列以及拥塞控制处理;
	//这种情况下需要做和持续定时器相关的操作,因为可能之前传送过探测报文
	prior_packets = tp->packets_out;
	if (!prior_packets)
		goto no_queue;

	//删除重传队列中已经确认的数据段,并进行时延采样
	flag |= tcp_clean_rtx_queue(sk, prior_fackets);

	//F-RTO算法相关内容
	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)) {
		/* Advance CWND, if state allows this. */
		if ((flag & FLAG_DATA_ACKED) && !frto_cwnd &&
		    tcp_may_raise_cwnd(sk, flag))
			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)
			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:
	//之前没有未被确认的段,收到了ACK,进行持续定时器相关处理
	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))
		tcp_ack_probe(sk);
	return 1;

old_ack:
	//虽然该ACK已经收到过了,但是如果其携带了SACK信息,需要更新确认内容
	if (TCP_SKB_CB(skb)->sacked)
		tcp_sacktag_write_queue(sk, skb, prior_snd_una);

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

2.2.1 Actualizar ventana de envío

"Ventana de transmisión de datos TCP de la pila del protocolo del kernel de Linux"

2.2.2 Eliminar el segmento de datos confirmado + muestreo RTT

"Los datos TCP de la pila del protocolo del kernel de Linux reciben una cola de envío clara + muestreo RTT"

2.2.3 Control de congestión

 

Supongo que te gusta

Origin blog.csdn.net/wangquan1992/article/details/109033972
Recomendado
Clasificación