本文是sockfs文件系统分析的第三篇文章,本文主要分析socket fd的创建过程,主要介绍系统调用
socket接口的处理流程,主要包括struct socket类型变量的创建、struct sock类型变量的创建以及这些变量与vfs的关联以及与具体的协议处理接口的关联等。因我们之前在第二篇文章《LINUX 套接字文件系统(sockfs)分析之二 相关结构体分析》中已经介绍了socketfs相关的结构体以及它们之间的关联,那针对socket的创建的分析,相对来说就简单许多。
对于网络协议簇而言,包括ax25协议簇、inet4协议簇、inet6协议簇、ipx协议簇等,而每一个协议
簇中又包含多个协议,以inet4协议簇为例,又包含ipv4、tcp、udp、icmp等协议。针对网络协议簇与网络协议之间的关系,sockfs的架构也按此进行了划分。此处以socket系统调用为例(以inet4为例进行介绍):
在socket系统调用中,主要包括网络部分相关的初始化、socket与vfs关联设置这两部分,下面是socket的流程图,其主要调用两个函数完成socket的创建工作:
1.调用sock_create,完成网络相关的socket创建、inode创建、相关协议的处理接口函数指针的设置等;
2.sock_map_fd,申请文件fd以及文件描述符创建,并完成与socket的关联。
socket函数的代码如下,我们接着就以socket_create、sock_map_fd这两个接口进行分析。
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
int retval;
struct socket *sock;
int flags;
/* Check the SOCK_* constants for consistency. */
BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);
flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
type &= SOCK_TYPE_MASK;
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}
socket_create接口分析
socket_create接口是通过调用__sock_create接口进行socket创建与设置的,我们重点关注这个接口。
下面是__sock_create接口的处理流程图,其主要完成如下几个功能:
- 调用sock_alloc接口进行struct socket类型变量的创建、inode节点的创建,主要是通过调用sockfs的超级块的alloc_inode接口sock_alloc_inode接口,该接口创建struct socket_alloc类型的内存变量,该变量中包含了inode成员、socket成员,并完成inode节点的初始化与设置。针对sock_alloc_inode接口,我们之前在第一篇文章《LINUX 套接字文件系统(sockfs)分析之一文件系统注册分析》中已经介绍过,需要了解的查看这篇文章的内容。
- 根据传递的网络协议簇的类型(AF_INET、AF_INET6、AF_NETLINK等),从全局数组net_families中查找对应协议簇的struct net_proto_family类型的指针pf;
- 调用pf->create接口,继续进行socket的设置操作。
以上即为sock_create的处理流程,针对上述第3步的pf->create接口,进行进一步的初始化操作,主要就是进行具体协议的初始化以及该协议簇相关的变量以及接口的初始化操作。为了更好理解协议簇相关的create接口,我们以ipv4的create接口进行详细的分析与说明。
inet的create接口(inet_create)
以上即为sock_create的处理流程,针对上述第3步的pf->create接口,此处以inet协议簇的create接口进行解析,inet的create接口名称为inet_create。该接口的处理流程如下:
1.将socket的状态社社长为SS_UNCONNETED;
2.根据socket类型、协议号,查找对应的struct inet_protosw类型的inet协议变量,若没有查找到,则返回创建失败;若成功,则继续执行如下步骤;
3.创建inet sock变量,并进行相应的初始化,包括sk_family、sk_proto、sk_net等成员的设置,同时inet sock中包含struct sock类型的成员;
4.设置socket变量的ops指针,ops为相应socket(SOCK_STREAM、SOCKET_DGRAM)类型对应的操作处理接口;
5.若sk->sk_proto->init指针不为空,则调用该接口进行具体协议的socket初始化操作(如针对tcp协议而言,则调用tcp_v4_init_sock接口进行相应的初始化)。
针对该接口涉及的结构体变量struct proto、struct proto_ops以及全局变量inetsw,之前的文章中已经介绍,此处不再赘述。
以上即为sock_create接口的处理流程,简而言之,即完成socket的创建、inode的创建、具体协议处理接口的设置等。
sock_map_fd接口分析
该接口主要完成文件描述符的创建以及文件描述符与socket的关联等信息,下面我们详细分析下
sock_map_fd接口的处理流程。
该接口实现的具体功能如下:
1.通过调用get_unused_fd_flags获取一个未使用的fd(关于get_unused_fd_flags,参见《LINUX 文件系统分析sys_open调用之get_unused_fd_flags接口分析》);
2.调用sock_alloc_file接口,进行文件描述符的创建,并与socket进行关联;
3.调用fd_install接口实现文件描述符与文件fd的关联(关于fd_install相关的内容,参见《LINUX VFS分析之do_sys_open接口实现的简要说明》)。
以上即为sock_map_fd接口实现的主要功能。
sock_alloc_file接口分析
该接口实现的具体功能在上面流程图中已经说明,此处再说明一下:
A.调用d_alloc_pseudo创建socket对应的dentry;
B.调用alloc_file,创建文件描述符,并设置文件描述符的操作处理接口指针f_op指针为socket_file_ops;
而socket_file_ops的定义如下,其实现了aio_read、aio_write接口,而在系统调用read、write接口
的分析中(见《LINUX VFS分析之write调用接口的内核实现分析》、《LINUX VFS分析之read调用接口的内核实现分析》)可知,其会通过调用文件描述的f_op->read/f_op->aio_read、f_op->write/f_op->aio_write,实现文件的读写操作。因此针对socket而言,也可以使用read、write接口实现数据的收发操作。
static const struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_sock_ioctl,
#endif
.mmap = sock_mmap,
.open = sock_no_open, /* special open code to disallow open via /proc */
.release = sock_close,
.fasync = sock_fasync,
.sendpage = sock_sendpage,
.splice_write = generic_splice_sendpage,
.splice_read = sock_splice_read,
};
至此我们完成了socket系统调用的实现流程。