TSO / GSO da pilha de protocolo do kernel linux transmissão de dados da camada TCP

índice

1 Conceitos básicos

2 julgamento de segmentação de atraso TCP

2.1 Inicialização do cliente

2.2 Inicialização do lado do servidor

2.3 sk_setup_caps ()

3 Estrutura geral

4. Processamento de TSO do caminho de envio TCP

4,1 tcp_sendmsg ()

4.1.1 tcp_current_mss

4.2 tcp_write_xmit ()

4.2.1 tcp_init_tso_segs ()

4.2.2 tso_fragment ()


O conteúdo relacionado ao TSO é inundado com todo o processo de envio do TCP e entender seu mecanismo é crucial para entender o processo de envio do TCP.

1 Conceitos básicos

Sabemos que a quantidade máxima de dados que um dispositivo de rede pode transmitir por vez é o MTU, ou seja, cada pacote de dados transmitido por IP para o dispositivo de rede não pode exceder bytes de MTU. A função de segmentação e remontagem da camada IP é se adaptar ao MTU do dispositivo de rede. existir. Teoricamente falando, o TCP não se preocupa com a limitação do MTU. Ele só precisa descartar os pacotes de dados para IP à vontade, de acordo com seus próprios desejos. Se a fragmentação é necessária, pode ser tratada de forma transparente por IP, mas porque o TCP é uma transmissão de streaming confiável, se É responsável pela transmissão na camada IP. Uma vez que apenas a primeira mensagem IP contém TCP, se a mensagem TCP subsequente se perder durante a transmissão, as duas partes da comunicação não conseguirão percebê-la. Com base nisso, o TCP será sempre baseado em MTU define seu próprio tamanho de pacote, tente evitar a fragmentação de pacotes de dados na camada IP, o que significa que o TCP garantirá que quando um segmento TCP for encapsulado por IP e transmitido para o dispositivo de rede, o tamanho do pacote de dados não excederá o MTU do dispositivo de rede.

Esta realização do TCP fará com que seja necessário segmentar os dados passados ​​do espaço do usuário.Este tipo de trabalho é muito fixo, mas consumirá tempo de CPU, por isso é desejável otimizar esta operação em uma rede de alta velocidade. A ideia de otimização é que o TCP transmite grandes blocos de dados (excedendo em muito o MTU) para o dispositivo de rede, e o dispositivo de rede divide os dados de acordo com o MTU para liberar os recursos da CPU. Esta é a ideia de design do TSO (TCP Segmentation Offload).

Obviamente, o TSO precisa de suporte de hardware de equipamento de rede. Um passo adiante, o TSO é na verdade uma tecnologia de segmentação atrasada. A segmentação atrasada reduz as operações de cópia de dados no caminho de envio. Portanto, mesmo que o dispositivo de rede não suporte TSO, é lucrativo, desde que a segmentação possa ser atrasada, e não apenas Limitado ao TCP, também é possível para outros protocolos L4, o que leva ao GSO (Generic Segmentation Offload). Esta técnica significa atrasar a segmentação o máximo possível. É melhor realizar o processamento da segmentação no driver de dispositivo. No entanto, não é realista modificar todos os drivers de dispositivo de rede desta forma, portanto, com um pouco de antecedência, nos dados A entrada submetida ao dispositivo de rede é segmentada por software (ver dev_queue_xmit ()), que é exatamente como o kernel Linux a implementa.

Nota: Alguns conceitos semelhantes, como LSO, UFO, etc., podem ser entendidos por analogia e não serão descritos aqui.

2 julgamento de segmentação de atraso TCP

Para o TCP, não importa se a segmentação de atraso final é realizada por TSO (equipamento de rede) ou por software (GSO), o processamento do TCP é o mesmo. Vamos dar uma olhada em como o TCP pode determinar se pode atrasar a segmentação.

static inline int sk_can_gso(const struct sock *sk)
{
	//实际上检查的就是sk->sk_route_caps是否设定了sk->sk_gso_type能力标记
	return net_gso_ok(sk->sk_route_caps, sk->sk_gso_type);
}

static inline int net_gso_ok(int features, int gso_type)
{
	int feature = gso_type << NETIF_F_GSO_SHIFT;
	return (features & feature) == feature;
}

