I/O重定向详解

2017-01-23  本文已影响194人  eesly_yuan
1、作用

shell中经常会使用到IO重定向,

bash test.sh >/dev/null 2>&1

0、1、2为文件描述符在默认情况下,分别表示进程的标准输入、标准输出、标准错误输出。I/O重定向可以将这些标准的输入输出重定向到其他文件上,例如上面语句作用是,将标准输出和标准错误输出重定向到文件/dev/null这个黑洞文件里面,即不输出

2、常用场景

shell常用的方法如下

exec n>filename   //开启新的描述符n并把目标指向对应的文件filename 
echo >&n  "some thing"
echo >>&n "some thing"
exec n>&-   //关闭描述符n
3、原理

说明io重定向先简单说明内核如何表示一个打开的文件,在一个进程的内核结构中维护着两张表表示该进程已打开的文件(这里简单说明,并不严谨),可以如下所示


简化版内核文件表示

用户层打开一个文件内核将分配一个fd和file结构并填入进程的fd和file表中,fd和file保持对应的关系,io重定向即可以修改这种对应关系,内核提供dup和dup2等系统调用完成这项功能

已dup2为例
2>&1对应调用dup2(1,2)
调用后fd和file的对应关系将变为如下形式,其中file2标识为灰色表面内核可能关闭file2结构


dup(1,2)

了解上述原理后简单说明以下两者的区别,通过实例图应该比较清楚

>/dev/null 2>&1
dup(/dev/null,1)
dup(1,2)

2>&1 >/dev/null
dup(1,2)
dup(/dev/null,1)
>/dev/null 2>&1与2>&1 >/dev/null
4、内核中dup实现

对于上述原理理解后对应IO重定向应该有比较形象的理解了,但是还是存在一些盲点,即被重定向的文件描述符是一个未打开过的文件,还是一个打开的文件,如果已打开,被重定向后,源文件是否会被关闭,带着这些疑问,看看内核源码是怎么实现的吧,下述源码对应内核版本为2.6.18

SYSCALL_DEFINE3(dup3, unsigned int, oldfd, unsigned int, newfd, int, flags)
{
        int err = -EBADF;
        struct file * file, *tofree;
        struct files_struct * files = current->files;
        struct fdtable *fdt;

        spin_lock(&files->file_lock);

        //获取oldfd处的file结构
        file = fcheck(oldfd);
        if (unlikely(!file))
                goto Ebadf;

        //扩展当前进程的files结构,返回值<0,0,1
        err = expand_files(files, newfd);
        if (unlikely(err < 0)) {
                if (err == -EMFILE)
                        goto Ebadf;
                goto out_unlock;
        }

        err = -EBUSY;
        //获取当前进程的fd table
        fdt = files_fdtable(files);

        //保存在newfd这个位置上,之前的file结构
        tofree = fdt->fd[newfd];

        //异常情况检查
        if (!tofree && FD_ISSET(newfd, fdt->open_fds))
                goto out_unlock;
        
        //增加oldfd的file的引用计数
        get_file(file);

        //将newfd处的file指针指向 oldfd的file结构
        rcu_assign_pointer(fdt->fd[newfd], file);

        //将newfd加入openfd列表中
        FD_SET(newfd, fdt->open_fds);
        if (flags & O_CLOEXEC)
                FD_SET(newfd, fdt->close_on_exec);
        else
                FD_CLR(newfd, fdt->close_on_exec);
        spin_unlock(&files->file_lock);

        //如果原先newfd处已经打开过文件,则关闭对应的文件
        if (tofree)
                filp_close(tofree, files);

        return newfd;

Ebadf:
        err = -EBADF;
out_unlock:
        spin_unlock(&files->file_lock);
        return err;
}

SYSCALL_DEFINE2(dup2, unsigned int, oldfd, unsigned int, newfd)
{
        if (unlikely(newfd == oldfd)) { /* corner case */
                //检查oldfd是否在进程已打开的文件列表中
                struct files_struct *files = current->files;
                int retval = oldfd;

                rcu_read_lock();
                if (!fcheck_files(files, oldfd))
                        retval = -EBADF;
                rcu_read_unlock();
                return retval;
        }
        return sys_dup3(oldfd, newfd, 0);
}

通过源码可以比较清楚的发现
dup(m,n)中
1、n可以是一个全新的文件描述符即还未分配给已打开的文件,此时内核将分配该fd,并把该对应关系执行m执行的file结构,并对该file结构引用+1
2、n也可以是一个已经被分配给打开文件的描述符,此时内核将重新将该fd的指向改为m指向的file结构,引用+1,之后对原先n指向的file结构尝试关闭

上一篇下一篇

猜你喜欢

热点阅读