Linux套接字与虚拟文件系统

时间:2022-02-02 11:01:42
引言
   在Unix的世界里,万物皆文件,通过虚拟文件系统VFS,程序可以用标准的Unix系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。对于网络套接字socket也是如此,除了专属的Berkeley Sockets API,还支持一些标准的文件IO系统调用如read(v)、write(v)和close等。那么为什么socket也支持文件IO系统调用呢?在Linux上,这是通过套接口伪文件系统sockfs来实现的,因为sockfs实现了VFS中的4种主要对象:超级块super block、索引节点inode、 目录项对象dentry和文件对象file,当执行文件IO系统调用时,VFS就将请求转发给sockfs,而sockfs就调用特定的协议实现,层次结构如下图:
Linux套接字与虚拟文件系统   本文以linux 2.6.34实现为基础,本篇阐述初始化和 Socket创建两部分的实现,下篇阐述 Socket操作和销毁两部分的实现。

初始化
   在内核引导时初始化网络子系统,进而调用sock_init,该函数主要步骤如下:创建inode缓存,注册和装载sockfs,定义在net/socket.c中。 1Linux套接字与虚拟文件系统static int __init sock_init(void)
2Linux套接字与虚拟文件系统{
3Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
4Linux套接字与虚拟文件系统    init_inodecache();
5Linux套接字与虚拟文件系统    register_filesystem(&sock_fs_type);
6Linux套接字与虚拟文件系统    sock_mnt = kern_mount(&sock_fs_type);
7Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
8Linux套接字与虚拟文件系统}
   
    创建inode缓存
   init_inodecache为socket_alloc对象创建SLAB缓存,名称为sock_inode_cachep,socket_alloc定义在include/net/sock.h中。
1Linux套接字与虚拟文件系统struct socket_alloc {
2Linux套接字与虚拟文件系统    struct socket socket;
3Linux套接字与虚拟文件系统    struct inode vfs_inode;
4Linux套接字与虚拟文件系统}
;
   socket_alloc由socket和inode结构2部分组成,这样就方便了在套接字与inode对象间双向定位。

   注册sockfs
   调用VFS的函数register_filesystem实现注册,sock_fs_type定义在net/socket.c中。
1Linux套接字与虚拟文件系统static struct file_system_type sock_fs_type = {
2Linux套接字与虚拟文件系统    .name =        "sockfs",
3Linux套接字与虚拟文件系统    .get_sb =    sockfs_get_sb,
4Linux套接字与虚拟文件系统    .kill_sb =    kill_anon_super,
5Linux套接字与虚拟文件系统}
;
   sock_fs_type包含了文件系统sockfs的名称、创建和销毁super block的函数,其中sockfs_get_sb实现在net/socket.c中。 1Linux套接字与虚拟文件系统static int sockfs_get_sb(struct file_system_type *fs_type,int flags, const char *dev_name, void *data,struct vfsmount *mnt)
2Linux套接字与虚拟文件系统{
3Linux套接字与虚拟文件系统    return get_sb_pseudo(fs_type, "socket:"&sockfs_ops, SOCKFS_MAGIC, mnt);
4Linux套接字与虚拟文件系统}
   它在kern_mount内被执行,通过调用get_sb_pseudo创建了一个super block(包含一个对应dentry及一个关联inode):操作对象为sockfs_ops,根目录名称为socket:,对应的根索引节点编号为1。
   sockfs_ops定义在net/socket.c中。 1Linux套接字与虚拟文件系统static const struct super_operations sockfs_ops = {
2Linux套接字与虚拟文件系统    .alloc_inode =    sock_alloc_inode,
3Linux套接字与虚拟文件系统    .destroy_inode = sock_destroy_inode,
4Linux套接字与虚拟文件系统    .statfs =    simple_statfs,
5Linux套接字与虚拟文件系统}
;
   sock_alloc_inode用于分配inode对象,将在socket创建过程中被调用;sock_destroy_inode用于释放inode对象,将在socket销毁过程中被调用;simple_statfs用于获取sockfs文件系统的状态信息。
   
    装载sockfs
   由kern_mount函数实现装载一个伪文件系统(当然,它没有装载点),返回一个static vfsmount对象sock_mnt。

   经过以上步骤后,所创建的VFS对象关系如下图:
