注:本文分析基于3.10.107内核版本
当我们在进行网络编程时,socket系统调用是必不可少的一个步骤。socket系统调用返回的是一个fd,即一个文件描述符。其实它就只是一个int类型的数值,我们为什么能像操作文件一样进行读写呢?这就是VFS的功劳了,同时协议栈为了适配VFS虚拟文件系统实现了sockfs,最终使得我们可以像操作文件一样操作文件描述符。
而实现socket和文件系统绑定的操作,就是在socket系统调用中实现的,里面最重要的一步就是调用sock_alloc()函数。sock_alloc()里体现了linux一切皆文件(Everything is a file)理念,即使用文件系统来管理socket,这也是VFS所要达到的效果。
static struct socket *sock_alloc(void)
{
struct inode *inode;
struct socket *sock;
inode = new_inode_pseudo(sock_mnt->mnt_sb);//创建一个inode,用于将sock和文件系统相关联
if (!inode)
return NULL;
sock = SOCKET_I(inode);//根据inode获取socket_alloc结构体中socket成员
...
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,我们可以找到该变量的定义:
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()函数。
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()。
/*
* Common helper for pseudo-filesystems (sockfs, pipefs, bdev - stuff that
* will never be mountable)
*/
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,
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。
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);//为这个socket创建一个等待队列
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;//返回inode节点,用于初始化等操作
}
这里我们看到struct socket_alloc,这个结构体,
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和文件系统绑定。