Linux 内核里的 TCP 连接,就像一个巨大的“俄罗斯套娃”(或者层层改装的卡车):
- 最里层 (
sock_common) :是 “身份证” 。只记录最核心的身份(我是谁,IP 是多少),为了查得快,做得极小。 - 中间层 (
struct sock) :是 “通用卡车底盘” 。装上了油箱(内存管理)、货箱(收发队列)、方向盘(锁与回调),不管是拉 TCP 的货还是 UDP 的货,底盘都一样。 - 最外层 (
struct tcp_sock) :是 “特种改装” 。因为 TCP 太复杂,需要加装“防丢雷达”(序列号)、“限速器”(拥塞控制)、“重传机械臂”(重传队列)。
1. sock_common
当网卡收到一个数据包,内核需要在几百万个连接中,瞬间找到这个包属于哪个 Socket。它是怎么做到的?答案就藏在 struct sock_common 里。
在 Linux 内核中,TCP、UDP、甚至 RAW Socket,虽然性格迥异,但它们都有一个共同点:都需要被寻址(IP+端口),都需要被管理(状态+引用计数)。
为了避免代码重复,内核设计者采用了类似 C++ “继承”的思想(虽然 C 语言没有继承)。
struct sock_common:就是那个“基类”。它只存最核心的、所有协议都共用的数据。struct sock:继承自sock_common,增加了通用的发送/接收队列。struct tcp_sock:继承自sock,增加了 TCP 特有的序列号、拥塞窗口。
“sock_common 就像是每个连接身份证上的基本信息:我是谁(IP/端口),我来自哪里(Family),我现在什么状态(State)。”
/**
* struct sock_common - 网络协议栈中 Socket 的“最小公分母”
*
* 【核心概念】:
* 它是 struct sock (TCP/UDP通用套接字) 和 struct inet_timewait_sock (TIME_WAIT套接字) 的公共头部。
* 内核在进行快速查找(如收包匹配)时,只需要访问这个结构体,就能获取四元组和状态。
*/
struct sock_common {
// --- 1. 核心身份标识:四元组 (Identity) ---
// 为了性能,IP地址和端口被紧凑地排列在一起,方便 CPU 一次性读取比对。
/*
* 地址对 (Address Pair)
* 在 64 位系统上,这两个 32 位 IP 会被打包成一个 64 位整数进行比对。
* 注意:这里使用的是网络字节序 (__be32),即大端序。
*/
union {
__addrpair skc_addrpair; // 用于快速比对的 64 位整体
struct {
__be32 skc_daddr; // 【目的IP】(Foreign IP) - 对方是谁
__be32 skc_rcv_saddr; // 【源IP】(Local IP) - 我是谁
};
};
/*
* 哈希值 (Hash Value)
* 这个哈希值是根据四元组计算出来的,用于在哈希表中快速定位。
*/
union {
unsigned int skc_hash; // 哈希桶的索引
__u16 skc_u16hashes[2]; // UDP 专用的两个 16 位哈希
};
/*
* 端口对 (Port Pair)
* 同样是为了紧凑排列,方便一次性读取。
*/
union {
__portpair skc_portpair; // 用于快速比对的 32 位整体
struct {
__be16 skc_dport; // 【目的端口】(Foreign Port) - 对方端口
__u16 skc_num; // 【源端口】(Local Port) - 本地端口 (主机序)
};
};
// --- 2. 基础属性 (Attributes) ---
unsigned short skc_family; // 协议族 (AF_INET 或 AF_INET6)
/*
* 状态 (State)
* volatile 保证多核可见性。
* 这里的值如 TCP_ESTABLISHED, TCP_TIME_WAIT 等。
*/
volatile unsigned char skc_state;
/*
* 标志位 (Flags) - 位域节省空间
* skc_reuse: 对应 SO_REUSEADDR (允许端口复用)
* skc_reuseport: 对应 SO_REUSEPORT (允许多个进程绑定同一端口)
*/
unsigned char skc_reuse:4;
unsigned char skc_reuseport:1;
unsigned char skc_ipv6only:1; // IPv6 套接字是否仅监听 IPv6
unsigned char skc_net_refcnt:1; // 网络命名空间引用计数标志
int skc_bound_dev_if; // 绑定的网卡接口索引 (如 eth0)
// --- 3. 链表节点 (List Linkage) ---
// 用于将这个 Socket 挂载到内核的全局哈希表或监听列表中
union {
struct hlist_node skc_bind_node; // 绑定哈希表节点
struct hlist_nulls_node skc_portaddr_node; // 端口地址哈希表节点
};
struct proto *skc_prot; // 协议操作集 (如 tcp_prot, udp_prot) - 类似 C++ 的虚函数表指针
possible_net_t skc_net; // 指向所在的网络命名空间 (Network Namespace)
// --- 4. IPv6 扩展 (Padding & IPv6) ---
#if IS_ENABLED(CONFIG_IPV6)
// 如果是 IPv6,这里存放 128 位地址
struct in6_addr skc_v6_daddr;
struct in6_addr skc_v6_rcv_saddr;
#endif
atomic64_t skc_cookie; // 用于防御 SYN Flood 攻击的 Cookie
// --- 5. 巧妙复用 (Polymorphism & Padding) ---
// 为了内存对齐,同时也为了在不同类型的 Socket 中复用这块内存
union {
unsigned long skc_flags; // 普通 Socket 的标志位
struct sock* skc_listener; // [请求套接字] 指向创建它的监听套接字
struct inet_timewait_death_row *skc_tw_dr; // [TIME_WAIT套接字] 指向死亡行
};
// --- 6. “不复制”区域标记 ---
// 这是一个 C 语言的黑魔法,标记哪些字段在 clone 时不需要复制
/* private: */
int skc_dontcopy_begin[0];
/* public: */
union {
struct hlist_node skc_node; // 主哈希表节点 (Established Hash Table)
struct hlist_nulls_node skc_nulls_node;
};
int skc_tx_queue_mapping; // 发送队列映射
union {
int skc_incoming_cpu; // 记录处理入站包的 CPU 编号 (用于 RPS)
u32 skc_rcv_wnd; // [普通套接字] 接收窗口大小
u32 skc_tw_rcv_nxt; // [TIME_WAIT套接字] 下一个期望的序列号
};
atomic_t skc_refcnt; // 引用计数 (生命周期的关键!)
/* private: */
int skc_dontcopy_end[0];
union {
u32 skc_rxhash; // 接收哈希
u32 skc_window_clamp; // 窗口钳制值
u32 skc_tw_snd_nxt; // [TIME_WAIT套接字] 下一个发送序列号
};
/* public: */
};
2. sock
一个数据包从网卡进来,最终被应用层 read 读取,中间经历了什么?它在 struct sock 的接收队列里排队;
应用层 write 数据发往互联网,数据在哪里暂存?它在 struct sock 的发送队列里等待。
struct sock 就是连接用户态与网络世界的“蓄水池”
/**
* struct sock - network layer representation of sockets
*
* 【核心概念】:
* 这是 Linux 网络协议栈中最庞大的结构体之一。
* 它继承了 sock_common(身份),并扩展了数据传输所需的所有通用资源。
*/
struct sock {
/*
* 第一部分:继承 (Inheritance)
* ----------------------------
* 必须放在最开头!
* 这样内核才能把 struct sock* 强转为 struct sock_common* 进行快速查找。
*/
struct sock_common __sk_common;
/*
* 宏定义魔法 (Macro Magic)
* ------------------------
* 为了让代码写起来像访问普通成员变量一样(如 sk->sk_state),
* 而不需要写 sk->__sk_common.skc_state。
*/
#define sk_node __sk_common.skc_node
#define sk_nulls_node __sk_common.skc_nulls_node
#define sk_refcnt __sk_common.skc_refcnt
#define sk_tx_queue_mapping __sk_common.skc_tx_queue_mapping
#define sk_dontcopy_begin __sk_common.skc_dontcopy_begin
#define sk_dontcopy_end __sk_common.skc_dontcopy_end
#define sk_hash __sk_common.skc_hash
#define sk_portpair __sk_common.skc_portpair
#define sk_num __sk_common.skc_num
#define sk_dport __sk_common.skc_dport
#define sk_addrpair __sk_common.skc_addrpair
#define sk_daddr __sk_common.skc_daddr
#define sk_rcv_saddr __sk_common.skc_rcv_saddr
#define sk_family __sk_common.skc_family
#define sk_state __sk_common.skc_state
#define sk_reuse __sk_common.skc_reuse
#define sk_reuseport __sk_common.skc_reuseport
#define sk_ipv6only __sk_common.skc_ipv6only
#define sk_net_refcnt __sk_common.skc_net_refcnt
#define sk_bound_dev_if __sk_common.skc_bound_dev_if
#define sk_bind_node __sk_common.skc_bind_node
#define sk_prot __sk_common.skc_prot
#define sk_net __sk_common.skc_net
#define sk_v6_daddr __sk_common.skc_v6_daddr
#define sk_v6_rcv_saddr __sk_common.skc_v6_rcv_saddr
#define sk_cookie __sk_common.skc_cookie
#define sk_incoming_cpu __sk_common.skc_incoming_cpu
#define sk_flags __sk_common.skc_flags
#define sk_rxhash __sk_common.skc_rxhash
/*
* 第二部分:锁与同步 (Locking)
* ---------------------------
* 网络栈是多核并发最激烈的地方,这把锁保护 Socket 的状态和队列。
*/
socket_lock_t sk_lock;
/*
* 第三部分:接收系统 (Receiving System)
* -------------------------------------
* 数据流向:网卡 -> 协议栈 -> sk_receive_queue -> 用户 read()
*/
// 【核心】接收队列:存放收到的数据包 (sk_buff),等待用户读取
struct sk_buff_head sk_receive_queue;
/*
* 【核心】积压队列 (Backlog)
* 这是一个特殊的队列。当软中断处理速度过快,或者为了优化性能,
* 包会先暂存在这里,等待进程上下文来处理。
* 注意:rmem_alloc 放在这里是为了在 64 位系统上填补内存空洞(Padding)。
*/
struct {
atomic_t rmem_alloc; // 接收缓冲区当前占用的内存
int len; // 队列长度
struct sk_buff *head; // 队头
struct sk_buff *tail; // 队尾
} sk_backlog;
#define sk_rmem_alloc sk_backlog.rmem_alloc
// 前向分配空间:一种内存分配优化,减少锁竞争
int sk_forward_alloc;
// 发送哈希:用于多队列网卡负载均衡
__u32 sk_txhash;
#ifdef CONFIG_NET_RX_BUSY_POLL
// 忙轮询 (Busy Poll) 相关配置,用于低延迟场景
unsigned int sk_napi_id;
unsigned int sk_ll_usec;
#endif
// 丢包计数器
atomic_t sk_drops;
// 接收缓冲区大小限制
int sk_rcvbuf;
/*
* 第四部分:过滤与路由 (Filtering & Routing)
* ------------------------------------------
*/
// Socket 过滤器 (如 BPF),用于过滤数据包
struct sk_filter __rcu *sk_filter;
// 等待队列:用于 select/poll/epoll 机制
union {
struct socket_wq __rcu *sk_wq;
struct socket_wq *sk_wq_raw;
};
#ifdef CONFIG_XFRM
// IPsec 安全策略
struct xfrm_policy __rcu *sk_policy[2];
#endif
// 接收路由缓存:记录入站包的路由信息,避免重复查表
struct dst_entry *sk_rx_dst;
// 目的路由缓存:记录出站包的路由信息
struct dst_entry __rcu *sk_dst_cache;
/*
* 第五部分:发送系统 (Sending System)
* -----------------------------------
* 数据流向:用户 write() -> sk_write_queue -> 协议栈 -> 网卡
*/
// 发送内存占用计数器
atomic_t sk_wmem_alloc;
// 其他内存占用计数器 (Option/Other)
atomic_t sk_omem_alloc;
// 发送缓冲区大小限制
int sk_sndbuf;
// 【核心】发送队列:存放待发送的数据包
struct sk_buff_head sk_write_queue;
// 标志位集合 (使用位域节省内存)
kmemcheck_bitfield_begin(flags);
unsigned int sk_shutdown : 2, // 关闭状态 (读/写)
sk_no_check_tx : 1, // 发送不校验和
sk_no_check_rx : 1, // 接收不校验和
sk_userlocks : 4, // 用户锁设置
sk_protocol : 8, // 协议类型 (TCP/UDP)
sk_type : 16; // Socket 类型
#define SK_PROTOCOL_MAX U8_MAX
kmemcheck_bitfield_end(flags);
// 持久化队列大小
int sk_wmem_queued;
// 分配模式 (GFP_KERNEL 等)
gfp_t sk_allocation;
// 限速参数 (Pacing Rate)
u32 sk_pacing_rate; /* bytes per second */
u32 sk_max_pacing_rate;
// 网卡特性能力集 (如 TSO, GSO)
netdev_features_t sk_route_caps;
netdev_features_t sk_route_nocaps;
// GSO (通用分段卸载) 相关配置
int sk_gso_type;
unsigned int sk_gso_max_size;
u16 sk_gso_max_segs;
// 接收低水位标记:当接收数据少于这个值时,触发通知
int sk_rcvlowat;
// SO_LINGER 选项的时间设置
unsigned long sk_lingertime;
// 错误队列:存放带外数据或错误信息
struct sk_buff_head sk_error_queue;
// 原始 Socket 创建者的协议处理函数
struct proto *sk_prot_creator;
// 回调函数的锁
rwlock_t sk_callback_lock;
// 错误码记录
int sk_err,
sk_err_soft;
// 连接队列当前长度 / 最大长度
u32 sk_ack_backlog;
u32 sk_max_ack_backlog;
// 优先级设置
__u32 sk_priority;
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
__u32 sk_cgrp_prioidx;
#endif
// 对端进程信息 (用于 Unix Domain Socket)
struct pid *sk_peer_pid;
const struct cred *sk_peer_cred;
// 超时设置
long sk_rcvtimeo;
long sk_sndtimeo;
// 定时器:用于保活、重传等
struct timer_list sk_timer;
// 时间戳相关
ktime_t sk_stamp;
u16 sk_tsflags;
u32 sk_tskey;
// 指向 VFS 层的 socket 结构
struct socket *sk_socket;
// 用户私有数据
void *sk_user_data;
// 页面碎片缓存:用于零拷贝优化
struct page_frag sk_frag;
// 发送头指针:指向发送队列中下一个要发的包
struct sk_buff *sk_send_head;
// 当前 peek 偏移量
__s32 sk_peek_off;
// 写等待标志
int sk_write_pending;
#ifdef CONFIG_SECURITY
// 安全模块指针 (SELinux 等)
void *sk_security;
#endif
// 通用包标记
__u32 sk_mark;
#ifdef CONFIG_CGROUP_NET_CLASSID
u32 sk_classid;
#endif
// CGroup 协议数据
struct cg_proto *sk_cgrp;
/*
* 第六部分:回调函数 (Callbacks - The Soul)
* ----------------------------------------
* 这是 Socket 的“灵魂”,负责异步通知用户进程。
*/
void (*sk_state_change)(struct sock *sk);
void (*sk_data_ready)(struct sock *sk);
void (*sk_write_space)(struct sock *sk);
void (*sk_error_report)(struct sock *sk);
int (*sk_backlog_rcv)(struct sock *sk,
struct sk_buff *skb);
// 析构函数:引用计数归零时调用
void (*sk_destruct)(struct sock *sk);
};
3. tcp_sock
“到了这一层,画风突变。这里不再是管内存、管锁的通用代码,而是纯粹的 TCP 逻辑。
如果你读过 RFC 793,你会在这里看到老朋友:
snd_nxt:RFC 里的 SND.NXT,下一个要发的序列号。snd_una:RFC 里的 SND.UNA,最早没被确认的包。snd_cwnd:拥塞窗口。这是 TCP 的‘油门’。
内核就是通过不断修改这几个数字,来控制发包的速度。
比如,发现丢包了?把 snd_cwnd 砍半!这就是拥塞控制的代码实现。”
/**
* struct tcp_sock - TCP 协议的私有数据 (TCB - Transmission Control Block)
*
* 【核心概念】:
* 它继承自 inet_connection_sock (进而继承自 struct sock)。
* 这里存放了所有 TCP 特有的状态机变量、拥塞控制参数、RTT 测量数据。
* 如果你在读 RFC 793,你会发现很多变量名和 RFC 里是一模一样的!
*/
struct tcp_sock {
/*
* ==========================================
* 第一部分:继承 (Inheritance)
* ==========================================
* 必须放在最前面。
* 层级关系:tcp_sock -> inet_connection_sock -> inet_sock -> sock -> sock_common
*/
struct inet_connection_sock inet_conn;
// TCP 头长度(包含选项),用于快速构建包头
u16 tcp_header_len;
// GSO (通用分段卸载) 分段数
u16 gso_segs;
/*
* ==========================================
* 第二部分:头部预测 (Header Prediction)
* ==========================================
* 这是一个极致的性能优化。
* 对于大多数数据包,内核不需要走完整个复杂的协议栈逻辑。
* 如果包头符合 pred_flags 的预测(比如是常规的 ACK 包),直接快速处理。
*/
__be32 pred_flags;
/*
* ==========================================
* 第三部分:RFC793 核心变量 (The RFC Variables)
* ==========================================
* 这是 TCP 可靠传输的基石。
* 这里的变量名直接对应 RFC 文档中的大写变量(如 SND.NXT -> snd_nxt)。
*/
// --- 接收侧 ---
u32 rcv_nxt; // 【核心】下一个期望接收的序列号 (Receive Next)
u32 copied_seq; // 用户进程已经读取到的序列号(用于 recv())
u32 rcv_wup; // 上一次窗口更新时的 rcv_nxt
// --- 发送侧 ---
u32 snd_nxt; // 【核心】下一个要发送的序列号 (Send Next)
u32 snd_una; // 【核心】最早未确认的序列号 (Send UnAcknowledged)
// 如果 snd_una 往前走,说明数据被对方确认了。
u32 snd_sml; // 最近发送的小包序列号(用于 Nagle 算法判断)
// --- 统计信息 (64位) ---
u64 bytes_received; // 接收字节数
u64 bytes_acked; // 确认字节数
u32 segs_in; // 接收段数
u32 segs_out; // 发送段数
/*
* ==========================================
* 第四部分:窗口管理 (Window Management)
* ==========================================
*/
u32 snd_wnd; // 发送窗口:对方通告给我们的窗口大小 (rwnd)
u32 snd_wl1; // 用于判断窗口更新的序列号
u32 max_window; // 曾经见过的最大窗口
u32 rcv_wnd; // 接收窗口:我们要通告给对方的窗口大小
u32 window_clamp; // 窗口钳制:限制 rcv_wnd 的最大值,防止内存耗尽
/*
* ==========================================
* 第五部分:拥塞控制 (Congestion Control)
* ==========================================
* 这是 TCP 最复杂的部分,决定了发送速度。
*/
u32 snd_ssthresh; // 【核心】慢启动阈值 (Slow Start Threshold)
u32 snd_cwnd; // 【核心】拥塞窗口 (Congestion Window)
// 实际发送窗口 = min(snd_wnd, snd_cwnd)
u32 snd_cwnd_cnt; // 拥塞窗口线性增长计数器
u32 prior_cwnd; // 恢复阶段开始前的 cwnd(用于回退)
// PRR (Proportional Rate Reduction) 算法相关
u32 prr_delivered;
u32 prr_out;
/*
* ==========================================
* 第六部分:重传与丢失恢复 (Retransmission & Loss)
* ==========================================
*/
u32 packets_out; // 在途数据包数量 (In Flight)
u32 retrans_out; // 重传包数量
u32 lost_out; // 标记为丢失的包数量
u32 sacked_out; // 被 SACK (选择性确认) 的包数量
// 乱序队列:存放乱序到达的包(等待空洞被填满)
struct sk_buff_head out_of_order_queue;
// SACK (选择性确认) 块
struct tcp_sack_block duplicate_sack[1]; // D-SACK (重复 SACK)
struct tcp_sack_block selective_acks[4]; // SACK 块
// 重传提示指针:加速重传队列的扫描
struct sk_buff* lost_skb_hint;
struct sk_buff *retransmit_skb_hint;
/*
* ==========================================
* 第七部分:RTT 测量 (RTT Measurement)
* ==========================================
* 用于计算 RTO (重传超时时间)。
*/
u32 srtt_us; // 平滑 RTT (Smoothed RTT) << 3
u32 mdev_us; // RTT 平均偏差 (Mean Deviation)
u32 mdev_max_us; // 最大偏差
u32 rttvar_us; // RTT 方差
/*
* ==========================================
* 第八部分:定时器与保活 (Timers & Keepalive)
* ==========================================
*/
u32 rcv_tstamp; // 最后一次收到包的时间戳(用于保活检测)
u32 lsndtime; // 最后一次发送数据的时间(用于 RTO 计算)
unsigned int keepalive_time; // 保活时间
unsigned int keepalive_intvl; // 保活探测间隔
/*
* ==========================================
* 第九部分:标志位与杂项 (Flags & Misc)
* ==========================================
*/
u8 nonagle : 4; // 是否禁用 Nagle 算法 (TCP_NODELAY)
u8 ecn_flags; // ECN (显式拥塞通知) 标志
u16 urg_data; // 紧急数据标志
// TCP 选项接收缓存
struct tcp_options_received rx_opt;
// 快速打开 (Fast Open) 相关
struct tcp_fastopen_request *fastopen_req;
// ... (省略部分安全、MD5 签名等不常用字段) ...
};