注:本文分析基于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等信息。

Logo

更多推荐