Linux套接字与虚拟文件系统     对于根目录项,不用进行路径转换,因此dentry的d_op为空(未画出);对于伪文件系统,操作索引对象没有意义,所以inode的i_op为空(未画出)。

Socket创建


   系统调用socket、accept和socketpair是用户空间创建socket的几种方法,其核心调用链如下图:
Linux套接字与虚拟文件系统    从上图可知共同的核心就3个过程:先构造inode,再构造对应的file,最后安装file到当前进程中(即关联映射到一个未用的文件描述符),下面就这3个过程进行详细说明。

    构造inode
   由sock_alloc函数实现,定义在net/socket.c中。  1Linux套接字与虚拟文件系统static struct socket *sock_alloc(void)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    struct inode *inode;
 4Linux套接字与虚拟文件系统    struct socket *sock;
 5Linux套接字与虚拟文件系统
 6Linux套接字与虚拟文件系统    inode = new_inode(sock_mnt->mnt_sb);
 7Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统    
 8Linux套接字与虚拟文件系统    sock = SOCKET_I(inode);
 9Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统        
10Linux套接字与虚拟文件系统    inode->i_mode = S_IFSOCK | S_IRWXUGO;
11Linux套接字与虚拟文件系统    inode->i_uid = current_fsuid();
12Linux套接字与虚拟文件系统    inode->i_gid = current_fsgid();
13Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统    
14Linux套接字与虚拟文件系统    return sock;
15Linux套接字与虚拟文件系统}
   先调用new_inode创建inode对象,再设置它的类型为S_IFSOCK,由此可知inode对应的文件类型为套接字。new_inode是文件系统的一个接口函数,用于创建一个inode对象,定义在fs/inode.c中,它调用了sockfs超级块的操作对象即sockfs_ops的sock_alloc_inode方法,由于sock_alloc_inode实际创建的是socket_alloc复合对象,因此要使用SOCKET_I宏从inode中取出关联的socket对象用于返回。

   构造file
   有了inode对象后,接下来就要构造对应的file对象了,由sock_alloc_file实现,定义在net/socket.c中。  1Linux套接字与虚拟文件系统static int sock_alloc_file(struct socket *sock, struct file **f, int flags)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    struct qstr name = { .name = "" };
 4Linux套接字与虚拟文件系统    struct path path;
 5Linux套接字与虚拟文件系统    struct file *file;
 6Linux套接字与虚拟文件系统    int fd;
 7Linux套接字与虚拟文件系统
 8Linux套接字与虚拟文件系统    fd = get_unused_fd_flags(flags);
 9Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统    
10Linux套接字与虚拟文件系统    path.dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
11Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统    
12Linux套接字与虚拟文件系统    path.mnt = mntget(sock_mnt);
13Linux套接字与虚拟文件系统
14Linux套接字与虚拟文件系统    path.dentry->d_op = &sockfs_dentry_operations;
15Linux套接字与虚拟文件系统    d_instantiate(path.dentry, SOCK_INODE(sock));
16Linux套接字与虚拟文件系统    SOCK_INODE(sock)->i_fop = &socket_file_ops;
17Linux套接字与虚拟文件系统
18Linux套接字与虚拟文件系统    file = alloc_file(&path, FMODE_READ | FMODE_WRITE, &socket_file_ops);
19Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
20Linux套接字与虚拟文件系统    sock->file = file;
21Linux套接字与虚拟文件系统    file->f_flags = O_RDWR | (flags & O_NONBLOCK);
22Linux套接字与虚拟文件系统    file->f_pos = 0;
23Linux套接字与虚拟文件系统    file->private_data = sock;
24Linux套接字与虚拟文件系统
25Linux套接字与虚拟文件系统    *= file;
26Linux套接字与虚拟文件系统    return fd;
27Linux套接字与虚拟文件系统}
   sock为 上一过程 返回的套接字对象,该函数主要做了以下几件事:
    1 )得到空闲的文件描述符fd,实际上就是fd数组的索引,准备作为返回值。
    2 )先初始化路径path:其目录项的父目录项为超级块对应的根目录,名称为空,操作对象为sockfs_dentry_operations,对应的索引节点对象为sock套接字关联的索引节点对象,即SOCK_INODE(sock);装载点为sock_mnt。  
   sockfs_dentry_operations定义在net/socket.c中。 
