代码一

linux/include/net/tcp.h
    
#define TCP_SKB_CB(__skb)	((struct tcp_skb_cb *)&((__skb)->cb[0]))

这段代码是一个宏定义,用于将一个struct sk_buff结构中的成员cb转换为struct tcp_skb_cb类型的指针。

具体来说:

  • struct sk_buff是Linux内核网络子系统中用于管理网络数据包的结构体。它包含了关于数据包的各种信息和操作所需的字段。
  • cbstruct sk_buff中的一个字段,通常是一个用于存储额外信息的缓冲区。
  • TCP_SKB_CB(__skb)是一个宏,它接受一个struct sk_buff结构的指针__skb作为参数,并将__skb中的cb字段转换为struct tcp_skb_cb类型的指针。

这种宏的主要目的是在特定情况下,将struct sk_buff结构中的通用cb字段用于存储TCP协议相关的信息,然后使用宏将其转换为struct tcp_skb_cb类型的指针,以便访问和操作TCP特定的字段和数据。这种技术通常用于Linux内核中网络协议栈的实现中,以提高性能和灵活性。

代码二

函数代码

//套接字(sk),套接字缓冲区(skb),一个标志 clone_it,一个分配内存的标志 gfp_mask,以及接收窗口的下一个序列号 rcv_nxt
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
			      int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
    //函数内部定义了一些局部变量,例如 icsk、inet、tp 等,用于引用套接字的相关信息。
	const struct inet_connection_sock *icsk = inet_csk(sk);
	struct inet_sock *inet;
	struct tcp_sock *tp;
	struct tcp_skb_cb *tcb;
	struct tcp_out_options opts;
	unsigned int tcp_options_size, tcp_header_size;
	struct sk_buff *oskb = NULL;
	struct tcp_md5sig_key *md5;
	struct tcphdr *th;
	u64 prior_wstamp;
	int err;

    //错误检查:代码开始部分使用 BUG_ON 宏进行错误检查,确保传入的 skb 不为空且至少包含一个数据包。
	BUG_ON(!skb || !tcp_skb_pcount(skb));
    
    //TCP时间戳:代码维护了TCP时间戳的相关信息,用于处理TCP数据包的时间戳。
	tp = tcp_sk(sk);
    prior_wstamp = tp->tcp_wstamp_ns;
	tp->tcp_wstamp_ns = max(tp->tcp_wstamp_ns, tp->tcp_clock_cache);
	skb_set_delivery_time(skb, tp->tcp_wstamp_ns, true);
    
    //克隆检查:如果 clone_it 标志为真,表示需要克隆传入的 skb。在克隆操作中,会复制或克隆输入的数据包,以确保操作不会影响原始数据包。这是用于处理可能的重传等情况。
	if (clone_it) {
		oskb = skb;

		tcp_skb_tsorted_save(oskb) {
			if (unlikely(skb_cloned(oskb)))
				skb = pskb_copy(oskb, gfp_mask);
			else
				skb = skb_clone(oskb, gfp_mask);
		} tcp_skb_tsorted_restore(oskb);

		if (unlikely(!skb))
			return -ENOBUFS;
		/* retransmit skbs might have a non zero value in skb->dev
		 * because skb->dev is aliased with skb->rbnode.rb_left
		 */
		skb->dev = NULL;
	}

    //套接字和选项:代码从套接字对象中获取相关信息,包括TCP选项,然后计算TCP头部的大小。
	inet = inet_sk(sk);
	tcb = TCP_SKB_CB(skb);
	memset(&opts, 0, sizeof(opts));

	if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
		tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
	} else {
		tcp_options_size = tcp_established_options(sk, skb, &opts,
							   &md5);
		/* Force a PSH flag on all (GSO) packets to expedite GRO flush
		 * at receiver : This slightly improve GRO performance.
		 * Note that we do not force the PSH flag for non GSO packets,
		 * because they might be sent under high congestion events,
		 * and in this case it is better to delay the delivery of 1-MSS
		 * packets and thus the corresponding ACK packet that would
		 * release the following packet.
		 */
		if (tcp_skb_pcount(skb) > 1)
			tcb->tcp_flags |= TCPHDR_PSH;
	}
	tcp_header_size = tcp_options_size + sizeof(struct tcphdr);

    // 数据包准备:代码设置了一系列与数据包相关的属性,包括时间戳、传输头、数据包所有权等。
	/* We set skb->ooo_okay to one if this packet can select
	 * a different TX queue than prior packets of this flow,
	 * to avoid self inflicted reorders.
	 * The 'other' queue decision is based on current cpu number
	 * if XPS is enabled, or sk->sk_txhash otherwise.
	 * We can switch to another (and better) queue if:
	 * 1) No packet with payload is in qdisc/device queues.
	 *    Delays in TX completion can defeat the test
	 *    even if packets were already sent.
	 * 2) Or rtx queue is empty.
	 *    This mitigates above case if ACK packets for
	 *    all prior packets were already processed.
	 */
	skb->ooo_okay = sk_wmem_alloc_get(sk) < SKB_TRUESIZE(1) ||
			tcp_rtx_queue_empty(sk);

	/* If we had to use memory reserve to allocate this skb,
	 * this might cause drops if packet is looped back :
	 * Other socket might not have SOCK_MEMALLOC.
	 * Packets not looped back do not care about pfmemalloc.
	 */
	skb->pfmemalloc = 0;

	skb_push(skb, tcp_header_size);
	skb_reset_transport_header(skb);

	skb_orphan(skb);
	skb->sk = sk;
	skb->destructor = skb_is_tcp_pure_ack(skb) ? __sock_wfree : tcp_wfree;
	refcount_add(skb->truesize, &sk->sk_wmem_alloc);

	skb_set_dst_pending_confirm(skb, sk->sk_dst_pending_confirm);

    //构建TCP头部:代码构建了TCP头部,包括源端口、目标端口、序列号、确认号、标志位等。
	/* Build TCP header and checksum it. */
	th = (struct tcphdr *)skb->data;
	th->source		= inet->inet_sport;
	th->dest		= inet->inet_dport;
	th->seq			= htonl(tcb->seq);
	th->ack_seq		= htonl(rcv_nxt);
	*(((__be16 *)th) + 6)	= htons(((tcp_header_size >> 2) << 12) |
					tcb->tcp_flags);

	th->check		= 0;
	th->urg_ptr		= 0;

	/* The urg_mode check is necessary during a below snd_una win probe */
	if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {
		if (before(tp->snd_up, tcb->seq + 0x10000)) {
			th->urg_ptr = htons(tp->snd_up - tcb->seq);
			th->urg = 1;
		} else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {
			th->urg_ptr = htons(0xFFFF);
			th->urg = 1;
		}
	}

    //TCP选项:根据TCP选项的情况,代码添加了适当的TCP选项,例如SYN选项。
	skb_shinfo(skb)->gso_type = sk->sk_gso_type;
	if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
		th->window      = htons(tcp_select_window(sk));
		tcp_ecn_send(sk, skb, th, tcp_header_size);
	} else {
		/* RFC1323: The window in SYN & SYN/ACK segments
		 * is never scaled.
		 */
		th->window	= htons(min(tp->rcv_wnd, 65535U));
	}

	tcp_options_write(th, tp, &opts);

    //MD5签名:如果需要使用MD5签名,则计算并添加MD5签名。
