注:本文分析基于3.10.107内核版本
当我们在进行网络编程时,socket 系统调用是必不可少的一个步骤。socket 系统调用返回的是一个fd,即一个文件描述符。其实它就只是一个 int 类型的数值,我们为什么能像操作文件一样进行读写呢?这就是 VFS 的功劳了,同时协议栈为了适配 VFS 虚拟文件系统实现了 sockfs,最终使得我们可以像操作文件一样操作文件描述符。
而实现 socket 和文件系统绑定的操作,就是在 socket 系统调用中实现的,里面最重要的一步就是调用 sock_alloc()
函数。sock_alloc()
里体现了 linux 一切皆文件(Everything is a file)理念,即使用文件系统来管理 socket,这也是VFS所要达到的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 static struct socket *sock_alloc(void ) { struct inode *inode; struct socket *sock; inode = new _inode_pseudo(sock_mnt ->mnt_sb ) ; if (!inode) return NULL; sock = SOCKET_I(inode ) ; ... return sock; }
我们追踪一下 sock_alloc()
函数的调用路径:
sock_alloc(sock_mnt->mnt_sb) –> new_inode_pseudo() –> alloc_inode() –> sb->s_op->alloc_inode()
那这个 s_op->alloc_inode()
到底是哪来的呢?
根据 new_inode_pseudo()
函数的入参 sock_mnt->mnt_sb
,我们可以找到该变量的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static struct dentry *sockfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { return mount_pseudo(fs_type, "socket:" , &sockfs_ops, &sockfs_dentry_operations, SOCKFS_MAGIC); }static struct vfsmount *sock_mnt __read_mostly;static struct file_system_type sock_fs_type = { .name = "sockfs" , .mount = sockfs_mount, .kill_sb = kill_anon_super, };
我顺带把等会要使用到的结构体和函数一并写上。sock_mnt
变量的初始化是在 sock_init()
函数。
1 2 3 4 5 6 7 8 9 static int __init sock_init(void ) { ... err = register_filesystem(&sock_fs_type ) ; if (err) goto out_fs; sock_mnt = kern_mount(&sock_fs_type ) ; ... }
到这可以知道 sock_fs_type->mount
指向 sockfs_mount()
,请记住这点。下面我们跟踪 kern_mount()
函数的调用路径
kern_mount(&sock_fs_type) –> kern_mount_data() –> vfs_kern_mount() –> mount_fs() –> type->mount()
这里 type->mount()
便是调用 sockfs_mount()
函数。sockfs_mount()
函数调用 mount_pseudo()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct dentry *mount_pseudo(struct file_system_type *fs_type, char *name, const struct super_operations *ops, const struct dentry_operations *dops, unsigned long magic) { ... s = sget(fs_type, NULL, set_anon_super, MS_NOUSER, NULL); ... s->s_op = ops ? ops : &simple_super_operations; ... }
从这个函数可以看出 s_op
就是 ops,也就是 sockfs_ops
,
1 2 3 4 5 static const struct super_operations sockfs_ops = { .alloc_inode = sock_alloc_inode, .destroy_inode = sock_destroy_inode, .statfs = simple_statfs, };
因此刚才 sock_alloc()
最终调用的就是 sock_alloc_inode
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static struct inode *sock_alloc_inode(struct super_block *sb) { struct socket_alloc *ei; struct socket_wq *wq; ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL); if (!ei) return NULL; wq = kmalloc(sizeof(*wq), GFP_KERNEL); if (!wq) { kmem_cache_free(sock_inode_cachep, ei); return NULL; } init_waitqueue_head(&wq->wait); wq->fasync_list = NULL; RCU_INIT_POINTER(ei->socket.wq, wq); ei->socket.state = SS_UNCONNECTED; ei->socket.flags = 0 ; ei->socket.ops = NULL; ei->socket.sk = NULL; ei->socket.file = NULL; return &ei->vfs_inode; }
这里我们看到 struct socket_alloc
,这个结构体,
1 2 3 4 struct socket_alloc { struct socket socket ; struct inode vfs_inode ; };
从这个结构体我们就能看到文件系统相关的结构体了。到这我们就明白内核是怎么把socket 和文件系统相关联的了,这也就是 sockfs 实现的关键。
回过头我们来理一下 sock_alloc()
所做的事。它分配一个 socket_alloc
结构体,将 sockfs
相关属性填充在 socket_alloc
结构体的 vfs_inode
变量中,以限定后续对这个 sock
文件允许的操作。这也为后面通过文件描述符操作 socket 奠定基础。同时 sock_alloc()
最终返回的是 socket_alloc
结构体的 socket
变量,因此用户层面并不感知内核如何将 socket 和文件系统绑定。
Reference