1Linux套接字与虚拟文件系统static const struct dentry_operations sockfs_dentry_operations = {
2Linux套接字与虚拟文件系统    .d_dname  = sockfs_dname,
3Linux套接字与虚拟文件系统}
;
   sockfs_dname会被d_path调用,用于计算socket对象的目录项名称。
   3 )设置索引节点的文件操作对象为socket_file_ops,定义在net/socket.c中。
1Linux套接字与虚拟文件系统static const struct file_operations socket_file_ops = {
2Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
3Linux套接字与虚拟文件系统    .aio_read =    sock_aio_read,
4Linux套接字与虚拟文件系统    .aio_write =    sock_aio_write,
5Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
6Linux套接字与虚拟文件系统    .open =        sock_no_open,    /* special open code to disallow open via /proc */
7Linux套接字与虚拟文件系统    .release =    sock_close,
8Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
9Linux套接字与虚拟文件系统}
;
    4 )调用alloc_file,以path和socket_file_ops为输入参数,这样返回得到的file便与sock的inode关联上了,并且操作对象为socket_file_ops,最后设置到输出参数f中。
    5 )建立file与socket的一一映射关系。
   
    安装file
   由fd_install实现,定义在fs/open.c中。
 1Linux套接字与虚拟文件系统void fd_install(unsigned int fd, struct file *file)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    struct files_struct *files = current->files;
 4Linux套接字与虚拟文件系统    struct fdtable *fdt;
 5Linux套接字与虚拟文件系统    spin_lock(&files->file_lock);
 6Linux套接字与虚拟文件系统    fdt = files_fdtable(files);
 7Linux套接字与虚拟文件系统    BUG_ON(fdt->fd[fd] != NULL);
 8Linux套接字与虚拟文件系统    rcu_assign_pointer(fdt->fd[fd], file);
 9Linux套接字与虚拟文件系统    spin_unlock(&files->file_lock);
10Linux套接字与虚拟文件系统}
   fd和file分别为上一过程返回的空闲文件描述符和文件对象,使RCU技术来设置file到当前进程的fd数组中。
 
   经过以上过程后,所创建的VFS对象关系图如下
Linux套接字与虚拟文件系统

   fd为file*数组的索引而不是成员字段;vfsmount与初始化之VFS对象关系图中的vfsmount是同一个对象,即sock_mnt;对于伪文件系统,操作索引对象没有意义,所以inode的i_op为空(未画出)。

Socket操作


   系统调用read(v)、write(v)是用户空间读写socket的一种方法,为了弄清楚它们是怎么通过VFS将请求转发到特定协议的实现,下面以read为例(write同理),并假定文件描述符对应的是IPv4 TCP类型的socket,来跟踪它的执行流程。首先来看下sys_read的代码,定义在fs/read_write.c中。  1Linux套接字与虚拟文件系统SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    struct file *file;
 4Linux套接字与虚拟文件系统    ssize_t ret = -EBADF;
 5Linux套接字与虚拟文件系统    int fput_needed;
 6Linux套接字与虚拟文件系统
 7Linux套接字与虚拟文件系统    file = fget_light(fd, &fput_needed);
 8Linux套接字与虚拟文件系统    if (file) {
 9Linux套接字与虚拟文件系统        loff_t pos = file_pos_read(file);
10Linux套接字与虚拟文件系统        ret = vfs_read(file, buf, count, &pos);
11Linux套接字与虚拟文件系统        Linux套接字与虚拟文件系统
12Linux套接字与虚拟文件系统    }