#ifdef CONFIG_TCP_MD5SIG
	/* Calculate the MD5 hash, as we have all we need now */
	if (md5) {
		sk_gso_disable(sk);
		tp->af_specific->calc_md5_hash(opts.hash_location,
					       md5, sk, skb);
	}
#endif

    //BPF程序:执行与BPF(Berkeley Packet Filter)相关的操作。
	/* BPF prog is the last one writing header option */
	bpf_skops_write_hdr_opt(sk, skb, NULL, NULL, 0, &opts);

	INDIRECT_CALL_INET(icsk->icsk_af_ops->send_check,
			   tcp_v6_send_check, tcp_v4_send_check,
			   sk, skb);

    //检查校验和:计算TCP头部校验和。
	if (likely(tcb->tcp_flags & TCPHDR_ACK))
		tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);

	if (skb->len != tcp_header_size) {
		tcp_event_data_sent(tp, sk);
		tp->data_segs_out += tcp_skb_pcount(skb);
		tp->bytes_sent += skb->len - tcp_header_size;
	}

	if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
		TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
			      tcp_skb_pcount(skb));

	tp->segs_out += tcp_skb_pcount(skb);
	skb_set_hash_from_sk(skb, sk);
	/* OK, its time to fill skb_shinfo(skb)->gso_{segs|size} */
	skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);
	skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);

	/* Leave earliest departure time in skb->tstamp (skb->skb_mstamp_ns) */

	/* Cleanup our debris for IP stacks */
	memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),
			       sizeof(struct inet6_skb_parm)));
   
    //数据发送:最后,代码使用底层网络栈的适当函数将数据包发送出去,并处理相关的统计信息、时间戳和错误情况。
	tcp_add_tx_delay(skb, tp);

	err = INDIRECT_CALL_INET(icsk->icsk_af_ops->queue_xmit,
				 inet6_csk_xmit, ip_queue_xmit,
				 sk, skb, &inet->cork.fl);

	if (unlikely(err > 0)) {
		tcp_enter_cwr(sk);
		err = net_xmit_eval(err);
	}
	if (!err && oskb) {
		tcp_update_skb_after_send(sk, oskb, prior_wstamp);
		tcp_rate_skb_sent(sk, oskb);
	}
	return err;
}

