本篇介绍网络栈中使用的核心数据结构sock和net_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,这大概是对所有完整套接字和辅助套接字又做了一层抽象,做成了common。sock中有很多成员,这里只列出一小部分:
sk_lock是锁。sk_receive_queue和sk_write_queue是接收和发送缓冲区,至于为什么命名非要一个receive一个write,这让我感到很疑惑。sk_buff和sk_buff_head的内容可以查看核心数据结构sk_buff讲解。sk_send_head是等待传输队列的头指针。- TCP协议已经重要到可以让抽象基类用
union留出位置,为其开一个专用接口。tcp_rtx_queue是TCP协议的重传队列。不得不说,TCP和UDP毕竟是sock最大的客户。
还有一些字段用于表示套接字类型(例如SOCK_STREAM、SOCK_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中了。