13Linux套接字与虚拟文件系统
14Linux套接字与虚拟文件系统    return ret;
15Linux套接字与虚拟文件系统}
   先调用fget_light得到fd对应的file,再调用vfs_read。接着跟踪vfs_read的代码,定义在fs/read_write.c中。
 1Linux套接字与虚拟文件系统ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    ssize_t ret;
 4Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
 5Linux套接字与虚拟文件系统    ret = rw_verify_area(READ, file, pos, count);
 6Linux套接字与虚拟文件系统    if (ret >= 0{
 7Linux套接字与虚拟文件系统        count = ret;
 8Linux套接字与虚拟文件系统        if (file->f_op->read)
 9Linux套接字与虚拟文件系统            ret = file->f_op->read(file, buf, count, pos);
10Linux套接字与虚拟文件系统        else
11Linux套接字与虚拟文件系统            ret = do_sync_read(file, buf, count, pos);
12Linux套接字与虚拟文件系统        Linux套接字与虚拟文件系统
13Linux套接字与虚拟文件系统    }

14Linux套接字与虚拟文件系统
15Linux套接字与虚拟文件系统    return ret;
16Linux套接字与虚拟文件系统}
   在 上篇Socket创建一节 已知,因为sockfs_file_ops没有定义read(即read指针为空),所以这儿实际调用了do_sync_read,继续跟踪它的代码,定义在fs/read_write.c中。
 1Linux套接字与虚拟文件系统ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    struct iovec iov = { .iov_base = buf, .iov_len = len };
 4Linux套接字与虚拟文件系统    struct kiocb kiocb;
 5Linux套接字与虚拟文件系统    ssize_t ret;
 6Linux套接字与虚拟文件系统
 7Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
 8Linux套接字与虚拟文件系统    for (;;) {
 9Linux套接字与虚拟文件系统        ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
10Linux套接字与虚拟文件系统        if (ret != -EIOCBRETRY)
11Linux套接字与虚拟文件系统            break;
12Linux套接字与虚拟文件系统        wait_on_retry_sync_kiocb(&kiocb);
13Linux套接字与虚拟文件系统    }

14Linux套接字与虚拟文件系统
15Linux套接字与虚拟文件系统    if (-EIOCBQUEUED == ret)
16Linux套接字与虚拟文件系统        ret = wait_on_sync_kiocb(&kiocb);
17Linux套接字与虚拟文件系统    *ppos = kiocb.ki_pos;
18Linux套接字与虚拟文件系统    return ret;
19Linux套接字与虚拟文件系统}
   显而易见,这儿调用到了f_op->aio_read,使用异步读来实现同步读,若异步读没有完成,则调用wait_on_sync_kiocb等待。由 上篇Socket创建一节 可知sockfs_file_ops的aio_read设为sock_aio_read函数,定义在net/socket.c中,至此sys_read的实现完成了前一半(操作对象是file)而进入后一半(操作对象是socket),即socket层的实现。
   在socket层跟踪sock_aio_read,可以得到最后调用的是sock->ops->recvmsg,由于socket类型为IPv4 TCP,因此sock->ops在socket创建过程中被设为inet_stream_ops,定义在net/ipv4/af_inet.c中。 1Linux套接字与虚拟文件系统const struct proto_ops inet_stream_ops = {
2Linux套接字与虚拟文件系统    .family    =  PF_INET,
3Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
4Linux套接字与虚拟文件系统    .release   =  inet_release,
5Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
6Linux套接字与虚拟文件系统    .recvmsg  =  sock_common_recvmsg,
7Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
8Linux套接字与虚拟文件系统}
;
   从上可知recvmsg设为sock_common_recvmsg,跟踪它的代码,定义在net/core/sock.c中。    1Linux套接字与虚拟文件系统int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size, int flags)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    struct sock *sk = sock->sk;
 4Linux套接字与虚拟文件系统    int addr_len = 0;
 5Linux套接字与虚拟文件系统    int err;
 6Linux套接字与虚拟文件系统
 7Linux套接字与虚拟文件系统    err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,flags & ~MSG_DONTWAIT, &addr_len);
 8Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
 9Linux套接字与虚拟文件系统    return err;