函数说明

/* This routine actually transmits TCP packets queued in by
 * tcp_do_sendmsg().  This is used by both the initial
 * transmission and possible later retransmissions.
 * All SKB's seen here are completely headerless.  It is our
 * job to build the TCP header, and pass the packet down to
 * IP so it can do the same plus pass the packet off to the
 * device.
 *
 * We are working here with either a clone of the original
 * SKB, or a fresh unique copy made by the retransmit engine.
 */

这个例程实际上是传输由tcp_do_sendmsg()排队的TCP数据包。它用于初始传输和可能的后续重传。在这里看到的所有SKB(Socket Buffer,套接字缓冲区)都是没有任何头部信息的。我们的任务是构建TCP头部,然后将数据包传递给IP层,以便它也可以进行相同的操作,并将数据包传递给网络设备。

在这里,我们要么使用原始SKB的克隆,要么使用由重传引擎创建的全新独立副本。这是为了确保对数据包进行操作时不会影响原始SKB或其他正在处理的数据包。重传可能会创建新的数据包副本,因为数据包可能需要多次传输,而每次传输可能需要进行不同的操作或重传。

GSO

/* Force a PSH flag on all (GSO) packets to expedite GRO flush
		 * at receiver : This slightly improve GRO performance.
		 * Note that we do not force the PSH flag for non GSO packets,
		 * because they might be sent under high congestion events,
		 * and in this case it is better to delay the delivery of 1-MSS
		 * packets and thus the corresponding ACK packet that would
		 * release the following packet.
		 */

这段注释是在讨论在TCP数据包中强制设置PSH(Push)标志的目的和条件。

在TCP协议中,PSH标志用于表示应用程序希望立即将数据传递给接收方,而不等待TCP缓冲区填满或其他条件。当设置了PSH标志时,接收方的TCP协议栈将尽快将数据交付给应用程序,而不等待数据包的到达。

这段注释中提到的目的是,在使用了TCP的大分段卸载(GSO,Generic Segmentation Offload)时,强制设置PSH标志,以加快GRO(Generic Receive Offload)的数据包刷新。GRO是一种网络卸载技术,用于在接收数据包时将多个数据包合并成较大的数据块,以提高性能和效率。

具体来说,当数据包使用GSO进行分段时,通常会生成多个小的数据包片段,这些片段在网络上传输后,最终会在接收端重新组装成原始数据包。如果在GSO生成的片段中强制设置PSH标志,接收端可以更快地刷新这些数据包片段,即将它们传递给应用程序。

然而,注释还指出,对于非GSO数据包,不应该强制设置PSH标志。这是因为在高拥塞情况下,非GSO数据包可能会被发送,而此时推迟将数据传递给应用程序可能更为合适,以避免进一步的拥塞。

总之,这段注释的目的是通过在使用GSO的数据包上设置PSH标志来优化GRO性能,但对于非GSO数据包,可能不会强制设置PSH标志,以便更好地处理拥塞情况。这是TCP网络协议中性能优化的一部分。

SYN

   unlikely(tcb->tcp_flags & TCPHDR_SYN) 

这是一种用于编写条件判断的宏,通常在性能敏感的代码路径中使用,用于提示编译器该条件判断的执行路径不太可能发生。这有助于编译器在生成机器代码时进行优化,以提高程序的执行效率。
具体来说,这个条件判断的目的是检查 tcb 结构体中的 tcp_flags 字段是否包含 TCPHDR_SYN 标志位。TCPHDR_SYN 是一个常量,表示TCP头部中的SYN标志位,用于检查TCP数据包是否是建立新连接的SYN包。

什么情况下使用GSO?

	if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
		tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
	} else {
		tcp_options_size = tcp_established_options(sk, skb, &opts,
							   &md5);
		/* Force a PSH flag on all (GSO) packets to expedite GRO flush
		 * at receiver : This slightly improve GRO performance.
		 * Note that we do not force the PSH flag for non GSO packets,
		 * because they might be sent under high congestion events,
		 * and in this case it is better to delay the delivery of 1-MSS
		 * packets and thus the corresponding ACK packet that would
		 * release the following packet.
		 */
		if (tcp_skb_pcount(skb) > 1)
			tcb->tcp_flags |= TCPHDR_PSH;
	}

在上述代码中,GSO(Generic Segmentation Offload)与网络数据包的处理有关,但并不是在这段代码中明确启用或禁用GSO。相反,这段代码是根据TCP数据包的特性来设置TCP标志位,包括PSH标志位。

