1.9、文件描述符的复制
文件描述符的复制文件描述符的复制的APILinux提供了三个复制文件描述符的系统调用,分别为:int dup(int oldfd);int dup2(int oldfd, int newfd);int dup3(int oldfd, int newfd, int flags);其中:dup会使用一个最小的未用文件描述符作为复制后的文件描述符。dup2是使用用户指定的文件描述符newfd来复制old
文件描述符的复制
文件描述符的复制的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;
}
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);
}
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);
}
更多推荐
所有评论(0)