10Linux套接字与虚拟文件系统}
   struct sock表示套接字的网络接口层,它的成员sk_prot表示网络协议块,在这它对应tcp_prot结构,定义在net/ipv4/tcp_ipv4.c中,由此可见进入到特定协议的实现。 1Linux套接字与虚拟文件系统struct proto tcp_prot = {
2Linux套接字与虚拟文件系统    .name            = "TCP",
3Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
4Linux套接字与虚拟文件系统    .close   =  tcp_close,
5Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
6Linux套接字与虚拟文件系统    .recvmsg  =  tcp_recvmsg,
7Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
8Linux套接字与虚拟文件系统}
;
   recvmsg设为tcp_recvmsg,至此跟踪结束。对于sys_readv的实现,调用的是vfs_readv,后面的过程和sys_read相同,总结核心调用链如下图:
Linux套接字与虚拟文件系统    由此可知,sockfs_file_ops只须实现aio_read,就能支持普通和聚集两种方式的读操作。为了对比,这里也给出Berkeley Sockets API中recv的核心调用链如下图:
Linux套接字与虚拟文件系统    显而易见,recv内部实现调用的是sys_recvfrom,它没有经过VFS,而是先调用sock_lookup_light从fd得到socket,再调用sock_recvmsg,后面的流程和recv就是一样的了。

Socket销毁
   Socket操作既可以调用文件IO,也可以调用Berkeley Sockets API。但销毁不同,系统调用close是用户空间销毁socket的唯一方法,它定义在fs/open.c中。
 1Linux套接字与虚拟文件系统SYSCALL_DEFINE1(close, unsigned int, fd)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    struct file * filp;
 4Linux套接字与虚拟文件系统    struct files_struct *files = current->files;
 5Linux套接字与虚拟文件系统    struct fdtable *fdt;
 6Linux套接字与虚拟文件系统    int retval;
 7Linux套接字与虚拟文件系统
 8Linux套接字与虚拟文件系统    spin_lock(&files->file_lock);
 9Linux套接字与虚拟文件系统    fdt = files_fdtable(files);
10Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
11Linux套接字与虚拟文件系统    filp = fdt->fd[fd];
12Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
13Linux套接字与虚拟文件系统    rcu_assign_pointer(fdt->fd[fd], NULL);
14Linux套接字与虚拟文件系统    FD_CLR(fd, fdt->close_on_exec);
15Linux套接字与虚拟文件系统    __put_unused_fd(files, fd);
16Linux套接字与虚拟文件系统    spin_unlock(&files->file_lock);
17Linux套接字与虚拟文件系统    retval = filp_close(filp, files);
18Linux套接字与虚拟文件系统    Linux套接字与虚拟文件系统
19Linux套接字与虚拟文件系统}
   首先从fd获取对应的file,若file非空则设置进程描述符数组对应项为空,并将fd从exec时关闭的文件描述符链表和打开的文件描述符链表中移除;最后调用filp_close,跟踪它的代码,定义在fs/open.c中。
 1Linux套接字与虚拟文件系统int filp_close(struct file *filp, fl_owner_t id)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    int retval = 0;
 4Linux套接字与虚拟文件系统
 5Linux套接字与虚拟文件系统    if (!file_count(filp)) {
 6Linux套接字与虚拟文件系统        printk(KERN_ERR "VFS: Close: file count is 0\n");
 7Linux套接字与虚拟文件系统        return 0;
 8Linux套接字与虚拟文件系统    }

 9Linux套接字与虚拟文件系统