具体情况如下:

  1. TCP连接建立(SYN包):tcb->tcp_flags & TCPHDR_SYN 成立时,表示正在处理TCP连接建立的SYN包。在这种情况下,代码会调用 tcp_syn_options 函数来处理TCP连接建立时的选项,并设置相应的标志。这与GSO无关,因为SYN包通常很小,不需要进行分段。
  2. 已建立的TCP连接(非SYN包):tcb->tcp_flags & TCPHDR_SYN 不成立时,表示正在处理已建立的TCP连接的数据包(非SYN包)。在这种情况下,代码会调用 tcp_established_options 函数来处理已建立连接的数据包的选项,并设置相应的标志。同时,如果数据包的数量大于1(tcp_skb_pcount(skb) > 1),则会强制设置PSH标志位,以加速GRO(Generic Receive Offload)的数据包刷新。

因此,GSO的使用与是否处理已建立连接的数据包以及数据包的数量有关。当处理已建立连接的数据包时,可能会考虑是否启用GSO,但这段代码本身并没有直接启用或禁用GSO。

可切换传输队列的标志

/* We set skb->ooo_okay to one if this packet can select
	 * a different TX queue than prior packets of this flow,
	 * to avoid self inflicted reorders.
	 * The 'other' queue decision is based on current cpu number
	 * if XPS is enabled, or sk->sk_txhash otherwise.
	 * We can switch to another (and better) queue if:
	 * 1) No packet with payload is in qdisc/device queues.
	 *    Delays in TX completion can defeat the test
	 *    even if packets were already sent.
	 * 2) Or rtx queue is empty.
	 *    This mitigates above case if ACK packets for
	 *    all prior packets were already processed.
	 */
	skb->ooo_okay = sk_wmem_alloc_get(sk) < SKB_TRUESIZE(1) ||
			tcp_rtx_queue_empty(sk);
  1. skb->ooo_okay标志: 这段代码在设置网络数据包(skb)的ooo_okay标志时,用于判断该数据包是否可以选择不同的传输队列(TX queue)而不会导致数据包的自我乱序。在多核系统中,网络数据包通常由多个CPU核心处理,为了避免乱序,需要确定是否可以切换到其他传输队列。
  2. 传输队列的选择: 根据当前系统配置,决定了如何选择其他传输队列。如果XPS(Transmit Packet Steering)功能启用,就会基于当前CPU核心的编号来选择传输队列;否则,会使用套接字(sk)的sk_txhash属性来选择传输队列。
  3. 切换到其他队列的条件: 代码列出了切换到其他传输队列的条件:
      1. 如果没有带有有效数据负载的数据包在网络设备的队列(qdisc)或设备队列中排队等待传输,那么可以考虑切换到其他队列。这是为了避免因在TX完成时的延迟而导致测试失败,即使数据包已经被发送。
      1. 或者,如果重新传输(rtx)队列为空,那么也可以考虑切换到其他队列。这可以减轻以上情况,因为说明之前发送的数据包的ACK已经全部处理完毕。

总之,这段注释描述了在选择传输队列时需要考虑的条件和策略,以确保数据包不会自我乱序,并且可以充分利用系统的多核处理能力。这有助于提高网络传输的性能和可靠性。

内存保留与循环回送

/* If we had to use memory reserve to allocate this skb,
	 * this might cause drops if packet is looped back :
	 * Other socket might not have SOCK_MEMALLOC.
	 * Packets not looped back do not care about pfmemalloc.
	 */
  1. 内存保留(memory reserve): 在某些情况下,为了分配这个网络数据包(skb),可能需要使用内存保留技术。内存保留是指在内存紧张的情况下,操作系统为了保持系统的稳定性而预留一部分内存。当内存不足时,系统可以使用这些保留的内存,以防止系统崩溃或服务中断。
  2. 可能导致丢包的情况: 该注释提到,如果必须使用内存保留来分配这个数据包,那么在数据包被循环回送(looped back)时可能会导致数据包丢失。这是因为,循环回送通常涉及到将数据包发送给同一台计算机上的另一个套接字(socket)。然而,并不是所有的套接字都具备SOCK_MEMALLOC标志,该标志表示套接字可以使用内存保留。
  3. 不循环回送的数据包不受影响: 对于不涉及循环回送的数据包(例如,数据包被发送到远程计算机而不是本地回送),就不太需要考虑内存保留和SOCK_MEMALLOC的问题。这些数据包不太可能受到内存保留的影响,因为它们不会在本地循环回送,不需要考虑本地套接字的内存保留情况。

总之,该注释提醒开发人员在处理涉及内存保留的数据包时要格外小心,因为这可能会在循环回送情况下导致数据包丢失,特别是当目标套接字没有SOCK_MEMALLOC标志时。不过,对于不涉及循环回送的数据包,通常不需要太担心内存保留问题。

为什么循环回送的数据包可能会丢失?

