前面几篇我们从收包路径到 TCP/UDP 协议实现,始终有一个隐含的入口没有拆解——socket。用户态程序看到的是一个文件描述符,内核看到的是一组精心组织的分发表和数据结构。socket 层是 VFS 文件系统抽象和协议栈实现之间的翻译层。
当你写下 socket(AF_INET, SOCK_STREAM, 0)
时,内核做了这些事:
- 分配
struct socket(VFS 层)+struct inode——合并为一次内存分配 - 查找地址族工厂(
AF_INET→inet_create) - 根据
(SOCK_STREAM, 0)查找协议注册表 → 得到tcp_prot+inet_stream_ops - 从
tcp_prot->slab(专用 kmem_cache)分配struct sock - 互相关联:
socket->sk = sock,sock->sk_socket = socket - 创建 VFS file,绑定到进程的文件描述符表
之后每次 send() / recv()
调用,都经过两层分发:先通过
socket->ops(proto_ops)做 VFS
语义翻译,再通过
sock->sk_prot(proto)调用协议实现。
本文从内核源码出发,完整拆解这个双层架构。
下面这张图展示了 socket 的双层分发架构——从 VFS 文件操作到 proto_ops 再到 proto 的完整调用链:
一、核心数据结构:双层分发的基石
struct socket——VFS 层的 socket 表示
struct socket(include/linux/net.h:117)是用户态
fd 直接对应的内核对象:
struct socket {
socket_state state; /* SS_UNCONNECTED/SS_CONNECTED/... */
short type; /* SOCK_STREAM/SOCK_DGRAM/SOCK_RAW */
unsigned long flags; /* SOCK_NOSPACE 等标志 */
struct file *file; /* 反向指针:VFS file → socket */
struct sock *sk; /* 前向指针:socket → 协议实现 */
const struct proto_ops *ops; /* VFS 层分发表 */
struct socket_wq wq; /* epoll/select/poll 的等待队列 */
};socket_state 是 VFS
层面的连接状态(include/uapi/linux/net.h:45),只有五个值:
| 状态 | 含义 |
|---|---|
SS_FREE |
未分配 |
SS_UNCONNECTED |
刚创建,未连接 |
SS_CONNECTING |
connect() 进行中 |
SS_CONNECTED |
已连接 |
SS_DISCONNECTING |
断连中 |
注意这与 TCP 的 11
个状态完全独立——SS_CONNECTED 只是 VFS
层面的标记,TCP 层面的
ESTABLISHED、CLOSE_WAIT 等状态存在
sock->sk_state 中。
struct sock——协议层的 socket 表示
struct sock(include/net/sock.h:341)是协议栈的核心对象,190+
个字段。最关键的几组:
struct sock {
struct sock_common __sk_common; /* 五元组、状态、哈希 */
/* 接收侧 */
struct sk_buff_head sk_receive_queue; /* 接收包队列 */
int sk_rcvbuf; /* SO_RCVBUF 限制 */
/* 发送侧 */
struct sk_buff_head sk_write_queue; /* 发送包队列 */
int sk_sndbuf; /* SO_SNDBUF 限制 */
refcount_t sk_wmem_alloc; /* 已分配写内存 */
/* 分发与关联 */
struct proto *sk_prot; /* 协议分发表 */
struct socket *sk_socket; /* 反向指针 → VFS socket */
/* 事件通知回调 */
void (*sk_data_ready)(struct sock *sk); /* 数据到达通知 */
void (*sk_write_space)(struct sock *sk); /* 写空间可用通知 */
void (*sk_error_report)(struct sock *sk); /* 错误通知 */
/* SO_REUSEPORT */
struct sock_reuseport __rcu *sk_reuseport_cb;
};sock_common(include/net/sock.h:150)包含连接五元组:
struct sock_common {
__be32 skc_daddr; /* 目的 IP */
__be32 skc_rcv_saddr; /* 本地 IP */
__be16 skc_dport; /* 目的端口(网络字节序) */
__u16 skc_num; /* 本地端口(主机字节序) */
unsigned short skc_family; /* AF_INET / AF_INET6 */
unsigned char skc_state; /* 协议层状态(TCP_ESTABLISHED 等) */
unsigned char skc_reuse:4; /* SO_REUSEADDR */
unsigned char skc_reuseport:1; /* SO_REUSEPORT */
struct proto *skc_prot; /* 协议处理器 */
possible_net_t skc_net; /* 网络命名空间 */
};两张分发表
proto_ops(include/linux/net.h:160)——VFS
到 socket 的翻译层:
struct proto_ops {
int (*bind)(struct socket *, struct sockaddr *, int);
int (*connect)(struct socket *, struct sockaddr *, int, int);
int (*accept)(struct socket *, struct socket *, int, bool);
int (*listen)(struct socket *, int);
int (*sendmsg)(struct socket *, struct msghdr *, size_t);
int (*recvmsg)(struct socket *, struct msghdr *, size_t, int);
__poll_t (*poll)(struct file *, struct socket *, poll_table *);
/* ... */
};proto(include/net/sock.h:1223)——协议特定实现:
struct proto {
void (*close)(struct sock *, long);
int (*connect)(struct sock *, struct sockaddr *, int);
struct sock *(*accept)(struct sock *, int, int *, bool);
int (*sendmsg)(struct sock *, struct msghdr *, size_t);
int (*recvmsg)(struct sock *, struct msghdr *, size_t, int, int *);
int (*hash)(struct sock *);
void (*unhash)(struct sock *);
int (*get_port)(struct sock *, unsigned short);
struct kmem_cache *slab; /* 协议专用内存池 */
unsigned int obj_size; /* socket 对象大小 */
char name[32]; /* "TCP" / "UDP" / "SCTP" */
/* ... */
};双层分发的调用路径:
用户态 send(fd, buf, len)
↓ VFS
file->f_op->write_iter()
↓ socket 文件操作
sock_sendmsg(socket, msg)
↓ proto_ops 分发
socket->ops->sendmsg(socket, msg, size)
↓ 对于 TCP: inet_sendmsg()
sock->sk_prot->sendmsg(sock, msg, size)
↓ proto 分发
tcp_sendmsg(sock, msg, size) ← 最终的协议实现
proto_ops 和 proto
的分离让同一协议能支持不同的 VFS 语义——例如 TCP 用
inet_stream_ops(面向连接),而 UDP 用
inet_dgram_ops(无连接),但底层的
tcp_prot / udp_prot
各自处理协议细节。
二、socket 创建路径
系统调用入口
socket(family, type, protocol) 系统调用经过
__sys_socket() 进入
__sock_create():
__sys_socket(family, type, protocol)
↓
__sock_create(current->nsproxy->net_ns, family, type, protocol, &sock, 0)
│
├── ① sock_alloc()
│ └── new_inode_pseudo(sock_mnt->mnt_sb)
│ → 分配 struct socket_alloc(= socket + inode 合体)
│ → 设置 inode->i_op = &sockfs_inode_ops
│
├── ② 查找地址族工厂
│ └── net_families[family]
│ → AF_INET: inet_family_ops
│ → AF_INET6: inet6_family_ops
│ → AF_UNIX: unix_family_ops
│
└── ③ family->create(net, sock, protocol, kern)
└── 对于 AF_INET: inet_create()
socket_alloc:一次分配两个对象
struct socket_alloc(include/net/sock.h:1555)把
struct socket 和 struct inode
合并为一个结构体:
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};sock_alloc() 通过
new_inode_pseudo() 从 sockfs
超级块分配这个合体对象。好处是缓存友好——socket
和 inode 在同一缓存行,VFS 操作和 socket
操作不会产生额外的缓存未命中。
转换宏: - SOCKET_I(inode) → 从 inode 取
socket(container_of) -
SOCK_INODE(socket) → 从 socket 取 inode
inet_create:协议注册与匹配
inet_create() 是 AF_INET
地址族的 socket 工厂:
inet_create(net, sock, protocol, kern)
│
├── 遍历 inetsw[sock->type] 链表
│ └── 查找匹配 (type, protocol) 的 inet_protosw
│ ├── SOCK_STREAM + IPPROTO_TCP → tcp_prot + inet_stream_ops
│ ├── SOCK_DGRAM + IPPROTO_UDP → udp_prot + inet_dgram_ops
│ └── SOCK_RAW + IPPROTO_ICMP → raw_prot + inet_sockraw_ops
│
├── sock->ops = answer->ops 设置 proto_ops 分发表
│
├── sk = sk_alloc(net, AF_INET, priority, answer->prot, kern)
│ └── 从 prot->slab(kmem_cache)分配 struct sock
│ ├── TCP: kmem_cache("TCP", sizeof(tcp_sock)) ~2KB
│ ├── UDP: kmem_cache("UDP", sizeof(udp_sock)) ~400B
│ └── 设置 sk->sk_prot = prot
│
├── sock_init_data(sock, sk)
│ ├── socket->sk = sk VFS → 协议
│ ├── sk->sk_socket = socket 协议 → VFS
│ ├── sk_data_ready = sock_def_readable 默认回调
│ ├── sk_write_space = sock_def_write_space
│ ├── sk_rcvbuf = sysctl_rmem_default
│ └── sk_sndbuf = sysctl_wmem_default
│
└── sk->sk_prot->init(sk)
├── TCP: tcp_v4_init_sock() 初始化拥塞控制、定时器
└── UDP: (无 init)
inet_protosw 注册表
struct inet_protosw(include/net/protocol.h:76)是协议注册表条目:
struct inet_protosw {
struct list_head list;
unsigned short type; /* SOCK_STREAM / SOCK_DGRAM / SOCK_RAW */
unsigned short protocol; /* IPPROTO_TCP / IPPROTO_UDP */
struct proto *prot; /* tcp_prot / udp_prot */
const struct proto_ops *ops; /* inet_stream_ops / inet_dgram_ops */
unsigned char flags; /* INET_PROTOSW_PERMANENT 等 */
};内核启动时,inet_init() 注册默认条目到
inetsw[] 数组。模块(如 SCTP)可以通过
inet_register_protosw() 添加新条目。
从 socket 到 fd
__sys_socket() 在
__sock_create() 返回后:
__sys_socket()
├── __sock_create() → 得到 struct socket
├── sock_map_fd(socket, flags)
│ ├── get_unused_fd_flags(flags) 分配 fd 号
│ ├── sock_alloc_file(socket, flags, NULL)
│ │ └── alloc_file_pseudo(inode, sock_mnt, dname, ...)
│ │ → 创建 struct file
│ │ → file->f_op = &socket_file_ops VFS 文件操作
│ └── fd_install(fd, file) 绑定到进程 fd 表
└── return fd
socket_file_ops 是 sockfs
文件系统的操作集,它把 VFS 的 read() /
write() / poll() 转换为 socket
层的 sock_read_iter() /
sock_write_iter() /
sock_poll()。
三、proto_ops 分发:inet_stream_ops 与 inet_dgram_ops
TCP 的分发链
inet_stream_ops(include/net/inet_common.h:11)是
TCP socket 的 VFS 分发表:
inet_stream_ops:
.bind = inet_bind
.connect = inet_stream_connect
.listen = inet_listen
.accept = inet_accept
.sendmsg = inet_sendmsg → sk->sk_prot->sendmsg = tcp_sendmsg
.recvmsg = inet_recvmsg → sk->sk_prot->recvmsg = tcp_recvmsg
.poll = tcp_poll
.setsockopt = sock_common_setsockopt
.getsockopt = sock_common_getsockopt
inet_sendmsg()
的实现非常薄——本质上就是转发到
sk->sk_prot->sendmsg():
int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
struct sock *sk = sock->sk;
/* 如果还没有绑定端口,先自动绑定 */
if (unlikely(inet_send_prepare(sk)))
return -EAGAIN;
return INDIRECT_CALL_2(sk->sk_prot->sendmsg, tcp_sendmsg,
udp_sendmsg, sk, msg, size);
}INDIRECT_CALL_2 是 retpoline 优化——在
Spectre 缓解环境下,间接调用很慢,这个宏先比较目标地址是否是
tcp_sendmsg 或
udp_sendmsg,如果是就直接调用,避免间接跳转。
UDP 的分发链
inet_dgram_ops 类似,但面向无连接语义:
inet_dgram_ops:
.bind = inet_bind
.connect = inet_dgram_connect ← 不建立连接,只缓存目的地址
.sendmsg = inet_sendmsg → udp_sendmsg
.recvmsg = inet_recvmsg → udp_recvmsg
.poll = udp_poll
inet_dgram_connect() 对 UDP
的语义是”记住默认目的地址”——后续 send()
不需要每次指定目的地址,且内核能把这个 socket
放入二级哈希表(参见 UDP
内核实现)。
四、SO_REUSEPORT:多核 socket 分发
问题
单个 socket 的收包路径天然是串行的——所有包都进入同一个
sk_receive_queue,无论系统有多少 CPU。对高 PPS
的 UDP 服务(DNS/QUIC)或高连接率的 TCP 监听(Nginx),单
socket 成为瓶颈。
机制
SO_REUSEPORT(Linux 3.9+)允许多个 socket
绑定同一
(address, port),收包时按包的哈希值分发到不同
socket——每个 socket 对应一个处理线程/进程。
struct sock_reuseport(include/net/sock_reuseport.h:13)是分发组的核心容器:
struct sock_reuseport {
struct rcu_head rcu;
u16 max_socks; /* 数组容量 */
u16 num_socks; /* 当前活跃 socket 数 */
u16 num_closed_socks; /* 已关闭待清理的 socket 数 */
unsigned int reuseport_id; /* BPF 程序可见的组 ID */
unsigned int bind_inany:1; /* 绑定 INADDR_ANY? */
unsigned int has_conns:1; /* 是否有已建立的连接 */
struct bpf_prog __rcu *prog; /* eBPF 选择器程序 */
struct sock *socks[]; /* socket 数组——flexible array */
};分发流程
收包路径(__udp4_lib_lookup / inet_lookup_listener)
↓ 找到一组 SO_REUSEPORT sockets
reuseport_select_sock(sk, hash, skb, hdr_len)
├── prog = rcu_dereference(reuse->prog)
├── prog 非空?
│ └── bpf_run_sk_reuseport(reuse, sk, prog, skb, ...)
│ → eBPF 程序返回选中的 socket 索引
└── prog 为空?
└── reciprocal_scale(hash, num_socks)
→ 用包哈希值选择 socket(均匀分布)
eBPF 选择器
reuseport_attach_prog()(include/net/sock_reuseport.h:44)挂载
BPF 程序。程序类型为
BPF_PROG_TYPE_SK_REUSEPORT,可以读取包头信息,实现自定义分发逻辑:
/* eBPF 伪代码:按 QUIC Connection ID 分发 */
SEC("sk_reuseport")
int select_by_cid(struct sk_reuseport_md *ctx)
{
/* 解析 QUIC 包头,提取 Connection ID */
__u64 cid = parse_quic_cid(ctx->data, ctx->data_end);
/* 按 CID 哈希选择 socket */
return bpf_sk_select_reuseport(ctx, &sock_map, &cid, 0);
}这让同一 QUIC 连接的所有包始终路由到同一个处理线程,避免跨线程同步。
五、epoll 集成:事件通知机制
回调链
socket 与 epoll
的集成通过三个回调函数实现(include/net/sock.h:514-517):
sk_data_ready(sk) → 数据到达时调用
sk_write_space(sk) → 发送缓冲区有空间时调用
sk_error_report(sk) → 错误发生时调用
默认实现(sock_init_data() 中设置):
sk_data_ready = sock_def_readable()
└── 检查 socket_wq 是否有等待者
└── 有 → wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN)
→ 唤醒 epoll_wait 中阻塞的进程
sk_write_space = sock_def_write_space()
└── 检查发送缓冲区是否有空间
└── 有空间 → wake_up_interruptible_poll(&wq->wait, EPOLLOUT)
epoll_wait 的底层流程
① 用户态: epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event)
→ 内核注册 socket 到 epoll 实例
→ socket->ops->poll(file, socket, &epq.pt)
→ sock_poll_wait(file, socket_wq, wait)
→ 把当前进程添加到 socket_wq.wait 队列
② 用户态: epoll_wait(epfd, events, maxevents, timeout)
→ 进程睡眠在 epoll 的等待队列上
③ 网卡收包 → softirq → tcp_rcv_established()
→ sk_data_ready(sk)
→ sock_def_readable(sk)
→ wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN)
→ 唤醒 epoll_wait 中的进程
④ 进程返回: events[i].events = EPOLLIN
→ 用户调用 recv() 读取数据
等待队列标志
socket_wq
中的标志(include/linux/net.h:99)用于优化唤醒:
| 标志 | 含义 |
|---|---|
SOCKWQ_ASYNC_NOSPACE |
发送缓冲区满,下次有空间时通知 |
SOCKWQ_ASYNC_WAITDATA |
正在等待数据到达 |
这些标志避免了不必要的唤醒——如果没有进程在等,sk_data_ready()
就不会触发昂贵的唤醒操作。
六、socket 选项处理
三级选项处理
socket 选项按层级分发:
setsockopt(fd, level, optname, optval, optlen)
↓
__sys_setsockopt()
├── level == SOL_SOCKET?
│ └── sock_setsockopt(sock, level, optname, ...)
│ → 通用选项(SO_RCVBUF/SO_SNDBUF/SO_REUSEPORT/...)
│
├── level == SOL_TCP / SOL_UDP?
│ └── sock->ops->setsockopt()
│ → inet_stream_ops: sock_common_setsockopt()
│ → sk->sk_prot->setsockopt()
│ → TCP: do_tcp_setsockopt()
│ → UDP: udp_setsockopt()
│
└── level == SOL_IP?
└── ip_setsockopt()
→ IP 层选项(IP_TTL/IP_TOS/IP_MULTICAST_IF/...)
关键通用选项
sock_setsockopt()
处理的通用选项及其内核映射:
| 选项 | sock 字段 | 行为 |
|---|---|---|
SO_RCVBUF |
sk->sk_rcvbuf |
设置接收缓冲区大小(上限
sysctl_rmem_max) |
SO_SNDBUF |
sk->sk_sndbuf |
设置发送缓冲区大小(上限
sysctl_wmem_max) |
SO_REUSEADDR |
sk->sk_reuse |
允许绑定 TIME_WAIT 状态的地址 |
SO_REUSEPORT |
sk->sk_reuseport |
启用多 socket 端口共享 |
SO_KEEPALIVE |
sock_set_flag(SOCK_KEEPOPEN) |
启用 TCP 保活探测 |
SO_BINDTODEVICE |
sk->sk_bound_dev_if |
绑定到指定网络设备 |
SO_RCVTIMEO |
sk->sk_rcvtimeo |
接收超时 |
SO_SNDTIMEO |
sk->sk_sndtimeo |
发送超时 |
SO_ZEROCOPY |
sock_set_flag(SOCK_ZEROCOPY) |
启用零拷贝发送 |
SO_RCVBUF / SO_SNDBUF
有一个重要细节——内核把用户设的值翻倍(sk->sk_rcvbuf = min(val * 2, sysctl_rmem_max * 2)),因为内核需要额外空间存放
sk_buff 元数据。SO_RCVBUFFORCE /
SO_SNDBUFFORCE(需要
CAP_NET_ADMIN)可以绕过 sysctl 上限。
七、可观测性实战
socket 创建追踪
# 追踪 socket 创建——谁创建了什么类型的 socket
bpftrace -e '
kprobe:__sock_create {
printf("%s pid=%d family=%d type=%d proto=%d\n",
comm, pid, arg1, arg2, arg3);
}'SO_REUSEPORT 分发监控
# 追踪 reuseport 选择——查看分发是否均匀
bpftrace -e '
kprobe:reuseport_select_sock {
@select[tid] = nsecs;
}
kretprobe:reuseport_select_sock /@select[tid]/ {
$sk = (struct sock *)retval;
if ($sk != 0) {
@selected_port[$sk->__sk_common.skc_num] = count();
}
delete(@select[tid]);
}
interval:s:10 { print(@selected_port); clear(@selected_port); }'epoll 唤醒延迟
# 测量从数据到达到 epoll_wait 返回的延迟
bpftrace -e '
kprobe:sock_def_readable {
@wake_start[arg0] = nsecs;
}
tracepoint:syscalls:sys_exit_epoll_wait /args->ret > 0/ {
/* 粗略估算——实际需要关联 sk */
@epoll_wake_us = hist((nsecs - @last_readable) / 1000);
}'socket 缓冲区使用率
# 查看所有 TCP socket 的缓冲区使用率
ss -tmni | awk '/skmem/ {
match($0, /r([0-9]+)/, r);
match($0, /rb([0-9]+)/, rb);
if (rb[1] > 0) printf("recv: %d%%\n", r[1]*100/rb[1]);
}'
# bpftrace:实时追踪接收缓冲区使用
bpftrace -e '
kprobe:sock_rfree {
$sk = (struct sock *)arg0;
$rmem = $sk->sk_rmem_alloc.refs.counter;
$rcvbuf = $sk->sk_rcvbuf;
if ($rcvbuf > 0) {
@rcvbuf_pct = hist($rmem * 100 / $rcvbuf);
}
}'proto_ops 分发开销
# 测量 inet_sendmsg → tcp_sendmsg 的分发开销
bpftrace -e '
kprobe:inet_sendmsg { @start[tid] = nsecs; }
kprobe:tcp_sendmsg /@start[tid]/ {
@dispatch_ns = hist(nsecs - @start[tid]);
delete(@start[tid]);
}'八、关键参数速查
| 参数 | 默认值 | 内核对应 | 调优建议 |
|---|---|---|---|
net.core.somaxconn |
4096 | listen() backlog 上限 | 高并发服务器建议 65535 |
net.core.rmem_default |
212992 | sk_rcvbuf 默认值 |
按业务需求调整 |
net.core.rmem_max |
212992 | SO_RCVBUF 上限 |
高 BDP 场景增大到 16MB+ |
net.core.wmem_default |
212992 | sk_sndbuf 默认值 |
按业务需求调整 |
net.core.wmem_max |
212992 | SO_SNDBUF 上限 |
高吞吐场景增大 |
net.core.optmem_max |
20480 | 辅助数据缓冲区上限 | 大量 cmsg 场景增大 |
net.ipv4.ip_local_port_range |
32768 60999 | 自动分配端口范围 | 高连接率扩大到 1024-65535 |
net.core.netdev_max_backlog |
1000 | softirq 队列深度 | 高 PPS 增大到 5000+ |
参考文献
- Linux
内核源码,
include/linux/net.h,6.8(struct socket 定义于第 117 行,struct proto_ops 于第 160 行) - Linux
内核源码,
include/net/sock.h,6.8(struct sock 定义于第 341 行,struct proto 于第 1223 行) - Linux
内核源码,
include/net/protocol.h,6.8(struct inet_protosw 定义于第 76 行) - Linux
内核源码,
include/net/sock_reuseport.h,6.8(struct sock_reuseport 于第 13 行) - Stevens, Fenner, Rudoff, “UNIX Network Programming, Volume 1”, 3rd Edition, 2003(socket API 参考)
- Linux
内核文档,
Documentation/networking/scaling.rst
上一篇:UDP 内核实现与 socket lookup 优化
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Linux 网络子系统深度拆解】UDP 内核实现与 socket lookup 优化
UDP 简单?在内核中它一点都不简单。双哈希表 socket 查找、SO_REUSEPORT 多核分发、Early Demux 路由缓存、UDP GRO 聚合、reader_queue 无锁读、forward allocation 内存管理、UDP 封装(ESP/L2TP/VXLAN)——本文从 Linux 6.6 内核源码拆解 UDP 的每一个优化细节。
【Linux 网络子系统深度拆解】网络子系统内存管理:sk_buff 分配、page pool 与 NUMA
从内核源码拆解网络子系统的内存管理全貌:sk_buff 分配路径与 slab 缓存、page_pool 页面回收机制、NUMA 感知分配策略、socket 内存记账与反压,以及 bpftrace 可观测实战。
【Linux 网络子系统深度拆解】邻居子系统与 ARP:L2 地址解析的内核实现
IP 层知道下一跳是 10.0.0.1,但网卡发帧需要 MAC 地址。ARP 解析只是表面——底层是邻居子系统(neighbour subsystem)的完整状态机:NUD_INCOMPLETE → NUD_REACHABLE → NUD_STALE → NUD_DELAY → NUD_PROBE → NUD_FAILED。本文从 Linux 6.6 内核源码拆解 struct neighbour、neigh_table 双哈希表、ARP 请求/响应处理、NDP(IPv6)、Proxy ARP、GC 回收机制,以及 neigh_connected_output 快路径的 L2 头缓存优化。
【Linux 网络子系统深度拆解】TCP 内核实现(下):数据传输与拥塞控制
tcp_sendmsg 把用户数据拷到 sk_buff 就完事了?远没有。后面还有 Nagle 合并、TSQ 限流、cwnd/rwnd 双窗口门控、RACK-TLP 丢包检测、拥塞状态机五态跳转、sk_pacing_rate 软件限速。本文从 Linux 6.6 内核源码拆解 TCP 数据传输的完整路径——从 send() 到 ACK 处理——以及拥塞控制框架 tcp_congestion_ops 的可插拔架构。