10Linux套接字与虚拟文件系统    if (filp->f_op && filp->f_op->flush)
11Linux套接字与虚拟文件系统        retval = filp->f_op->flush(filp, id);
12Linux套接字与虚拟文件系统
13Linux套接字与虚拟文件系统    dnotify_flush(filp, id);
14Linux套接字与虚拟文件系统    locks_remove_posix(filp, id);
15Linux套接字与虚拟文件系统    fput(filp);
16Linux套接字与虚拟文件系统    return retval;
17Linux套接字与虚拟文件系统}
   首先判断file的引用计数,若为0则打印一个错误日志(说明这是一个bug,因为file已经被释放)并返回;由于sockfs_file_ops中的flush没有定义即为空,因此跳过;dnotify_flush用于释放任何相关的dnotify(一种文件监控机制)资源,locks_remove_posix用于清除文件锁相关的资源,由于socket对应的inode没有使用文件锁,因此它什么也没做。最后调用fput来释放file,定义在fs/file_table.c中。
1Linux套接字与虚拟文件系统void fput(struct file *file)
2Linux套接字与虚拟文件系统{
3Linux套接字与虚拟文件系统    if (atomic_long_dec_and_test(&file->f_count))
4Linux套接字与虚拟文件系统        __fput(file);
5Linux套接字与虚拟文件系统}
   先递减引用计数,若为0则调用__fput释放file,它会调用到sockfs_file_ops定义的release函数即sock_close,它是sock_release的包装函数,sock_release定义在net/socket.c中。
 1Linux套接字与虚拟文件系统void sock_release(struct socket *sock)
 2Linux套接字与虚拟文件系统{
 3Linux套接字与虚拟文件系统    if (sock->ops) {
 4Linux套接字与虚拟文件系统        struct module *owner = sock->ops->owner;
 5Linux套接字与虚拟文件系统
 6Linux套接字与虚拟文件系统        sock->ops->release(sock);
 7Linux套接字与虚拟文件系统        sock->ops = NULL;
 8Linux套接字与虚拟文件系统        module_put(owner);
 9Linux套接字与虚拟文件系统    }

10Linux套接字与虚拟文件系统    if (sock->fasync_list)
11Linux套接字与虚拟文件系统        printk(KERN_ERR "sock_release: fasync list not empty!\n");
12Linux套接字与虚拟文件系统
13Linux套接字与虚拟文件系统    percpu_sub(sockets_in_use, 1);
14Linux套接字与虚拟文件系统    if (!sock->file) {
15Linux套接字与虚拟文件系统        iput(SOCK_INODE(sock));
16Linux套接字与虚拟文件系统        return;
17Linux套接字与虚拟文件系统    }

18Linux套接字与虚拟文件系统    sock->file = NULL;
19Linux套接字与虚拟文件系统}
          
   先调用ops->release即特定协议的释放操作,对于IPv4 TCP,就是inet_stream_ops中定义的inet_release函数,它又会调用到tcp_prot中定义的close即tcp_close;对于关联inode的释放,这里要分2种情况:如果sock->file为空,就调用iput释放,否则返回到__fput中,会调用dput释放dentry,而dentry又关联着inode,最终调用iput释放inode;当最后一个iput被调用时,sockfs_ops中定义的sock_destroy_inode就会被调用,归还由sock_alloc_inode分配的struct socket_alloc对象到SALB缓存中。总结核心调用链如下图:
Linux套接字与虚拟文件系统    
   在 上篇初始化一节 ,我们已知sockfs文件系统被装载,然而实际上没有卸载它的方式。由于TCP/IP协议栈和sockfs被静态编译到内核中,而不是一个内核模块。因此没必要提供一个卸载函数,sockfs伪文件系统在启动到关闭期间,总是被装载着的。