目录前言一、一行代码看懂入口二、重点调用 socket() 时内核默默做了什么事情1.调用框图2.关键结构体2.1 操作集2.2 socket 相关结构体3.调用过程3.1 sock_create3.2 sock_map_fd三、总结四、后续预告五、写在最后前言学习Linux网络编程接触的第一个函数是socket()在过往使用中并没有真正搞懂操作系统做了什么。本文旨在记录学习Linux网络子系统源码的过程与思考不死扣代码细节仅聚焦于核心逻辑和数据流希望能和同样对Linux网络感兴趣的朋友一起探讨学习。一、一行代码看懂入口如无特殊说明代码以TCP为例intfdsocket(intdomain,inttype,intprotocol);socket 的类型由三个参数决定domain协议族决定套接字的寻址方式AF_INET使用IPv4协议AF_INET6使用IPv6协议type套接字类型决定数据传输方式SOCK_STREAM提供有序、可靠、双向、基于连接的字节流如TCPSOCK_DGRAM支持数据报无连接、不可靠的、具有固定最大长度的消息如UDPprotocol具体传输协议通常填0让系统选择SOCK_STREAM默认使用TCPSOCK_DGRAM默认使用UDP返回值成功返回一个非负整数的文件描述符失败-1二、重点调用 socket() 时内核默默做了什么事情1.调用框图socket()sock_create()__sock_create()sock_alloc() 分配 socketpf-create() → inet_create()sk_alloc() 分配 socksock_init_data() 初始化 sock关联 socket 与 socksk-sk_prot-init() → tcp_v4_init_sock() 初始化 TCPsock_map_fd()get_unused_fd_flags() 获取空闲 fdsock_alloc_file() 分配 file关联 socket 与 file返回文件描述符 fd2.关键结构体2.1 操作集// IPv4协议族staticconststructnet_proto_familyinet_family_ops{.familyPF_INET,.createinet_create,.ownerTHIS_MODULE,};// 该数组定义了 IPv4 协议族下 TCP、UDP、ICMP 和 RAW 四种套接字类型的协议实现prot与操作接口opsstaticstructinet_protoswinetsw_array[]{{.typeSOCK_STREAM,.protocolIPPROTO_TCP,// 定义了协议本身的具体实现负责具体协议算法的实现.prottcp_prot,// 定义了套接字层的操作接口负责将用户空间的请求转化为内核协议栈的调用.opsinet_stream_ops,.flagsINET_PROTOSW_PERMANENT|INET_PROTOSW_ICSK,},{.typeSOCK_DGRAM,.protocolIPPROTO_UDP,.protudp_prot,.opsinet_dgram_ops,.flagsINET_PROTOSW_PERMANENT,},{.typeSOCK_DGRAM,.protocolIPPROTO_ICMP,.protping_prot,.opsinet_sockraw_ops,.flagsINET_PROTOSW_REUSE,},{.typeSOCK_RAW,.protocolIPPROTO_IP,.protraw_prot,.opsinet_sockraw_ops,.flagsINET_PROTOSW_REUSE,}};// IPv4 地址族专有操作conststructinet_connection_sock_af_opsipv4_specific{.queue_xmitip_queue_xmit,.send_checktcp_v4_send_check,.rebuild_headerinet_sk_rebuild_header,.sk_rx_dst_setinet_sk_rx_dst_set,.conn_requesttcp_v4_conn_request,.syn_recv_socktcp_v4_syn_recv_sock,.net_header_lensizeof(structiphdr),.setsockoptip_setsockopt,.getsockoptip_getsockopt,.addr2sockaddrinet_csk_addr2sockaddr,.sockaddr_lensizeof(structsockaddr_in),.bind_conflictinet_csk_bind_conflict,.mtu_reducedtcp_v4_mtu_reduced,};2.2 socket 相关结构体structsocketstructsockstructinet_sockstructinet_connection_sockstructtcp_sock在 Linux 内核网络协议栈中struct socket、struct sock、struct inet_sock、struct inet_connection_sock和 struct tcp_sock是层层嵌套、逐步特化的数据结构共同构成了从通用套接字接口到具体 TCP 连接控制块的完整抽象链。整体继承关系如下struct socket(面向用户态)struct sock(面向内核协议栈)struct inet_sock(AF_INET 家族扩展, IPv4 特有字段)struct inet_connection_sock(面向连接的传输层公共部分, 如 TCP)struct tcp_sock(TCP 协议全部状态与控制块)3.调用过程socket的相关函数都在net\socket.c中socket()方法主要调用了两个函数如下SYSCALL_DEFINE3(socket,int,family,int,type,int,protocol){// 创建 socket 对象retvalsock_create(family,type,protocol,sock);//retvalsock_map_fd(sock,flags(O_CLOEXEC|O_NONBLOCK));}3.1 sock_create该函数调用 __sock_create() 其关键代码如下int__sock_create(structnet*net,intfamily,inttype,intprotocol,structsocket**res,intkern){// 分配一个 struct socket 对象structsocket*sock;socksock_alloc();// 协议族 net_proto_family 通过 net/ipv4/af_inet.c 中的 sock_register(inet_family_ops) 注册进来pfrcu_dereference(net_families[family]);// AF_INET 对应的是 int inet_create(...)errpf-create(net,sock,protocol,kern);}inet_create主要做的工作包括staticintinet_create(structnet*net,structsocket*sock,intprotocol,intkern){structsock*sk;structinet_sock*inet;// 设置监听 socket 状态为 未连接状态sock-stateSS_UNCONNECTED;list_for_each_entry_rcu(answer,inetsw[sock-type],list){// inet_stream_ops 操作集赋值到 socket 上sock-opsanswer-ops;// 获取 tcp_protanswer_protanswer-prot;// 分配一个 struct sock 对象并将其与指定的协议族IPv4、协议实现tcp_prot关联起来sksk_alloc(net,PF_INET,GFP_KERNEL,answer_prot,kern);// 初始化 sock 对象并将 socket 和 sock 关联起来sock_init_data(sock,sk);// 初始化TCP套接字实际调用 tcp_prot 中的 tcp_v4_init_sock 方法errsk-sk_prot-init(sk);}}sock_init_data主要工作voidsock_init_data(structsocket*sock,structsock*sk){// 接收队列 已经到达内核、等待用户 recvmsg()取走的 skb初始化为空链表skb_queue_head_init(sk-sk_receive_queue);// 发送队列 已发送但尚未得到 ACK或待发送的 skb初始化为空链表skb_queue_head_init(sk-sk_write_queue);// 设置监听 sock 状态为 关闭状态sk-sk_stateTCP_CLOSE;// 建立双向指针sock-sk sksk-sk_socket socksk_set_socket(sk,sock);sock-sksk;// 设置默认回调函数sk-sk_state_changesock_def_wakeup;sk-sk_data_readysock_def_readable;sk-sk_write_spacesock_def_write_space;}tcp_v4_init_sock的主要工作staticinttcp_v4_init_sock(structsock*sk){// 面向连接的 socket 扩展结构体structinet_connection_sock*icskinet_csk(sk);// 在 sock_init_data的基础上进一步设置 TCP 特有的字段使 socket 具备 TCP 协议运行所需的状态和参数tcp_init_sock(sk);// 绑定 IPv4 特定的网络层操作icsk-icsk_af_opsipv4_specific;}tcp_init_sock的主要工作voidtcp_init_sock(structsock*sk){structinet_connection_sock*icskinet_csk(sk);structtcp_sock*tptcp_sk(sk);// 初始化乱序队列tp-out_of_order_queueRB_ROOT;// 初始化TCP定时器重传定时器、延迟 ACK 定时器、keepalive 定时器tcp_init_xmit_timers(sk);// 初始化 prequeuetcp_prequeue_init(tp);// 设置 TCP 缓冲区大小sk-sk_sndbufsysctl_tcp_wmem[1];sk-sk_rcvbufsysctl_tcp_rmem[1];}至此一个 SOCK_STREAM 内核对象已经创建好了。3.2 sock_map_fd该函数关键代码如下staticintsock_map_fd(structsocket*sock,intflags){structfile*newfile;// 在当前进程的文件描述符表中找到一个空闲的 slot并返回其索引intfdget_unused_fd_flags(flags);// sock_alloc_file分配一个新的 struct file实例并建立与 socket 的双向关联// 1. 将 sock-file指向新文件// 2. 将 file-private_data指向 socknewfilesock_alloc_file(sock,flags,NULL);}三、总结对socket()方法主要做了以下几件事情分配内核 socket 结构面向用户设置监听 socket 状态为 SS_UNCONNECTED创建并初始化协议对应的 sock面向网络协议栈设置监听 sock 状态为 TCP_CLOSE初始化接收队列、发送队列、乱序队列初始化缓冲区大小绑定文件描述符返回文件描述符四、后续预告本篇是网络系统调用开篇下一篇我们继续拆解bind() 到底干了什么为什么客户端通常不需要 bind内核端口绑定原理五、写在最后以往学习过程中经常只输入不输出这次坚持记录每一个关键知识点、每一次踩坑与复盘不追求速成坚持稳步积累慢慢搭建属于自己的网络、运维、性能调优技术体系能将实际遇到的问题转换成机器语言。
深入Linux网络子系统01:socket() 到底做了什么?
目录前言一、一行代码看懂入口二、重点调用 socket() 时内核默默做了什么事情1.调用框图2.关键结构体2.1 操作集2.2 socket 相关结构体3.调用过程3.1 sock_create3.2 sock_map_fd三、总结四、后续预告五、写在最后前言学习Linux网络编程接触的第一个函数是socket()在过往使用中并没有真正搞懂操作系统做了什么。本文旨在记录学习Linux网络子系统源码的过程与思考不死扣代码细节仅聚焦于核心逻辑和数据流希望能和同样对Linux网络感兴趣的朋友一起探讨学习。一、一行代码看懂入口如无特殊说明代码以TCP为例intfdsocket(intdomain,inttype,intprotocol);socket 的类型由三个参数决定domain协议族决定套接字的寻址方式AF_INET使用IPv4协议AF_INET6使用IPv6协议type套接字类型决定数据传输方式SOCK_STREAM提供有序、可靠、双向、基于连接的字节流如TCPSOCK_DGRAM支持数据报无连接、不可靠的、具有固定最大长度的消息如UDPprotocol具体传输协议通常填0让系统选择SOCK_STREAM默认使用TCPSOCK_DGRAM默认使用UDP返回值成功返回一个非负整数的文件描述符失败-1二、重点调用 socket() 时内核默默做了什么事情1.调用框图socket()sock_create()__sock_create()sock_alloc() 分配 socketpf-create() → inet_create()sk_alloc() 分配 socksock_init_data() 初始化 sock关联 socket 与 socksk-sk_prot-init() → tcp_v4_init_sock() 初始化 TCPsock_map_fd()get_unused_fd_flags() 获取空闲 fdsock_alloc_file() 分配 file关联 socket 与 file返回文件描述符 fd2.关键结构体2.1 操作集// IPv4协议族staticconststructnet_proto_familyinet_family_ops{.familyPF_INET,.createinet_create,.ownerTHIS_MODULE,};// 该数组定义了 IPv4 协议族下 TCP、UDP、ICMP 和 RAW 四种套接字类型的协议实现prot与操作接口opsstaticstructinet_protoswinetsw_array[]{{.typeSOCK_STREAM,.protocolIPPROTO_TCP,// 定义了协议本身的具体实现负责具体协议算法的实现.prottcp_prot,// 定义了套接字层的操作接口负责将用户空间的请求转化为内核协议栈的调用.opsinet_stream_ops,.flagsINET_PROTOSW_PERMANENT|INET_PROTOSW_ICSK,},{.typeSOCK_DGRAM,.protocolIPPROTO_UDP,.protudp_prot,.opsinet_dgram_ops,.flagsINET_PROTOSW_PERMANENT,},{.typeSOCK_DGRAM,.protocolIPPROTO_ICMP,.protping_prot,.opsinet_sockraw_ops,.flagsINET_PROTOSW_REUSE,},{.typeSOCK_RAW,.protocolIPPROTO_IP,.protraw_prot,.opsinet_sockraw_ops,.flagsINET_PROTOSW_REUSE,}};// IPv4 地址族专有操作conststructinet_connection_sock_af_opsipv4_specific{.queue_xmitip_queue_xmit,.send_checktcp_v4_send_check,.rebuild_headerinet_sk_rebuild_header,.sk_rx_dst_setinet_sk_rx_dst_set,.conn_requesttcp_v4_conn_request,.syn_recv_socktcp_v4_syn_recv_sock,.net_header_lensizeof(structiphdr),.setsockoptip_setsockopt,.getsockoptip_getsockopt,.addr2sockaddrinet_csk_addr2sockaddr,.sockaddr_lensizeof(structsockaddr_in),.bind_conflictinet_csk_bind_conflict,.mtu_reducedtcp_v4_mtu_reduced,};2.2 socket 相关结构体structsocketstructsockstructinet_sockstructinet_connection_sockstructtcp_sock在 Linux 内核网络协议栈中struct socket、struct sock、struct inet_sock、struct inet_connection_sock和 struct tcp_sock是层层嵌套、逐步特化的数据结构共同构成了从通用套接字接口到具体 TCP 连接控制块的完整抽象链。整体继承关系如下struct socket(面向用户态)struct sock(面向内核协议栈)struct inet_sock(AF_INET 家族扩展, IPv4 特有字段)struct inet_connection_sock(面向连接的传输层公共部分, 如 TCP)struct tcp_sock(TCP 协议全部状态与控制块)3.调用过程socket的相关函数都在net\socket.c中socket()方法主要调用了两个函数如下SYSCALL_DEFINE3(socket,int,family,int,type,int,protocol){// 创建 socket 对象retvalsock_create(family,type,protocol,sock);//retvalsock_map_fd(sock,flags(O_CLOEXEC|O_NONBLOCK));}3.1 sock_create该函数调用 __sock_create() 其关键代码如下int__sock_create(structnet*net,intfamily,inttype,intprotocol,structsocket**res,intkern){// 分配一个 struct socket 对象structsocket*sock;socksock_alloc();// 协议族 net_proto_family 通过 net/ipv4/af_inet.c 中的 sock_register(inet_family_ops) 注册进来pfrcu_dereference(net_families[family]);// AF_INET 对应的是 int inet_create(...)errpf-create(net,sock,protocol,kern);}inet_create主要做的工作包括staticintinet_create(structnet*net,structsocket*sock,intprotocol,intkern){structsock*sk;structinet_sock*inet;// 设置监听 socket 状态为 未连接状态sock-stateSS_UNCONNECTED;list_for_each_entry_rcu(answer,inetsw[sock-type],list){// inet_stream_ops 操作集赋值到 socket 上sock-opsanswer-ops;// 获取 tcp_protanswer_protanswer-prot;// 分配一个 struct sock 对象并将其与指定的协议族IPv4、协议实现tcp_prot关联起来sksk_alloc(net,PF_INET,GFP_KERNEL,answer_prot,kern);// 初始化 sock 对象并将 socket 和 sock 关联起来sock_init_data(sock,sk);// 初始化TCP套接字实际调用 tcp_prot 中的 tcp_v4_init_sock 方法errsk-sk_prot-init(sk);}}sock_init_data主要工作voidsock_init_data(structsocket*sock,structsock*sk){// 接收队列 已经到达内核、等待用户 recvmsg()取走的 skb初始化为空链表skb_queue_head_init(sk-sk_receive_queue);// 发送队列 已发送但尚未得到 ACK或待发送的 skb初始化为空链表skb_queue_head_init(sk-sk_write_queue);// 设置监听 sock 状态为 关闭状态sk-sk_stateTCP_CLOSE;// 建立双向指针sock-sk sksk-sk_socket socksk_set_socket(sk,sock);sock-sksk;// 设置默认回调函数sk-sk_state_changesock_def_wakeup;sk-sk_data_readysock_def_readable;sk-sk_write_spacesock_def_write_space;}tcp_v4_init_sock的主要工作staticinttcp_v4_init_sock(structsock*sk){// 面向连接的 socket 扩展结构体structinet_connection_sock*icskinet_csk(sk);// 在 sock_init_data的基础上进一步设置 TCP 特有的字段使 socket 具备 TCP 协议运行所需的状态和参数tcp_init_sock(sk);// 绑定 IPv4 特定的网络层操作icsk-icsk_af_opsipv4_specific;}tcp_init_sock的主要工作voidtcp_init_sock(structsock*sk){structinet_connection_sock*icskinet_csk(sk);structtcp_sock*tptcp_sk(sk);// 初始化乱序队列tp-out_of_order_queueRB_ROOT;// 初始化TCP定时器重传定时器、延迟 ACK 定时器、keepalive 定时器tcp_init_xmit_timers(sk);// 初始化 prequeuetcp_prequeue_init(tp);// 设置 TCP 缓冲区大小sk-sk_sndbufsysctl_tcp_wmem[1];sk-sk_rcvbufsysctl_tcp_rmem[1];}至此一个 SOCK_STREAM 内核对象已经创建好了。3.2 sock_map_fd该函数关键代码如下staticintsock_map_fd(structsocket*sock,intflags){structfile*newfile;// 在当前进程的文件描述符表中找到一个空闲的 slot并返回其索引intfdget_unused_fd_flags(flags);// sock_alloc_file分配一个新的 struct file实例并建立与 socket 的双向关联// 1. 将 sock-file指向新文件// 2. 将 file-private_data指向 socknewfilesock_alloc_file(sock,flags,NULL);}三、总结对socket()方法主要做了以下几件事情分配内核 socket 结构面向用户设置监听 socket 状态为 SS_UNCONNECTED创建并初始化协议对应的 sock面向网络协议栈设置监听 sock 状态为 TCP_CLOSE初始化接收队列、发送队列、乱序队列初始化缓冲区大小绑定文件描述符返回文件描述符四、后续预告本篇是网络系统调用开篇下一篇我们继续拆解bind() 到底干了什么为什么客户端通常不需要 bind内核端口绑定原理五、写在最后以往学习过程中经常只输入不输出这次坚持记录每一个关键知识点、每一次踩坑与复盘不追求速成坚持稳步积累慢慢搭建属于自己的网络、运维、性能调优技术体系能将实际遇到的问题转换成机器语言。