【Linux】网络专题(四)——核心数据结构sock族类和net_device

本篇介绍网络栈中使用的核心数据结构socknet_device,从而帮助我们更快更透彻地理解网络栈的实现细节。

sock是一个特别重要的基类。为什么是基类?可以认真考虑一下C++中的继承是如何实现的。C虽然不是OOP,却不妨碍它实现OO的功能。我们还会看到,由sock派生而来的tcp_sock,正是RFC标准中的传输控制块

我们来看一些常用套接字的继承关系:

tcp_sock -> inet_connection_sock -> inet_sock -> sock -> sock_common
                        udp_sock -> inet_sock -> sock -> sock_common
                                    unix_sock -> sock -> sock_common

从中我们可以明显地看出,TCP套接字是一种面向连接的INET套接字;UDP套接字只是INET套接字,不需要维护连接信息;UNIX域套接字和INET域套接字一样都继承于sock基类,但UNIX套接字因为是在本地通信,所以甚至没有提供面向连接套接字的必要。

net_device:TBD

1. sock族类

1.1. sock_common

sock_common包含了套接字最基础的内容,一些mini sock会直接继承这个类,而非继承sock。它包含了地址对、端口对、套接字协议族、连接状态等内容。

值得一提的是,不管是流套接字还是数据报套接字,它们都使用TCP的状态来填充sock_common成员中的连接状态,这就非常灵性,也许是因为可靠传输协议的状态集可以涵盖不可靠传输协议的状态集?

enum {
	TCP_ESTABLISHED = 1,
	TCP_SYN_SENT,
	TCP_SYN_RECV,
	TCP_FIN_WAIT1,
	TCP_FIN_WAIT2,
	TCP_TIME_WAIT,
	TCP_CLOSE,
	TCP_CLOSE_WAIT,
	TCP_LAST_ACK,
	TCP_LISTEN,
	TCP_CLOSING,	/* Now a valid state */
	TCP_NEW_SYN_RECV,

	TCP_MAX_STATES	/* Leave at the end! */
};

1.2. sock

sock结构体是BSD通用套接字socket在网络栈中的表示形式。每一个具有完整功能的套接字(区别于mini sock)肯定应该具有读写数据缓冲区,即会进行sk_buff的管理。

struct sock {
	struct sock_common	__sk_common;
	socket_lock_t		sk_lock;
	struct sk_buff_head	sk_receive_queue;

	union {
		struct sk_buff	*sk_send_head;
		struct rb_root	tcp_rtx_queue;
	};

	struct sk_buff_head	sk_write_queue;

	/* ... */
};

sock继承自sock_common,这大概是对所有完整套接字和辅助套接字又做了一层抽象,做成了commonsock中有很多成员,这里只列出一小部分:

  • sk_lock是锁。
  • sk_receive_queuesk_write_queue是接收和发送缓冲区,至于为什么命名非要一个receive一个write,这让我感到很疑惑。sk_buffsk_buff_head的内容可以查看核心数据结构sk_buff讲解
  • sk_send_head是等待传输队列的头指针。
  • TCP协议已经重要到可以让抽象基类用union留出位置,为其开一个专用接口。tcp_rtx_queue是TCP协议的重传队列。不得不说,TCP和UDP毕竟是sock最大的客户。

还有一些字段用于表示套接字类型(例如SOCK_STREAMSOCK_DGRAM)、套接字所属协议(例如IPPROTO_TCP),将其设在基类字段中有一点运行时类型信息的感觉。

1.3. inet_connection_sock

该类似乎也是一个抽象类,用于表示“面向连接”的特征。

