当前位置:首页>Linux>深入Linux网络子系统01:socket() 到底做了什么?

深入Linux网络子系统01:socket() 到底做了什么?

  • 2026-09-11 00:51:14
深入Linux网络子系统01:socket() 到底做了什么?

前言

  学习Linux网络编程,接触的第一个函数是socket(),在过往使用中,并没有真正搞懂操作系统做了什么。本文旨在记录学习Linux网络子系统源码的过程与思考,不死扣代码细节,仅聚焦于核心逻辑和数据流,希望能和同样对Linux网络感兴趣的朋友一起探讨学习。

一、一行代码,看懂入口

如无特殊说明,代码以TCP为例

int fd = socket(int domain, int type, int protocol);

socket 的类型由三个参数决定:

  • domain:协议族,决定套接字的寻址方式
    • AF_INET,使用IPv4协议
    • AF_INET6:使用IPv6协议
  • type:套接字类型,决定数据传输方式
    • SOCK_STREAM,提供有序、可靠、双向、基于连接的字节流,如TCP
    • SOCK_DGRAM,支持数据报(无连接、不可靠的、具有固定最大长度的消息),如UDP
  • protocol:具体传输协议,通常填0让系统选择
    • SOCK_STREAM:默认使用TCP
    • SOCK_DGRAM:默认使用UDP

返回值:

  • 成功:返回一个非负整数的文件描述符
  • 失败:-1

二、重点:调用 socket() 时,内核默默做了什么事情

1.调用框图

2.关键结构体

2.1 操作集

// IPv4协议族staticconststructnet_proto_familyinet_family_ops = { .family = PF_INET, .create = inet_create, .owner = THIS_MODULE,};// 该数组定义了 IPv4 协议族下 TCP、UDP、ICMP 和 RAW 四种套接字类型的协议实现(prot)与操作接口(ops)staticstructinet_protoswinetsw_array[] ={ {  .type =       SOCK_STREAM,  .protocol =   IPPROTO_TCP,// 定义了协议本身的具体实现,负责具体协议算法的实现  .prot =       &tcp_prot,// 定义了套接字层的操作接口,负责将用户空间的请求转化为内核协议栈的调用  .ops =        &inet_stream_ops,  .flags =      INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK, }, {  .type =       SOCK_DGRAM,  .protocol =   IPPROTO_UDP,  .prot =       &udp_prot,  .ops =        &inet_dgram_ops,  .flags =      INET_PROTOSW_PERMANENT, }, {  .type =       SOCK_DGRAM,  .protocol =   IPPROTO_ICMP,  .prot =       &ping_prot,  .ops =        &inet_sockraw_ops,  .flags =      INET_PROTOSW_REUSE, }, {  .type =       SOCK_RAW,  .protocol =   IPPROTO_IP,  .prot =       &raw_prot,  .ops =        &inet_sockraw_ops,  .flags =      INET_PROTOSW_REUSE, }};// IPv4 地址族专有操作conststructinet_connection_sock_af_opsipv4_specific = { .queue_xmit    = ip_queue_xmit, .send_check    = tcp_v4_send_check, .rebuild_header    = inet_sk_rebuild_header, .sk_rx_dst_set    = inet_sk_rx_dst_set, .conn_request    = tcp_v4_conn_request, .syn_recv_sock    = tcp_v4_syn_recv_sock, .net_header_len    = sizeof(struct iphdr), .setsockopt    = ip_setsockopt, .getsockopt    = ip_getsockopt, .addr2sockaddr    = inet_csk_addr2sockaddr, .sockaddr_len    = sizeof(struct sockaddr_in), .bind_conflict    = inet_csk_bind_conflict, .mtu_reduced    = tcp_v4_mtu_reduced,};

2.2 socket 相关结构体

structsocket:面向用户态structsock:面向内核协议栈structinet_sock:AF_INET家族扩展, IPv4特有字段structinet_connection_sock:面向连接的传输层公共部分, 如TCPstructtcp_sock:TCP协议全部状态与控制块

在 Linux 内核网络协议栈中,struct socket、struct sock、struct inet_sock、struct inet_connection_sock和 struct tcp_sock是层层嵌套、逐步特化的数据结构,共同构成了从通用套接字接口到具体 TCP 连接控制块的完整抽象链。

3.调用过程