循环回送的数据包可能会丢失的原因与操作系统的内存管理和网络协议栈的工作方式有关。以下是导致循环回送数据包丢失的主要原因:

  1. 内存保留分配问题: 循环回送的数据包通常需要分配内存以存储它们的内容。如果操作系统的内存已经非常紧张,没有足够的可用内存用于分配这些数据包,那么内核可能会尝试使用内存保留机制。内存保留是指在内存不足的情况下,操作系统会使用预留的一部分内存来满足紧急的内存需求。
  2. SOCK_MEMALLOC 标志: 在循环回送数据包到达的套接字(socket)上,必须设置SOCK_MEMALLOC标志才能使用内存保留机制。这个标志告诉操作系统,套接字允许使用内存保留分配内存。如果套接字没有设置这个标志,操作系统可能无法使用内存保留,从而导致内存不足时无法为数据包分配内存。
  3. 内存不足: 如果内存保留机制也无法满足数据包的内存需求,而且没有足够的可用内存,那么内核可能会选择丢弃数据包,以确保系统的稳定性。这是一种防止内存耗尽的保护措施。
  4. 资源竞争: 当多个任务或线程同时竞争有限的内存资源时,可能会导致数据包被丢弃。内核必须在多个任务之间做出决策,决定哪个任务能够获得内存资源。

总之,循环回送的数据包可能会丢失,主要原因是内存不足,内核无法为这些数据包分配足够的内存,同时套接字上未设置SOCK_MEMALLOC标志。这些情况下,内核可能会选择丢弃数据包,以确保系统的稳定性和可用性。因此,在使用循环回送机制时,特别是在资源受限的情况下,需要小心处理内存分配和数据包丢失的问题。

BPF

/* BPF prog is the last one writing header option */
	bpf_skops_write_hdr_opt(sk, skb, NULL, NULL, 0, &opts);

这段代码涉及到BPF(Berkeley Packet Filter)程序,用于处理网络数据包的过滤和修改操作。具体来说,它使用BPF程序来处理数据包的头部选项(Header Option)。

解释这段代码的关键部分:

  • bpf_skops_write_hdr_opt:这是一个函数调用,用于执行与BPF相关的操作。通常,它会传递一些参数,以便BPF程序可以操作数据包的头部选项。
  • sk:这是一个指向套接字(socket)的指针,表示数据包的来源或目标套接字。BPF程序可能需要知道与套接字相关的信息,以便决定如何处理数据包。
  • skb:这是一个指向数据包(Socket Buffer)的指针,表示待处理的网络数据包。BPF程序通常需要在数据包上执行操作,例如修改数据包的头部选项。
  • &opts:这是一个指向结构体的指针,包含有关头部选项的信息。BPF程序可能需要检查或修改这些选项。
  • 其他参数:代码中还可能包含其他参数,用于向BPF程序传递更多的上下文信息或配置选项。

总的来说,这段代码的目的是将数据包传递给BPF程序,以便BPF程序可以检查或修改数据包的头部选项。BPF程序通常用于网络数据包的过滤、路由、转发或其他自定义处理操作,因此它可以根据需要来处理头部选项以满足特定的网络需求。

BPF的作用

BPF是一个灵活的机制,通常用于网络数据包处理和流量控制。以下是一些BPF在网络领域的主要用途:

  1. 数据包过滤: BPF程序可以用于过滤网络数据包,只保留满足特定条件的数据包。这对于实现防火墙规则、流量监控和网络分析非常有用。
  2. 数据包修改: BPF程序可以修改数据包的内容,包括修改头部字段、地址转换、端口转发等。这使得BPF可以用于实现网络地址转换(NAT)和负载均衡等功能。
  3. 流量控制: BPF可以用于控制网络流量,包括限制带宽、排队和调度数据包,以及执行QoS(Quality of Service)策略。
  4. 网络分析: BPF程序可以捕获网络数据包,并用于网络性能分析、故障排除和监控应用程序。
  5. 安全监控: BPF可以用于监控网络中的异常活动,以便检测入侵和网络安全事件。

在Linux内核中,BPF已经得到广泛应用,并且具有丰富的功能和灵活性,可以在内核中执行自定义的网络数据包处理逻辑。通过加载BPF程序,管理员和开发人员可以根据特定的网络需求来自定义和优化网络数据包的处理方式,以满足性能、安全性和可用性等方面的要求。因此,BPF在网络领域发挥着重要作用。

INDIRECT_CALL_INET

#ifdef CONFIG_RETPOLINE

/*
 * INDIRECT_CALL_$NR - wrapper for indirect calls with $NR known builtin
 *  @f: function pointer
 *  @f$NR: builtin functions names, up to $NR of them
 *  @__VA_ARGS__: arguments for @f
 *
 * Avoid retpoline overhead for known builtin, checking @f vs each of them and
 * eventually invoking directly the builtin function. The functions are check
 * in the given order. Fallback to the indirect call.
 */
