因为协议族和协议都是用户在创建socket时指定的,所以相比系统初始化和模块初始化,socket创建的过程要复杂一些。下面我们先来看一下socket的创建过程:
从上图(我画的是一种类似于时序图的图)中可以看出,socket的创建过程是纵向的,因为它要把几个层中的接口串在一起使用。socket初始化最多就涉及到proto接口层。
下面我们就按照调用顺序来分析socket的初始化代码。
1.系统调用接口sys_socket()[net/socket.c:1217]
sys_socket主要有两个动作:创建socket,将该socket映射到一个fd。
我们先来说一下socket映射,因为linux下一切皆文件,那么socket也不例外,那么如果要通过文件操作的系统调用来访问socket,就需要将它映射到一个fd上,sock_map_fd()[net/socket.c:400]正是做这件事的。它所做的主要工作就是生成了该socket的file->dentry->inode结构,值得注意的是它给inode->i_fop赋了socket_file_ops[net/socket.c:126],这就是socket的文件系统接口。
socket的映射说完了,我们再回过头来看sock_create[net/socket.c:1207],它只是调用__sock_create()[net/socket.c:1094],这才是真正的socket创建函数,为什么要这么做呢?因为该函数不仅能通过系统调用的方式来创建socket,还能在内核里被调用来创建socket,所以内核将该函数抽象了出来。
我们来看__sock_create()的实现,这个函数并不复杂,它主要做了两项工作:首先alloc一个socket,值得注意的是该socket是和对应的inode是被分配到同一片连续内存中的,提高了socket的管理效率并减少了复杂度;然后找到该socket的协议族,如果该协议族模块没有被加载就加载它,调用该协议族的create接口。
2.af_family接口inet_create()[net/socket.c:265]
协议族的接口很简单,只是提供了一个create接口,在socket创建的时候进行初始化。因为我们要使用哪个协议族的哪个协议都是在socket创建时才确定的,所以协议族只需要提供一个创建的接口即可,与具体协议的绑定都是在该接口里进行的。
该函数主要初始化了两个结构体:struct sock和struct inet_sock.有意思的是这两个结构体也是指向同一片内存的,前者被包含在后者的内存区里,同样指向这片内存区的还有后面会遇到的TCP协议的inet_connection_sock和tcp_sock,UDP协议的udp_sock.这是用C语言实现的一个继承关系,很有意思,很高效。
我们现在要关注的是该函数对几套函数指针的初始化:1.socket->ops,即上图中所说的proto_ops接口;2.sock->sk_prot,即上图中所说的proto接口;3.sock->sk_data_ready等六个函数接口,这几个接口在上图中并没有,它们是在接收数据时向上发送消息的几个回调函数。
关于结构体sock和inet_sock。sock是内核网络子系统中的一个抽象结构,在所有的协议族中都被使用到,主要包含了一些和输入输出队列,计时器等和内核关系比较紧密的数据成员。而inet_sock,是af_inet协议族中公共的结构体,它的字段都是和协议相关的。这两个结构的具体数据成员在以后我们会做一个总结。
3.proto接口
proto接口的初始化init函数是可选的,在UDP中就没有该接口,下面我们就说一下TCP协议中的初始化函数tcp_v4_init_sock()[net/ipv4/tcp_ipv4.c:1759]。
可以看出该函数也初始化了两个结构:inet_connection_sock和tcp_sock(正如我们之前所说,它们同样位于和sock,inet_sock相邻的内存中)。
从初始化代码中我们看不出什么,要到具体使用的时候才能对这些结构体的字段进行分析,现在只需要知道他们初始化的位置就够了,在以后遇到相关字段时再回过头来查询。