文件描述符的复制

文件描述符的复制的API

Linux提供了三个复制文件描述符的系统调用,分别为:

int dup(int oldfd);

int dup2(int oldfd, int newfd);

int dup3(int oldfd, int newfd, int flags);

其中:

dup会使用一个最小的未用文件描述符作为复制后的文件描述符。

dup2是使用用户指定的文件描述符newfd来复制oldfd的。如果newfd已经是打开的文件描述符,Linux会先关闭newfd,然后再复制oldfd。

若oldfd等于newfd,则dup2返回newfd,而不关闭它。

dup3,只有定义了feature宏“_GNU_SOURCE”才可以使用,它比dup2多了一个参数,可以指定标志——不过目前仅仅支持O_CLOEXEC标志,可在newfd上设置O_CLOEXEC标志。定义dup3的原因与open类似,可以在进行dup操作的同时原子地将fd设置为O_CLOEXEC,从而避免将文件内容暴露给子进程。

这些函数返回的新的文件描述符与参数oldfd,共享一个文件表项

为什么会有dup、dup2、dup3这种像兄弟一样的系统调用呢?

这是因为随着软件工程的日益复杂,已有的系统调用已经无法满足需求,或者存在安全隐患,这时,就需要内核针对已有问题推出新的接口。

话说在很久以前,程序员在写daemon服务程序时,基本上都有这样的流程:

首先关闭标准输出stdout、标准出错stderr,然后进行dup操作,将stdout或stderr重定向。但是在多线程程序成为主流以后,由于close和dup操作不是原子的,这就造成了在某些情况下,重定向会失败。

因此就引入了dup2将close和dup合为一个系统调用,以保证原子性,然而这依然有问题。大家可以回顾1.2.2节中对O_CLOEXEC的介绍。在多线程中进行fork操作时,dup2同样会有让相同的文件描述符暴露的风险,dup3也就随之诞生了。这三个系统调用看起来有些冗余重复,但实际上它们也是软件工程发展的结果。从这个dup的发展过程来看,我们也可以领会到编写健壮代码的不易。正如前文所述,对于一个现代接口,一般都会有一个flag标志参数,这样既可以保证兼容性,还可以通过引用新的标志来改善或纠正接口的行为。

打开文件的选项:

O_CLOEXEC在打开文件的时候,就为文件描述符设置FD_CLOEXEC标志。这是一个新的选项,用于解决在多线程下fork与用fcntl设置FD_CLOEXEC的竞争问题。某些应用使用fork来执行第三方的业务,为了避免泄露已打开文件的内容,那些文件会设置FD_CLOEXEC标志。但是fork与fcntl是两次调用,在多线程下,可能会在fcntl调用前,就已经fork出子进程了,从而导致该文件句柄暴露给子进程。关于O_CLOEXEC的用途,将会在第4章详细讲解

下面先看dup的实现,如下所示:

SYSCALL_DEFINE1(dup, unsigned int, fildes) 
{
    int ret = -EBADF;
    /* 必须先得到文件管理结构file,同时也是对描述符 fildes的检查 */
    struct file *file = fget_raw(fildes); 
    if (file) { 
        /* 得到一个未使用的文件描述符 */
        ret = get_unused_fd();
        if (ret >= 0)
        {
             /* 将文件描述符与 file指针关联起来 */
             fd_install(ret, file);
        }
        else
            fput(file); 
    }
    return ret;
}
然后,再看看 fd_install 的实现,代码如下所示:
void fd_install(unsigned int fd, struct file *file)
{
    struct files_struct *files = current->files;
    struct fdtable *fdt;
    /* 对文件表进行保护 */ 
    spin_lock(&files->file_lock); 
    /* 得到文件表 */
    fdt = files_fdtable(files);
    BUG_ON(fdt->fd[fd] != NULL);
    /* 让文件表中 fd对应的指针等于该文件关联结构 file */ 
    rcu_assign_pointer(fdt->fd[fd], file);
    spin_unlock(&files->file_lock); 
}
dup 中调用 get_unused_fd ,只是得到一个未用的文件描述符,那么如何实现在 dup 接口中使用最小的未用文件描述符呢?
Linux 总是尝试给用户最小的未用文件描述符,所以 get_unused_fd得到的文件描述符始终是最小的可用文件描述符。
fd_install中,fdfile的关联是利用fd来作为指针数组的索引的,从而让对应的指针指向file 。对于dup 来说,这意味着数组中两个指针都指向了同一个 file 。而 file 是进程中真正的管理文件的结构,文件偏移等信息都是保存在file 中的。这就意味着,当使用 oldfd 进行读写操作时,无论是 oldfd 还是 newfd 的文件偏移都会发生变化。
再来看一下 dup2 的实现,如下所示:
SYSCALL_DEFINE2(dup2, unsigned int, oldfd, unsigned int, newfd) 
{ 
    /* 如果 oldfd与 newfd相等,这是一种特殊的情况 */ 
    if (unlikely(newfd == oldfd)) 
    { 
        /* corner case */ 
        struct files_struct *files = current->files;
        int retval = oldfd; 
        /*检查 oldfd的合法性,如果是合法的 fd,则直接返回 oldfd的值;如果是不合法的,则返回 EBADF */
        rcu_read_lock(); 
        if (!fcheck_files(files, oldfd)) 
            retval = -EBADF;
        rcu_read_unlock();
        return retval;
     }
     /* 如果 oldfd与 newfd不同,则利用sys_dup3来实现 dup2 */ 
     return sys_dup3(oldfd, newfd, 0); 
}

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