O campo sk_route_caps representa as capacidades de roteamento; sk_gso_type representa a tecnologia GSO que o protocolo L4 espera suportar na parte inferior. Ambos os campos são definidos durante o handshake triplo.A inicialização do cliente e do servidor são as seguintes.

2.1 Inicialização do cliente

O cliente é feito em tcp_v4_connect (), o código relevante é o seguinte:

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
...
	//设置GSO类型为TCPV4,该类型值会体现在每一个skb中,底层在
	//分段时需要根据该类型区分L4协议是哪个,以做不同的处理
	sk->sk_gso_type = SKB_GSO_TCPV4;
	//见下面
	sk_setup_caps(sk, &rt->u.dst);
...
}

2.2 Inicialização do lado do servidor

O lado do servidor é a última etapa do handshake de três vias após receber o ACK do cliente. Uma meia será criada e inicializada. O código relevante é o seguinte:

struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
				  struct request_sock *req,
				  struct dst_entry *dst)
{
...
	//同上
	newsk->sk_gso_type = SKB_GSO_TCPV4;
	sk_setup_caps(newsk, dst);
...
}

2.3 sk_setup_caps ()

O dispositivo e a rota estão relacionados. O protocolo L4 irá verificar a rota primeiro, então as capacidades do dispositivo serão eventualmente refletidas no cache de rota. Sk_setup_caps () inicializa o campo sk_route_caps de acordo com as capacidades do dispositivo no cache de rota.

enum {
	SKB_GSO_TCPV4 = 1 << 0,
	SKB_GSO_UDP = 1 << 1,
	/* This indicates the skb is from an untrusted source. */
	SKB_GSO_DODGY = 1 << 2,
	/* This indicates the tcp segment has CWR set. */
	SKB_GSO_TCP_ECN = 1 << 3,
	SKB_GSO_TCPV6 = 1 << 4,
};

#define NETIF_F_GSO_SHIFT	16
#define NETIF_F_GSO_MASK	0xffff0000
#define NETIF_F_TSO		(SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO		(SKB_GSO_UDP << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO_ECN		(SKB_GSO_TCP_ECN << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO6		(SKB_GSO_TCPV6 << NETIF_F_GSO_SHIFT)

#define NETIF_F_GSO_SOFTWARE	(NETIF_F_TSO | NETIF_F_TSO_ECN | NETIF_F_TSO6)

void sk_setup_caps(struct sock *sk, struct dst_entry *dst)
{
	__sk_dst_set(sk, dst);
	//初始值来源于网络设备中的features字段
	sk->sk_route_caps = dst->dev->features;
	//如果支持GSO,那么路由能力中的TSO标记也会设定,因为对于L4协议来讲,
	//延迟分段具体是用软件还是硬件来实现自己并不关心
	if (sk->sk_route_caps & NETIF_F_GSO)
		sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE;
	//支持GSO时,sk_can_gso()返回非0。还需要对一些特殊场景判断是否真的可以使用GSO
	if (sk_can_gso(sk)) {
		//只有使用IPSec时,dst->header_len才不为0,这种情况下不能使用TSO特性
		if (dst->header_len)
			sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
		else
			//支持GSO时,必须支持SG IO和校验功能,这是因为分段时需要单独设置每个
			//分段的校验和,这些工作L4是没有办法提前做的。此外,如果不支持SG IO,
			//那么延迟分段将失去意义,因为这时L4必须要保证skb中数据只保存在线性
			//区域,这就不可避免的在发送路径中必须做相应的数据拷贝操作
			sk->sk_route_caps |= NETIF_F_SG | NETIF_F_HW_CSUM;
	}
}

O significado de vários recursos envolvidos no código acima é mostrado na tabela a seguir:

habilidade valor descrição
NETIF_F_GSO 0x0000 0800 Se o GSO implementado por software estiver ativado, defina este sinalizador. Na versão superior do kernel, este valor é aberto à força em register_netdevice ()
NETIF_F_TSO 0x0001 0000 Se o dispositivo de rede suportar TSO sobre IP, defina este sinalizador
NETIF_F_TSO_ECN 0x0008 0000 Se o dispositivo de rede suportar TSO com a marca ECE, defina a marca
NETIF_F_TSO6 0x0010 0000 Se o dispositivo de rede suportar TSO sobre IPv6, defina este sinalizador

