注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4
在网络编程中,我们通过socket()系统调用分配一个fd,后续的操作都通过这个文件描述符fd实现。那内核是怎么通过这个文件描述符fd找到我们需要的socket结构体呢。下面就来看下sockfd_lookup_light()的实现。
static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
struct file *file;
struct socket *sock;
*err = -EBADF;
file = fget_light(fd, fput_needed);//根据fd获取file结构体
if (file) {
sock = sock_from_file(file, err);//根据file结构体获取socket结构体
if (sock)
return sock;
fput_light(file, *fput_needed);
}
return NULL;
}
可见,主要分成两个部分,一个是由fd找到file结构体,然后才是由file结构体获取socket结构体。
先看看是如何从fd找到file结构体的。
struct file *fget_light(unsigned int fd, int *fput_needed)
{
struct file *file;
struct files_struct *files = current->files;//获取当前进程打开的文件列表
*fput_needed = 0;
//如果只有一个进程在使用,那就不需要加锁了,锁比较耗性能
if (atomic_read(&files->count) == 1) {
file = fcheck_files(files, fd);//根据files_struct结构获取file结构体
if (file && (file->f_mode & FMODE_PATH))
file = NULL;
} else {
rcu_read_lock();//多个进程使用,需要加锁保护
file = fcheck_files(files, fd);
if (file) {
if (!(file->f_mode & FMODE_PATH) &&
atomic_long_inc_not_zero(&file->f_count))
*fput_needed = 1;
else
/* Didn't get the reference, someone's freed */
file = NULL;
}
rcu_read_unlock();
}
return file;
}
static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)
{
struct file * file = NULL;
struct fdtable *fdt = files_fdtable(files);//获得文件描述符位图表
if (fd < fdt->max_fds)
//根据句柄fd获取file结构体,fdt->fd可以理解为一个数组,以文件句柄fd为索引
file = rcu_dereference_check_fdtable(files, fdt->fd[fd]);
return file;
}
由此可见,进程结构体task_struct维护了一个files_struct结构体,用于记录当前进程使用的文件情况,这样也便于控制每个进程允许打开的文件个数,但这个就是另外的话题了。files_struct结构体里的fdtable变量里存放了该进程使用的所有文件句柄,并且每个文件句柄关联到了对应的file结构体。因此以fd为索引就能获取file结构体。这个赋值操作是在socket()系统调用做的,通过fd_install()函数完成fd和file结构体的关联。具体参看Linux socket系统调用(一)的2.2.2 fd_install()函数。
获取了file结构体,我们再来看下socket结构体是怎么从file结构体找到的,也就是sock_from_file()函数。
struct socket *sock_from_file(struct file *file, int *err)
{
if (file->f_op == &socket_file_ops)
//在socket()系统调用中赋值的,具体是在sock_alloc_file()函数里赋值
return file->private_data; /* set in sock_map_fd */
*err = -ENOTSOCK;
return NULL;
}
可见,sock_from_file()函数其实就直接返回了其private_data变量。其实这个赋值也是在socket()系统调用里操作的,具体参看Linux socket系统调用(一)的2.2.1 sock_alloc_file()函数。
总的来说,sockfd_lookup_light()函数其实就是socket()函数的反向操作,socket()里为每个结构体设置了关联性,后面的调用通过这些关系就能层层深入,获取想要操作的对象。说起来这里面就涉及了进程、文件系统以及网络这三个方面的知识。
最后,画了张图,让大家看清楚这些结构体的关系。其实也是在分析socket()函数文章里的图。
通过这张图,我们就能从fd开始,找到socket结构体,甚至该socket对应的inode等信息。