• 重定向(dup、dup2、dup3)--Linux


    🚩重定向是什么?

    在上篇博客[文件描述符]中我曾提到了一个有意思的证明:进程在最开始运行的时候,首先打开了三个文件,分别是标准输入流、标准输出流、标准错误输出流。证明的时候我是把标准输出留给关闭了,然后紧接着创建的文件就会占用已关闭的标准输出流,使得本该流向显示器的数据流向了新创建的文件。先不谈底层的原理,就只看表象,就像是使数据流的方向从一个方向,指向了另一个方向,完成了数据流的方向重定向。现在再次理解重定向就好理解得多了:重新锚定方向

    🚩dup系列函数实现重定向

    就如我上面提到的证明过程,虽说最后也实现了重定向的操作,但是这都是我们手动一步一步设计的环节,先关闭再创建,并且是重定向哪个,哪个就要关闭,关闭和创建之间,不能有其他文件的创建,否则就会把关闭的文件给占用掉了,从而导致定向到了错误的地方。

    晕😵~,感觉好麻烦。但是别担心,操作系统给我们提供了函数接口,帮我们在文件管理的层面直接解决这个问题。

    image-20221106210452858

    上图是dup系列的重定向函数,同样的,也都是系统提供给我们的函数,属于直接对内核数据进行修改。

    由于看着实在是太多而且还是英文,这里就由我一步一步从dup开始解析吧。

    🍁dup

    image-20221106230337023

    头文件:unistd.h

    参数:oldfd–旧的文件描述符(意味着最终要指向的文件,用old来描述确实很奇怪,但是没办法,将就着理解叭)

    返回值:在成功的情况下,返回新文件描述符。如果出现错误,则返回-1,并适当设置errno。

    返回的文件描述符重新指向了oldfd指向的文件,这个新的文件描述符是没有被使用的最小的文件描述符。

    ⌨整点代码测试:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define PATH_NAME "log.txt"
    using namespace std;
    int main()
    {
        umask(0);
        int oldfd = open(PATH_NAME, O_CREAT | O_RDWR | O_TRUNC, 0600);
        if (oldfd < 0)
        {
            cerr << strerror(errno) << endl;
            exit(2);
        }
        cout << "oldfd: " << oldfd << endl;
        int newfd = dup(oldfd);
        cout << "newfd: " << newfd << endl;
    
        const char *str = "Hello, Kangkang, this is Michael\n";
        char output[1024];
        write(newfd, str, strlen(str));
        lseek(oldfd, 0, SEEK_SET);
        ssize_t size = read(oldfd, output, sizeof(output) - 1);
        if (size)
        {
            output[size] = '\0';
            cout << output;
        }
        else
        {
            cout << "do nothing" << endl;
        }
        close(oldfd);
        close(newfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    💻:

    image-20221106223158740

    首先以读写的方式打开一个文件log.txt,返回一个文件描述符oldfd,接着调用dup返回一个新的文件描述符,这个文件描述符按道理讲也是指向log.txt的,接着我们以新的文件描述符去从log.txt中读取,事实证明确实可以,dup的功能得到证实。

    🍁dup2

    dup虽然可以完成重定向,但是使用起来也不是那么方便,因为它只能重定向未被使用的最小的文件描述符,对已有的文件不能完成重定向。因此为了解决这个问题,又提供了dup的进阶版–dup2,可以实现指定的文件描述符newfd的重定向。

    image-20221106230452314

    头文件:unistd.h

    参数:

    oldfd–最终指向的文件的 文件描述符

    newfd–需要被重定向的文件描述符

    newfd与oldfd一样的时候,什么都不做,直接返回newfd

    返回值:在成功的情况下,返回新文件描述符。如果出现错误,则返回-1,并适当设置errno。

    这里的newfd就是我们用户指定的需要被重定向的文件描述符。

    ⌨整点代码测试:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define PATH_NAME "log.txt"
    using namespace std;
    int main()
    {
        umask(0);
        int oldfd = open(PATH_NAME, O_CREAT | O_RDWR | O_TRUNC, 0600);
        if (oldfd < 0)
        {
            cerr << strerror(errno) << endl;
            exit(2);
        }
        cout << "oldfd: " << oldfd << endl;
        int newfd = dup2(oldfd, 1);        //使得标准输出流指向log.txt
        if (newfd == -1)
        {
            cerr << strerror(errno) << endl;
            exit(2);
        }
    
        //以下的cout全部都是向log.txt中输出数据
        cout << "newfd: " << newfd << endl; 
    
        const char *str = "Hello, Kangkang, this is Michael\n";
        cout << str;
        
        //使得标准输入流指向log.txt
        newfd = dup2(oldfd,0);
        if (newfd == -1)
        {
            cerr << strerror(errno) << endl;
            exit(2);
        }
        //重新设置文件偏移量,从文件的起始位置开始
        lseek(oldfd,0,SEEK_SET);
        char output[1024];
        cin >> output;  //仅仅从文件log.txt中读取一次数据
        cout << output << endl;  //再次将其放入文件中
        close(oldfd);
        close(newfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    💻:

    image-20221106234417970

    之所以只有一个"newfd:"被读取到了,是因为cin提取流默认读到空格’ ‘或者换行符’\n’都会停止读取。并且我们发现上面的代码我们使用了lseek来改变文件的偏移量,改变了oldfd的也会导致newfd的文件偏移量发生同步变化,这也就是说新的文件描述符不仅仅只指向了oldfd,并且还共享文件偏移量和文件的状态。

    讲真的,一般情况下,我们用到最多的就是指定文件的重定向dup2,毕竟重定向一般都是有需求了才会重定向的叭🤔。

    🍁dup3

    image-20221107105459533

    dup3和dup2功能相似,只是多了一个参数:flags,并且要宏定义_GNU_SOURCE,这样就可以设置flags为库里宏定义好的O_CLOEXEC,那这样做的意义是什么呢?

    🔺首先我们得清楚O_CLOEXEC的作用是干嘛的:

    由于在调用exec系列函数([进程替换]((1条消息) 进程控制–Linux_皮皮蜥的博客-CSDN博客))时,由于进程里的程序被替换,文件描述符就会被释放,但是文件表项却没被释放,有点像内存泄漏,相当于栈上的指针被释放了,但是指针指向的堆上的空间并没有被释放。也就是说,假如我们知道了替换程序前的文件描述符,就可以在替换后的程序中继续对之前打开的文件进行操作,这意味着新的程序会继承被替换的程序的文件表项(文件指针数组)。

    但是我们替换程序的时候一般的需求都是使用新的程序,很少需要用到之前已打开的文件,这就意味着我们需要去关闭那些我们在新的程序中用不到的文件(闲置文件)。更严重的情况是如果不关闭某些文件,由于替换之后文件描述符的丢失,会造成一些文件始终无法关闭的情况,造成不可预测的结果。但是如果已经打开了好多文件,手动去一个一个的关闭肯定很麻烦,于是我们可以在open这些文件的时候在open的flags里也加上O_CLOEXEC使得在进程替换时能够直接识别到文件描述符中有O_CLOEXEC开启的标志,在进程替换之前就把各个相应的文件全部自动关闭,省心省力。

    那么绕了一圈,在dup3中设置flags的用途又是干嘛的呢?

    由于给newfd加了O_CLOEXEC标志,进程替换前会将newfd会被自动关闭,也就是把重定向给关闭了!然后替换的新的程序继承的文件描述符就没有相关的重定向了。

    ⌨整点代码测试一下:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define PATH_NAME "log.txt"
    using namespace std;
    int main()
    {
        umask(0);
        int fd =open(PATH_NAME,O_CREAT | O_RDWR | O_TRUNC,0600);
        if(fd<0)
        {
            cerr << strerror(errno) << endl;
            exit(2);
        }
    
    
        const char *str = "Hello, Kangkang, this is Michael";
    
        //一旦发生进程替换就把对应的重定向newwfd关闭,相当于cout无法被正常使用了(1所对应的文件指针就变成nullptr)。
        dup3(fd,1,O_CLOEXEC);  
        cout<<str<<endl;
        execlp("ls","ls","-al",nullptr);
        cerr<<"something wrong"<<endl;
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    💻:

    在这里插入图片描述

    观察到发生了写入错误,并提示错误的文件描述符,这不就是因为fd:1被关闭了吗?并且由于文件异常关闭,导致log.txt中并没有数据写入。这里我是为了方便观察才使用的标准输出重定向做的例子,实际使用的时候基本都不会出现这种进程异常结束的现象,只会单纯地把重定向取消罢了。

    🚩总结

    重定向的知识并不简单,首先得清楚文件描述符的概念,其次得知道进程替换的具体应用细节。并且还得熟练掌握dup函数族,实际应用还是比较重要的。

    我在整理这篇博客的时候也是又回头看了一下才疏通了知识脉络,希望正在学习的你能够从这篇文章中有所收获吧,有问题欢迎留言或私信,我们一起学习进步🐾

  • 相关阅读:
    xtrabackup恢复数据时提示“This target seems to be not preared yet.”
    Rust逆向学习 (1)
    猿创征文| Unity~DOTween相关使用
    推荐《全职猎人》
    赠书 | 《认知控制》:我们的大脑如何完成任务?
    昇腾CANN 7.0 黑科技:大模型训练性能优化之道
    springboot+vue+Elementui共享单车管理系统
    详解K-Means算法
    深入解析Kotlin类与对象:构造、伴生、单例全面剖析
    python 处理 dataframe的汇总
  • 原文地址:https://blog.csdn.net/qq_63412763/article/details/127732577