3 Estrutura geral

O processamento do TSO afetará todo o caminho de transmissão do pacote de dados, não apenas a camada TCP. Vamos primeiro examinar um diagrama de estrutura geral e, em seguida, analisar o processamento do TSO no caminho de transmissão da camada TCP inferior. O processamento de outras camadas de protocolo precisa ser complementado posteriormente.

Nota: A imagem vem de: https://www.cnblogs.com/lvyilong316/p/6818231.html
Insira a descrição da imagem aqui
Conforme mostrado na figura acima, no caminho de envio do TCP, existem os seguintes pontos para projetar o processamento TSO:

  1. Chame tcp_current_mss () em tcp_sendmsg () para determinar a quantidade máxima de dados que um skb pode conter, ou seja, determinar tp-> xmit_size_goal;
  2. Chame tcp_init_gso_segs () em tcp_write_xmit () para definir o campo GSO em skb, e o software ou placa de rede subjacente executará o processamento de segmentação com base nesta informação;
  3. Use tso_fragment () para segmentar o pacote de dados, consulte "Os novos dados enviados pela camada TCP da pilha de protocolos do kernel do linux" .

4. Processamento de TSO do caminho de envio TCP

4,1 tcp_sendmsg ()

O primeiro é tcp_sendmsg (). Esta função é responsável por encapsular os dados do espaço do usuário em skb individual, portanto, ela precisa saber quantos dados cada skb deve conter. Isso é definido por tcp_current_mss (), o código é o seguinte:

int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
		size_t size)
{
...
	//tcp_current_mss()中会设置tp->xmit_size_goal
	mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));
	//size_goal就是本次发送每个skb可以容纳的数据量,它是mss_now的整数倍,
	//后面tcp_sendmsg()在组织skb时,就以size_goal为上界填充数据
	size_goal = tp->xmit_size_goal;
...
}

4.1.1 tcp_current_mss

//在"TCP选项之MSS"笔记中已经分析过该函数确定发送MSS的部分,这里重点关注tp->xmit_size_goal的部分
unsigned int tcp_current_mss(struct sock *sk, int large_allowed)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct dst_entry *dst = __sk_dst_get(sk);
	u32 mss_now;
	u16 xmit_size_goal;
	int doing_tso = 0;

	mss_now = tp->mss_cache;

	//不考虑MSG_OOB相关,从前面的介绍中我们可以知道都是支持GSO的
	if (large_allowed && sk_can_gso(sk) && !tp->urg_mode)
		doing_tso = 1;

	//下面三个分支是MSS相关
	if (dst) {
		u32 mtu = dst_mtu(dst);
		if (mtu != inet_csk(sk)->icsk_pmtu_cookie)
			mss_now = tcp_sync_mss(sk, mtu);
	}
	if (tp->rx_opt.eff_sacks)
		mss_now -= (TCPOLEN_SACK_BASE_ALIGNED +
			    (tp->rx_opt.eff_sacks * TCPOLEN_SACK_PERBLOCK));
#ifdef CONFIG_TCP_MD5SIG
	if (tp->af_specific->md5_lookup(sk, sk))
		mss_now -= TCPOLEN_MD5SIG_ALIGNED;
#endif

	//xmit_size_goal初始化为MSS
	xmit_size_goal = mss_now;
	//如果支持TSO,则xmit_size_goal可以更大
	if (doing_tso) {
		//65535减去协议层的头部,包括选项部分
		xmit_size_goal = (65535 -
				  inet_csk(sk)->icsk_af_ops->net_header_len -
				  inet_csk(sk)->icsk_ext_hdr_len -
				  tp->tcp_header_len);
		//调整xmit_size_goal不能超过对端接收窗口的一半
		xmit_size_goal = tcp_bound_to_half_wnd(tp, xmit_size_goal);
		//调整xmit_size_goal为MSS的整数倍
		xmit_size_goal -= (xmit_size_goal % mss_now);
	}
	//将确定的xmit_size_goal记录到TCB中
	tp->xmit_size_goal = xmit_size_goal;

	return mss_now;
}

