美文网首页
I/O重定向详解

I/O重定向详解

作者: eesly_yuan | 来源:发表于2017-01-23 01:15 被阅读194次
    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结构尝试关闭

    相关文章

      网友评论

          本文标题:I/O重定向详解

          本文链接:https://www.haomeiwen.com/subject/ykiobttx.html