4. 套接字层实现:struct socket 与 struct sock 的关系、socket 系统调用流程、协议族注册与查找

套接字层是用户态网络编程与内核协议栈之间的桥梁。理解 struct socket 与 struct sock 的双层抽象、系统调用在内核中的完整流转路径,以及协议族的注册与查找机制,是掌握 Linux 网络协议栈的基石。本章将深入剖析这三个核心主题。

4.1 双层抽象:struct socket 与 struct sock

Linux 内核将套接字抽象为两个层次,分别服务于不同的关注点:

  • struct socket:通用套接字层,面向 VFS(虚拟文件系统),负责与用户态文件描述符交互。它不关心具体的协议细节。
  • struct sock:协议相关的套接字层,面向具体的传输协议(如 TCP、UDP)。它包含了协议状态机、缓冲区、拥塞控制等核心数据。

这种设计实现了“通用接口 + 协议特定实现”的解耦,使得添加新协议族时无需修改 VFS 层代码。

面试高频点: 面试官常问“为什么需要两个结构体?” 核心答案是:struct socket 提供统一的文件操作接口(如 read/write),而 struct sock 封装了协议特有的状态和逻辑。一个 struct socket 通过 sk 指针唯一关联一个 struct sock。

4.1.1 核心数据结构对比

特性 struct socket struct sock
所属层次 通用套接字层(VFS 层) 协议特定层(传输层)
主要字段 state, type, ops, sk sk_state, sk_receive_queue, sk_write_queue, sk_prot
与用户态交互 通过 file->private_data 关联 不直接与用户态交互
生命周期 由 sock_alloc 创建,sock_release 销毁 由 sk_alloc 创建,sk_free 销毁
协议操作集 struct proto_ops *ops struct proto *sk_prot

4.1.2 关联关系示意图

下图展示了 struct socket 与 struct sock 之间的 1:1 映射关系,以及它们如何通过 VFS 和协议层与外部交互。

struct socket 与 struct sock 关联关系 用户态进程 VFS (struct file) struct socket state, type, ops struct sock sk_state, sk_prot, queues 协议实现 (TCP/UDP) sk 指针 sk_prot 关键关系 socket->sk = sock sock->sk_socket = socket 双向指针引用

4.2 socket 系统调用完整流程

当用户态调用 socket(AF_INET, SOCK_STREAM, 0) 时,内核经历了一条从 VFS 到具体协议实现的完整路径。理解这条路径是分析网络性能问题和调试内核的基础。

4.2.1 核心调用链

  1. 系统调用入口:__x64_sys_socket() 处理用户态传入的参数,进入内核通用套接字层。
  2. 套接字创建:sock_create() 根据协议族(AF_INET)和类型(SOCK_STREAM)查找对应的 net_family,并调用其 create 方法。
  3. 协议族回调:对于 AF_INET,调用 inet_create(),该函数负责分配 struct sock 并初始化 TCP/UDP 协议控制块。
  4. 文件描述符绑定:sock_map_fd() 将 struct socket 与一个新的文件描述符关联,并设置 file->f_op 为套接字文件操作集。
工程实战提示: 在调试网络问题时,可以通过 strace -e socket 观察系统调用的返回值和参数。内核中可以通过 trace_sock_create 或 kprobe 挂载 __sys_socket 函数来追踪创建流程。

4.2.2 socket 系统调用流程图

socket 系统调用完整流程 用户态: socket() __x64_sys_socket() sock_create() net_families 查找 inet_create() sk_alloc() + init sock_map_fd() 返回文件描述符 关键函数调用栈 sys_socket -> sock_create -> inet_create -> sock_map_fd

4.3 协议族注册与查找机制

内核支持多种协议族(如 AF_INET、AF_INET6、AF_UNIX、AF_PACKET 等)。这些协议族通过一个全局数组 net_families 进行管理,实现了可插拔的协议族架构。

4.3.1 协议族注册

协议族在模块初始化或内核启动时通过 sock_register() 函数注册到 net_families 数组中。每个协议族需要提供一个 struct net_proto_family 结构体,其中最重要的是 create 回调函数。

// net/socket.c
#define NPROTO 256  // 最大支持的协议族数量
static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

int sock_register(const struct net_proto_family *ops)
{
    int err;
    if (ops->family >= NPROTO)
        return -ENOBUFS;
    // 检查是否已注册
    if (rcu_dereference_protected(net_families[ops->family], 1) != NULL)
        return -EEXIST;
    // 写入数组
    rcu_assign_pointer(net_families[ops->family], ops);
    err = 0;
    pr_info("NET: Registered protocol family %d\n", ops->family);
    return err;
}
EXPORT_SYMBOL(sock_register);

4.3.2 协议族查找

当 sock_create() 被调用时,内核通过 __sock_create() 函数在 net_families 数组中查找对应的协议族,并调用其 create 方法。

// net/socket.c
int __sock_create(struct net *net, int family, int type, int protocol,
                  struct socket **res, int kern)
{
    int err;
    struct socket *sock;
    const struct net_proto_family *pf;

    // 1. 分配 struct socket
    sock = sock_alloc();
    if (!sock)
        return -ENOMEM;

    // 2. 查找协议族
    rcu_read_lock();
    pf = rcu_dereference(net_families[family]);
    if (unlikely(!pf)) {
        rcu_read_unlock();
        err = -EAFNOSUPPORT;
        goto out_release;
    }

    // 3. 调用协议族的 create 方法
    err = pf->create(net, sock, protocol, kern);
    rcu_read_unlock();
    // ...
}
EXPORT_SYMBOL(__sock_create);

4.3.3 常见协议族注册示例

协议族 family 值 注册函数 create 回调
AF_UNIX 1 unix_proto_init() unix_create()
AF_INET 2 inet_init() inet_create()
AF_INET6 10 inet6_init() inet6_create()
AF_PACKET 17 packet_init() packet_create()
AF_NETLINK 16 netlink_proto_init() netlink_create()

4.3.4 协议族注册与查找架构图

协议族注册与查找架构 net_families[] [1] [2] [10] [16] [17] AF_UNIX (family=1) AF_INET (family=2) AF_INET6 (family=10) AF_NETLINK (family=16) AF_PACKET (family=17) unix_create() inet_create() inet6_create() netlink_create() packet_create() 查找流程 sock_create(family) -> net_families[family] -> pf->create()
避坑指南: 在编写内核模块或自定义协议族时,必须确保 family 值不与已有协议族冲突。建议使用 AF_MAX 以上的值(如 40+)进行实验。同时,注册时需注意 net_families 数组使用 RCU 保护,读取时必须在 RCU 读锁内进行。

4.4 本章小结

  • 双层抽象:struct socket 面向 VFS,struct sock 面向协议,通过 sk 指针双向关联,实现通用接口与协议实现的解耦。
  • 系统调用流程:从 sys_socket 到 sock_create,再到协议族特定的 create 回调,最后通过 sock_map_fd 绑定文件描述符,形成完整的创建链路。
  • 协议族管理:通过 net_families 数组实现 O(1) 查找,sock_register 负责注册,__sock_create 负责查找和调用,支持动态加载和卸载协议族模块。