#define INDIRECT_CALL_1(f, f1, ...)					\
	({								\
		likely(f == f1) ? f1(__VA_ARGS__) : f(__VA_ARGS__);	\
	})
#define INDIRECT_CALL_2(f, f2, f1, ...)					\
	({								\
		likely(f == f2) ? f2(__VA_ARGS__) :			\
				  INDIRECT_CALL_1(f, f1, __VA_ARGS__);	\
	})
#define INDIRECT_CALL_3(f, f3, f2, f1, ...)					\
	({									\
		likely(f == f3) ? f3(__VA_ARGS__) :				\
				  INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__);	\
	})
#define INDIRECT_CALL_4(f, f4, f3, f2, f1, ...)					\
	({									\
		likely(f == f4) ? f4(__VA_ARGS__) :				\
				  INDIRECT_CALL_3(f, f3, f2, f1, __VA_ARGS__);	\
	})

#define INDIRECT_CALLABLE_DECLARE(f)	f
#define INDIRECT_CALLABLE_SCOPE
#define EXPORT_INDIRECT_CALLABLE(f)	EXPORT_SYMBOL(f)

#else
#define INDIRECT_CALL_1(f, f1, ...) f(__VA_ARGS__)
#define INDIRECT_CALL_2(f, f2, f1, ...) f(__VA_ARGS__)
#define INDIRECT_CALL_3(f, f3, f2, f1, ...) f(__VA_ARGS__)
#define INDIRECT_CALL_4(f, f4, f3, f2, f1, ...) f(__VA_ARGS__)
#define INDIRECT_CALLABLE_DECLARE(f)
#define INDIRECT_CALLABLE_SCOPE		static
#define EXPORT_INDIRECT_CALLABLE(f)
#endif

/*
 * We can use INDIRECT_CALL_$NR for ipv6 related functions only if ipv6 is
 * builtin, this macro simplify dealing with indirect calls with only ipv4/ipv6
 * alternatives
 */
#if IS_BUILTIN(CONFIG_IPV6)
#define INDIRECT_CALL_INET(f, f2, f1, ...) \
	INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__)
#elif IS_ENABLED(CONFIG_INET)
#define INDIRECT_CALL_INET(f, f2, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__)
#else
#define INDIRECT_CALL_INET(f, f2, f1, ...) f(__VA_ARGS__)
#endif

#if IS_ENABLED(CONFIG_INET)
#define INDIRECT_CALL_INET_1(f, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__)
#else
#define INDIRECT_CALL_INET_1(f, f1, ...) f(__VA_ARGS__)
#endif

#endif

INDIRECT_CALL_INET 是一个宏,通常用于在网络协议栈中根据协议族(IPv4 或 IPv6)动态调用特定协议族的函数。这个宏的目的是根据网络套接字(socket)的协议族选择正确的函数进行调用,以确保协议族的适配性。

在 Linux 内核中,网络协议栈支持多种协议族,如 IPv4 和 IPv6。每种协议族都有自己的一组函数,用于处理数据包的发送、接收、路由和其他协议相关的操作。因此,在处理套接字相关操作时,需要根据套接字的协议族来选择正确的函数。

INDIRECT_CALL_INET 宏通常接受以下参数:

  • icsk->icsk_af_ops->send_check:这是一个指向函数指针的成员,表示协议族相关的发送检查函数。不同的协议族会有不同的发送检查函数。
  • tcp_v6_send_checktcp_v4_send_check:这些是不同协议族的发送检查函数的具体实现。
  • sk:这是一个指向套接字的指针,用于确定套接字的协议族。
  • skb:这是一个指向数据包(Socket Buffer)的指针,表示待发送的网络数据包。

通过使用这个宏,可以根据套接字的协议族自动选择正确的发送检查函数,从而确保数据包按照正确的协议族规则进行处理。这有助于提高网络协议栈的通用性和可扩展性,使其能够支持多种不同的网络协议族。

这段代码是用于在 Linux 内核中处理网络协议族的函数调用的宏定义。它主要关注的是 IPv6 协议族的处理,根据不同的情况选择不同的函数调用方式。

让我来逐步解释这段代码的含义:

  1. #if IS_BUILTIN(CONFIG_IPV6):这个条件编译语句检查是否在内核配置中启用了 IPv6 协议支持,并且 IPv6 是内核的一部分(builtin)。如果是,那么说明 IPv6 协议是作为内核的一部分编译进去的。
  2. #define INDIRECT_CALL_INET(f, f2, f1, ...):这是一个宏定义,定义了一个名为 INDIRECT_CALL_INET 的宏,它接受多个参数,其中 ff2f1 是函数指针或函数名,__VA_ARGS__ 表示可变数量的参数。
  3. INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__)INDIRECT_CALL_1(f, f1, __VA_ARGS__):这两个宏是用来执行函数调用的。根据不同的情况,它们会选择调用不同的函数。INDIRECT_CALL_2 用于 IPv6 协议支持内核,而 INDIRECT_CALL_1 用于只支持 IPv4 的内核。
  4. #elif IS_ENABLED(CONFIG_INET):这是一个条件编译语句,检查是否启用了通用的网络协议(不特指 IPv4 或 IPv6),如果是,则说明内核支持通用的网络协议。
  5. #else:如果以上条件都不满足,那么默认情况下直接调用函数 f,不进行间接调用。