struct inet_connection_sock {
	/* inet_sock has to be the first member! */
	struct inet_sock	  icsk_inet;
	struct request_sock_queue icsk_accept_queue;
	struct inet_bind_bucket	  *icsk_bind_hash;
	unsigned long		  icsk_timeout;
 	struct timer_list	  icsk_retransmit_timer;
 	struct timer_list	  icsk_delack_timer;
	__u32			  icsk_rto;
	__u32                     icsk_rto_min;
	__u32                     icsk_delack_max;
	__u32			  icsk_pmtu_cookie;
	const struct tcp_congestion_ops *icsk_ca_ops;
	const struct inet_connection_sock_af_ops *icsk_af_ops;
	const struct tcp_ulp_ops  *icsk_ulp_ops;
	void __rcu		  *icsk_ulp_data;
	void (*icsk_clean_acked)(struct sock *sk, u32 acked_seq);
	struct hlist_node         icsk_listen_portaddr_node;
	unsigned int		  (*icsk_sync_mss)(struct sock *sk, u32 pmtu);
	__u8			  icsk_ca_state:5,
				  icsk_ca_initialized:1,
				  icsk_ca_setsockopt:1,
				  icsk_ca_dst_locked:1;
	__u8			  icsk_retransmits;
	__u8			  icsk_pending;
	__u8			  icsk_backoff;
	__u8			  icsk_syn_retries;
	__u8			  icsk_probes_out;
	__u16			  icsk_ext_hdr_len;
	struct {
		__u8		  pending;	 /* ACK is pending			   */
		__u8		  quick;	 /* Scheduled number of quick acks	   */
		__u8		  pingpong;	 /* The session is interactive		   */
		__u8		  retry;	 /* Number of attempts			   */
		__u32		  ato;		 /* Predicted tick of soft clock	   */
		unsigned long	  timeout;	 /* Currently scheduled timeout		   */
		__u32		  lrcvtime;	 /* timestamp of last received data packet */
		__u16		  last_seg_size; /* Size of last incoming segment	   */
		__u16		  rcv_mss;	 /* MSS used for delayed ACK decisions	   */
	} icsk_ack;

从中我们可以看到,inet_connection_sock包含了连接本身所需要的信息,例如RTO、最小RTO、重传计时器、拥塞控制接口成员,以及若干拥塞控制所需要的核心信息,例如拥塞状态等,我们会在后面的文章讲述。

1.4. tcp_sock

即TCP套接字,它除了是一个面向连接的套接字外,还保有TCP协议本身需要的信息,是面向连接套接字的实例化类。

struct tcp_sock {
	/* inet_connection_sock has to be the first member of tcp_sock */
	struct inet_connection_sock	inet_conn;
	u16	tcp_header_len;	/* Bytes of tcp header to send		*/
	u16	gso_segs;	/* Max number of segs per GSO packet	*/

/*
 *	Header prediction flags
 *	0x5?10 << 16 + snd_wnd in net byte order
 */
	__be32	pred_flags;

/*
 *	RFC793 variables by their proper names. This means you can
 *	read the code and the spec side by side (and laugh ...)
 *	See RFC793 and RFC1122. The RFC writes these in capitals.
 */
	u64	bytes_received;	/* RFC4898 tcpEStatsAppHCThruOctetsReceived
				 * sum(delta(rcv_nxt)), or how many bytes
				 * were acked.
				 */
	u32	segs_in;	/* RFC4898 tcpEStatsPerfSegsIn
				 * total number of segments in.
				 */
	u32	data_segs_in;	/* RFC4898 tcpEStatsPerfDataSegsIn
				 * total number of data segments in.
				 */
 	u32	rcv_nxt;	/* What we want to receive next 	*/
	u32	copied_seq;	/* Head of yet unread data		*/
	u32	rcv_wup;	/* rcv_nxt on last window update sent	*/
 	u32	snd_nxt;	/* Next sequence we send		*/
	u32	segs_out;	/* RFC4898 tcpEStatsPerfSegsOut
				 * The total number of segments sent.
				 */
	u32	data_segs_out;	/* RFC4898 tcpEStatsPerfDataSegsOut
				 * total number of data segments sent.
				 */
	u64	bytes_sent;	/* RFC4898 tcpEStatsPerfHCDataOctetsOut
				 * total number of data bytes sent.
				 */
	u64	bytes_acked;	/* RFC4898 tcpEStatsAppHCThruOctetsAcked
				 * sum(delta(snd_una)), or how many bytes
				 * were acked.
				 */
	u32	dsack_dups;	/* RFC4898 tcpEStatsStackDSACKDups
				 * total number of DSACK blocks received
				 */
 	u32	snd_una;	/* First byte we want an ack for	*/
 	u32	snd_sml;	/* Last byte of the most recently transmitted small packet */
	u32	rcv_tstamp;	/* timestamp of last received ACK (for keepalives) */

很多字段是RFC标准中所定义的变量。另外一些变量维护Fast Open功能、RTT估计、TCP选项内容、慢启动参数、拥塞控制具体信息等。

tcp_sock的部分字段不代表整个连接的状态和参数,可能只保存某一次报文段解析所产生的结果,例如ecn_flags

如前面所说,TCP协议的状态保存在sock_common中了。

发表评论

您的电子邮箱地址不会被公开。