土法炼钢兴趣小组的算法知识备份

【Linux 网络子系统深度拆解】Socket 层内核实现:从 VFS 到协议栈的桥梁

文章导航

分类入口
linuxnetworking
标签入口
#socket#linux-kernel#vfs#proto-ops#reuseport#epoll#sock-alloc#inet-create#bpftrace

目录

前面几篇我们从收包路径到 TCP/UDP 协议实现,始终有一个隐含的入口没有拆解——socket。用户态程序看到的是一个文件描述符,内核看到的是一组精心组织的分发表和数据结构。socket 层是 VFS 文件系统抽象和协议栈实现之间的翻译层。

当你写下 socket(AF_INET, SOCK_STREAM, 0) 时,内核做了这些事:

  1. 分配 struct socket(VFS 层)+ struct inode——合并为一次内存分配
  2. 查找地址族工厂(AF_INETinet_create
  3. 根据 (SOCK_STREAM, 0) 查找协议注册表 → 得到 tcp_prot + inet_stream_ops
  4. tcp_prot->slab(专用 kmem_cache)分配 struct sock
  5. 互相关联:socket->sk = socksock->sk_socket = socket
  6. 创建 VFS file,绑定到进程的文件描述符表

之后每次 send() / recv() 调用,都经过两层分发:先通过 socket->opsproto_ops)做 VFS 语义翻译,再通过 sock->sk_protproto)调用协议实现。

本文从内核源码出发,完整拆解这个双层架构。

下面这张图展示了 socket 的双层分发架构——从 VFS 文件操作到 proto_ops 再到 proto 的完整调用链:

Socket 双层分发架构

一、核心数据结构:双层分发的基石

struct socket——VFS 层的 socket 表示

struct socketinclude/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 层面的 ESTABLISHEDCLOSE_WAIT 等状态存在 sock->sk_state 中。

struct sock——协议层的 socket 表示

struct sockinclude/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_commoninclude/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_opsinclude/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 *);
    /* ... */
};

protoinclude/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_opsproto 的分离让同一协议能支持不同的 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_allocinclude/net/sock.h:1555)把 struct socketstruct 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_protoswinclude/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_opsinclude/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_sendmsgudp_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_reuseportinclude/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+

参考文献

  1. Linux 内核源码,include/linux/net.h,6.8(struct socket 定义于第 117 行,struct proto_ops 于第 160 行)
  2. Linux 内核源码,include/net/sock.h,6.8(struct sock 定义于第 341 行,struct proto 于第 1223 行)
  3. Linux 内核源码,include/net/protocol.h,6.8(struct inet_protosw 定义于第 76 行)
  4. Linux 内核源码,include/net/sock_reuseport.h,6.8(struct sock_reuseport 于第 13 行)
  5. Stevens, Fenner, Rudoff, “UNIX Network Programming, Volume 1”, 3rd Edition, 2003(socket API 参考)
  6. Linux 内核文档,Documentation/networking/scaling.rst

上一篇UDP 内核实现与 socket lookup 优化

下一篇邻居子系统与 ARP:L2 地址解析的内核实现

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】UDP 内核实现与 socket lookup 优化

UDP 简单?在内核中它一点都不简单。双哈希表 socket 查找、SO_REUSEPORT 多核分发、Early Demux 路由缓存、UDP GRO 聚合、reader_queue 无锁读、forward allocation 内存管理、UDP 封装(ESP/L2TP/VXLAN)——本文从 Linux 6.6 内核源码拆解 UDP 的每一个优化细节。

2026-04-20 · linux / networking

【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 头缓存优化。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】TCP 内核实现(下):数据传输与拥塞控制

tcp_sendmsg 把用户数据拷到 sk_buff 就完事了?远没有。后面还有 Nagle 合并、TSQ 限流、cwnd/rwnd 双窗口门控、RACK-TLP 丢包检测、拥塞状态机五态跳转、sk_pacing_rate 软件限速。本文从 Linux 6.6 内核源码拆解 TCP 数据传输的完整路径——从 send() 到 ACK 处理——以及拥塞控制框架 tcp_congestion_ops 的可插拔架构。


By .