总的来说,这段代码的目的是根据内核的配置和支持情况,选择正确的函数调用方式。如果内核支持 IPv6 并且编译进去了,就会使用 INDIRECT_CALL_2 执行函数调用;如果内核只支持通用的网络协议(不特指 IPv4 或 IPv6),就会使用 INDIRECT_CALL_1 执行函数调用;否则,直接调用函数 f。这样可以确保在不同的内核配置下,正确地选择和调用相关的函数。

INDIRECT_CALL_INET(icsk->icsk_af_ops->send_check,
                   tcp_v6_send_check, tcp_v4_send_check,
                   sk, skb);

这个调用的目的是执行发送检查函数。根据之前的宏定义,它会选择调用适用于当前协议族(IPv4 或 IPv6)的发送检查函数。具体来说,它会根据 sk 套接字的协议族,选择调用 tcp_v6_send_checktcp_v4_send_check 函数中的一个,以执行发送前的检查和操作。

err = INDIRECT_CALL_INET(icsk->icsk_af_ops->queue_xmit,
                         inet6_csk_xmit, ip_queue_xmit,
                         sk, skb, &inet->cork.fl);

这个调用的目的是执行数据包的发送操作。类似于第一个调用,它也根据套接字的协议族选择合适的发送函数。具体来说,它会选择调用 inet6_csk_xmitip_queue_xmit 函数中的一个,以执行数据包的发送。发送完成后,可能会返回错误码 err,用于处理发送过程中可能出现的错误情况。

代码三

GFP_ATOMIC

 /*
 * %GFP_ATOMIC users can not sleep and need the allocation to succeed. A lower
 * watermark is applied to allow access to "atomic reserves".
 * The current implementation doesn't support NMI and few other strict
 * non-preemptive contexts (e.g. raw_spin_lock). The same applies to %GFP_NOWAIT.
 */
 #define GFP_ATOMIC	(__GFP_HIGH|__GFP_KSWAPD_RECLAIM)

这是 Linux 内核中定义的一个内存分配标志,叫做 GFP_ATOMIC。它是由 __GFP_HIGH__GFP_KSWAPD_RECLAIM 这两个宏组成的。

  • __GFP_HIGH 表示请求高优先级的内存分配。这意味着分配内存时,内核会尽力确保分配成功,而不会陷入睡眠状态,即使需要回收一些内存来满足分配请求。
  • __GFP_KSWAPD_RECLAIM 表示如果没有足够的可用内存,可以触发内核交换守护进程(kswapd)的工作来回收内存,以便满足分配请求。

综合起来,GFP_ATOMIC 表示一个高优先级、非睡眠的内存分配请求,内核会尽力确保分配成功,即使需要触发内存回收操作来满足这个请求。这通常用于中断上下文、自旋锁上下文或其他不允许睡眠的关键代码路径中,以确保分配操作的原子性和快速性。但需要注意,虽然它提供了高优先级的内存分配,但在极端情况下,如果系统内存已经非常紧张,仍然可能导致分配失败。

__netdev_alloc_skb

/**
 *	__netdev_alloc_skb - allocate an skbuff for rx on a specific device
 *	@dev: network device to receive on
 *	@len: length to allocate
 *	@gfp_mask: get_free_pages mask, passed to alloc_skb
 *
 *	Allocate a new &sk_buff and assign it a usage count of one. The
 *	buffer has NET_SKB_PAD headroom built in. Users should allocate
 *	the headroom they think they need without accounting for the
 *	built in space. The built in space is used for optimisations.
 *
 *	%NULL is returned if there is no free memory.
 */
