系统调用 socket

注:本文分析基于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);//创建一个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,我们可以找到该变量的定义:

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
/*
* 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

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);//为这个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,这个结构体,

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


系统调用 socket
https://flepeng.github.io/002-Linux-41-系统调用-系统调用-socket-sock-alloc-源码分析/
作者
Lepeng
发布于
2024年9月23日
许可协议