前言
学习Linux网络编程,接触的第一个函数是socket(),在过往使用中,并没有真正搞懂操作系统做了什么。本文旨在记录学习Linux网络子系统源码的过程与思考,不死扣代码细节,仅聚焦于核心逻辑和数据流,希望能和同样对Linux网络感兴趣的朋友一起探讨学习。
一、一行代码,看懂入口
如无特殊说明,代码以TCP为例
int fd = socket(int domain, int type, int protocol);
socket 的类型由三个参数决定:
- SOCK_STREAM,提供有序、可靠、双向、基于连接的字节流,如TCP
- SOCK_DGRAM,支持数据报(无连接、不可靠的、具有固定最大长度的消息),如UDP
- protocol:具体传输协议,通常填0让系统选择
返回值:
二、重点:调用 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()方法主要做了以下几件事情:
- 设置监听 socket 状态为 SS_UNCONNECTED
四、后续预告
本篇是网络系统调用开篇,下一篇我们继续拆解: bind() 到底干了什么?为什么客户端通常不需要 bind?内核端口绑定原理?
五、写在最后
以往学习过程中经常只输入不输出,这次坚持记录每一个关键知识点、每一次踩坑与复盘,不追求速成,坚持稳步积累,慢慢搭建属于自己的网络、运维、性能调优技术体系,能将实际遇到的问题转换成机器语言。