struct sk_buff *__netdev_alloc_skb(struct net_device *dev, unsigned int len,
				   gfp_t gfp_mask)
{
	struct page_frag_cache *nc;
	struct sk_buff *skb;
	bool pfmemalloc;
	void *data;

	len += NET_SKB_PAD;

	/* If requested length is either too small or too big,
	 * we use kmalloc() for skb->head allocation.
	 */
	if (len <= SKB_WITH_OVERHEAD(1024) ||
	    len > SKB_WITH_OVERHEAD(PAGE_SIZE) ||
	    (gfp_mask & (__GFP_DIRECT_RECLAIM | GFP_DMA))) {
		skb = __alloc_skb(len, gfp_mask, SKB_ALLOC_RX, NUMA_NO_NODE);
		if (!skb)
			goto skb_fail;
		goto skb_success;
	}

	len = SKB_HEAD_ALIGN(len);

	if (sk_memalloc_socks())
		gfp_mask |= __GFP_MEMALLOC;

	if (in_hardirq() || irqs_disabled()) {
		nc = this_cpu_ptr(&netdev_alloc_cache);
		data = page_frag_alloc(nc, len, gfp_mask);
		pfmemalloc = nc->pfmemalloc;
	} else {
		local_bh_disable();
		nc = this_cpu_ptr(&napi_alloc_cache.page);
		data = page_frag_alloc(nc, len, gfp_mask);
		pfmemalloc = nc->pfmemalloc;
		local_bh_enable();
	}

	if (unlikely(!data))
		return NULL;

	skb = __build_skb(data, len);
	if (unlikely(!skb)) {
		skb_free_frag(data);
		return NULL;
	}

	if (pfmemalloc)
		skb->pfmemalloc = 1;
	skb->head_frag = 1;

skb_success:
	skb_reserve(skb, NET_SKB_PAD);
	skb->dev = dev;

skb_fail:
	return skb;
}
EXPORT_SYMBOL(__netdev_alloc_skb);

__netdev_alloc_skb 的函数,该函数用于为特定网络设备上的接收操作分配内存。它分配一个新的 sk_buff 数据结构,该数据结构用于在内核中表示网络数据包。函数还将新分配的 sk_buff 的使用计数设置为 1,以确保在使用完成后可以正确释放内存。函数还提供了一个长度参数 len,以指定要分配的内存长度,并且接受一个用于内存分配的 gfp_mask 参数。

注释还提到,sk_buff 数据结构的内存中预留了一些额外的头部空间(称为前导空间),这部分空间用于优化目的。用户在使用这个函数时,应该根据他们自己的需求来分配额外的前导空间,而不需要考虑这个内置的前导空间。

	gfp_mask |= __GFP_MEMALLOC;

代码表示对 gfp_mask 变量进行按位或运算(|=)。gfp_mask 通常是一个用于内存分配的标志掩码,它用于指定内存分配的一些属性和要求。

__GFP_MEMALLOC 是 Linux 内核中的一个标志位,表示要尝试进行内存分配,并且如果需要的话,可以使用内存回收机制来获得内存。通过将 __GFP_MEMALLOC 添加到 gfp_mask 中,代码告诉内核在尝试分配内存时,可以使用内存回收策略以获取所需的内存。

这通常在内核中的一些特殊情况下使用,例如当内存分配失败时,内核可能会尝试通过回收一些未使用的内存来满足分配请求,以避免系统崩溃或关键性错误。

因此,这行代码的作用是将 __GFP_MEMALLOC 标志添加到 gfp_mask 中,以指示内核可以尝试使用内存回收机制来满足内存分配请求。

EXPORT_SYMBOL

EXPORT_SYMBOL(__netdev_alloc_skb);

EXPORT_SYMBOL 是 Linux 内核中的一个宏,用于将一个函数或符号(通常是函数或变量)导出到内核模块之外,以便其他内核模块或内核的其他部分可以访问和使用这个函数或符号。

在你提供的代码中,EXPORT_SYMBOL(__netdev_alloc_skb); 表示将 __netdev_alloc_skb 函数导出为一个可供其他模块或内核代码使用的全局符号。这允许其他部分的内核代码或加载的内核模块调用 __netdev_alloc_skb 函数,即使它不是在当前模块中定义的。

这通常用于将一些核心的内核函数或符号暴露给外部模块或需要访问这些函数的内核部分,以便实现模块化的内核设计。

自旋锁

自旋锁的实现是为了保护一段短小的临界区操作代码,保证这个临界区的操作是原子的,从而避免并发的竞争冒险。在Linux内核中,自旋锁通常用于包含内核数据结构的操作,你可以看到在许多内核数据结构中都嵌入有spinlock,这些大部分就是用于保证它自身被操作的原子性,在操作这样的结构体时都经历这样的过程:上锁-操作-解锁。

如果内核控制路径发现自旋锁“开着”(可以获取),就获取锁并继续自己的执行。相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,就在原地“旋转”,反复执行一条紧凑的循环检测指令,直到锁被释放。 自旋锁是循环检测“忙等”,即等待时内核无事可做(除了浪费时间),进程在CPU上保持运行,所以它保护的临界区必须小,且操作过程必须短。不过,自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段,所以等待自旋锁的释放不会消耗太多CPU的时间。

一文搞懂Linux内核自旋锁技术原理 - 知乎 (zhihu.com)
注:内容参考AI