socket的相关函数都在net\socket.c中,socket()方法主要调用了两个函数,如下:

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol){// 创建 socket 对象    retval = sock_create(family, type, protocol, &sock);// 将创建好的 struct socket 对象与一个文件描述符绑定,并返回该文件描述符给用户空间    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));}

3.1 sock_create

该函数调用 __sock_create() ,其关键代码如下:

int __sock_create(struct net *net, int family, int type, int protocol, struct socket **res, int kern){// 分配一个 struct socket 对象structsocket *sock;    sock = sock_alloc();// 协议族 net_proto_family 通过 net/ipv4/af_inet.c 中的 sock_register(&inet_family_ops) 注册进来    pf = rcu_dereference(net_families[family]);// AF_INET 对应的是 int inet_create(...)    err = pf->create(net, sock, protocol, kern);}

inet_create主要做的工作包括:

staticintinet_create(struct net *net, struct socket *sock, int protocol, int kern){structsock *sk;structinet_sock *inet;// 设置监听 socket 状态为 未连接状态    sock->state = SS_UNCONNECTED;    list_for_each_entry_rcu(answer, &inetsw[sock->type], list)    {// inet_stream_ops 操作集赋值到 socket 上        sock->ops = answer->ops;// 获取 tcp_prot        answer_prot = answer->prot;// 分配一个 struct sock 对象,并将其与指定的协议族(IPv4)、协议实现(tcp_prot)关联起来        sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);// 初始化 sock 对象,并将 socket 和 sock 关联起来        sock_init_data(sock, sk);// 初始化TCP套接字,实际调用 tcp_prot 中的 tcp_v4_init_sock 方法        err = sk->sk_prot->init(sk);    }}

sock_init_data主要工作:

voidsock_init_data(struct socket *sock, struct sock *sk){// 接收队列 已经到达内核、等待用户 recvmsg()取走的 skb,初始化为空链表 skb_queue_head_init(&sk->sk_receive_queue);// 发送队列 已发送但尚未得到 ACK或待发送的 skb,初始化为空链表 skb_queue_head_init(&sk->sk_write_queue);// 设置监听 sock 状态为 关闭状态 sk->sk_state  = TCP_CLOSE;// 建立双向指针:sock->sk = sk,sk->sk_socket = sock sk_set_socket(sk, sock); sock->sk = sk;// 设置默认回调函数 sk->sk_state_change = sock_def_wakeup; sk->sk_data_ready = sock_def_readable; sk->sk_write_space = sock_def_write_space;}

tcp_v4_init_sock的主要工作:

staticinttcp_v4_init_sock(struct sock *sk){// 面向连接的 socket 扩展结构体structinet_connection_sock *icsk = inet_csk(sk);// 在 sock_init_data的基础上进一步设置 TCP 特有的字段,使 socket 具备 TCP 协议运行所需的状态和参数 tcp_init_sock(sk);// 绑定 IPv4 特定的网络层操作 icsk->icsk_af_ops = &ipv4_specific;}

tcp_init_sock的主要工作:

voidtcp_init_sock(struct sock *sk){structinet_connection_sock *icsk = inet_csk(sk);structtcp_sock *tp = tcp_sk(sk);// 初始化乱序队列 tp->out_of_order_queue = RB_ROOT;// 初始化TCP定时器(重传定时器、延迟 ACK 定时器、keepalive 定时器) tcp_init_xmit_timers(sk);// 初始化 prequeue tcp_prequeue_init(tp);// 设置 TCP 缓冲区大小 sk->sk_sndbuf = sysctl_tcp_wmem[1]; sk->sk_rcvbuf = sysctl_tcp_rmem[1];}

至此,一个 SOCK_STREAM 内核对象已经创建好了。

3.2 sock_map_fd

该函数关键代码如下:

staticintsock_map_fd(struct socket *sock, int flags){structfile *newfile;// 在当前进程的文件描述符表中找到一个空闲的 slot,并返回其索引int fd = get_unused_fd_flags(flags);// sock_alloc_file分配一个新的 struct file实例,并建立与 socket 的双向关联:// 1. 将 sock->file指向新文件// 2. 将 file->private_data指向 sock newfile = sock_alloc_file(sock, flags, NULL);}

三、总结

对socket()方法主要做了以下几件事情:

  1. 分配内核 socket 结构:面向用户
  2. 设置监听 socket 状态为 SS_UNCONNECTED
  3. 创建并初始化协议对应的 sock:面向网络协议栈
  4. 设置监听 sock 状态为 TCP_CLOSE
  5. 初始化接收队列、发送队列、乱序队列
  6. 初始化缓冲区大小
  7. 绑定文件描述符
  8. 返回文件描述符

四、后续预告

本篇是网络系统调用开篇,下一篇我们继续拆解: bind() 到底干了什么?为什么客户端通常不需要 bind?内核端口绑定原理?

五、写在最后

以往学习过程中经常只输入不输出,这次坚持记录每一个关键知识点、每一次踩坑与复盘,不追求速成,坚持稳步积累,慢慢搭建属于自己的网络、运维、性能调优技术体系,能将实际遇到的问题转换成机器语言。

最新文章

随机文章