/* Bound MSS / TSO packet size with the half of the window */
static int tcp_bound_to_half_wnd(struct tcp_sock *tp, int pktsize)
{
	//max_window为当前已知接收方所拥有的最大窗口值,这里如果参数pktsize超过
	//了接收窗口的一半,则调整其大小最大为接收窗口的一半
	if (tp->max_window && pktsize > (tp->max_window >> 1))
		return max(tp->max_window >> 1, 68U - tp->tcp_header_len);
	else
		//其余情况不做调整
		return pktsize;
}

4.2 tcp_write_xmit ()

static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)
{
...
	unsigned int tso_segs;

	while ((skb = tcp_send_head(sk))) {
...
		//用MSS初始化skb中的gso字段,返回本skb将会被分割成几个TSO段传输
		tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
		BUG_ON(!tso_segs);
...
		if (tso_segs == 1) {
			//Nagle算法检测,如果已经有小数据段没有被确认,则本次发送尝试失败
			if (unlikely(!tcp_nagle_test(tp, skb, mss_now, 
				(tcp_skb_is_last(sk, skb) ? nonagle : TCP_NAGLE_PUSH)))) {
				break;
			}
		} else {
			if (tcp_tso_should_defer(sk, skb))
				break;
		}
		//limit是本次能够发送的字节数,如果skb的大小超过了limit,那么需要将其切割
		limit = mss_now;
		if (tso_segs > 1)
			limit = tcp_mss_split_point(sk, skb, mss_now, cwnd_quota);
		if (skb->len > limit && unlikely(tso_fragment(sk, skb, limit, mss_now)))
			break;
...
	}
...
}

4.2.1 tcp_init_tso_segs ()

Esta função define as informações de campo relacionadas ao GSO em skb e retorna

/* This must be invoked the first time we consider transmitting
 * SKB onto the wire.
 */
static int tcp_init_tso_segs(struct sock *sk, struct sk_buff *skb, unsigned int mss_now)
{
	int tso_segs = tcp_skb_pcount(skb);
	//cond1: tso_segs为0表示该skb的GSO信息还没有被初始化过
	//cond2: MSS发生了变化,需要重新计算GSO信息
	if (!tso_segs || (tso_segs > 1 && tcp_skb_mss(skb) != mss_now)) {
		tcp_set_skb_tso_segs(sk, skb, mss_now);
		tso_segs = tcp_skb_pcount(skb);
	}
	//返回需要分割的段数
	return tso_segs;
}

/* Due to TSO, an SKB can be composed of multiple actual
 * packets.  To keep these tracked properly, we use this.
 */
static inline int tcp_skb_pcount(const struct sk_buff *skb)
{
	//gso_segs记录了网卡在传输当前skb时应该将其分割成多少个包进行
	return skb_shinfo(skb)->gso_segs;
}

/* This is valid iff tcp_skb_pcount() > 1. */
static inline int tcp_skb_mss(const struct sk_buff *skb)
{
	//gso_size记录了该skb应该按照多大的段被切割,即上次的MSS
	return skb_shinfo(skb)->gso_size;
}

//设置skb中的GSO信息,所谓GSO信息,就是指skb_shared_info中的
//gso_segs、gso_size、gso_type三个字段
static void tcp_set_skb_tso_segs(struct sock *sk, struct sk_buff *skb, unsigned int mss_now)
{
	//如果该skb数据量不足一个MSS,或者根本就不支持GSO,那么就是一个段
	if (skb->len <= mss_now || !sk_can_gso(sk)) {
		/* Avoid the costly divide in the normal non-TSO case.*/
		//只需设置gso_segs为1,另外两个字段在这种情况下无意义
		skb_shinfo(skb)->gso_segs = 1;
		skb_shinfo(skb)->gso_size = 0;
		skb_shinfo(skb)->gso_type = 0;
	} else {
		//计算要切割的段数,就是skb->len除以MSS,结果向上取整
		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss_now);
		skb_shinfo(skb)->gso_size = mss_now;
		//gso_type来自于TCB,该字段的初始化见上文
		skb_shinfo(skb)->gso_type = sk->sk_gso_type;
	}
}

4.2.2 tso_fragment ()

tso_fragment () Fragmenta o pacote de dados, consulte "Os novos dados enviados pela camada TCP da pilha de protocolos do kernel do linux" .

Acho que você gosta

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