雁过留声 发表于 2024-7-29 15:13:23

【Linux取经路】底子I/O之重定向的实现原理

https://i-blog.csdnimg.cn/blog_migrate/7c811d01abc6f6c98d0002e8b6a9384f.gif


一、再来明白重定向

1.1 输出重定向结果演示

https://i-blog.csdnimg.cn/blog_migrate/581ee6acd6cabc50fa90cd2dd7930e44.gif
分析:ls 指令是体现当前目次下的文件,本质就是将当前目次下所有的文件名以字符串的形式写入到体现器文件。采用输出重定向 >,将原本应该写入体现器文件的内容写入到了 log.txtx 文件中。
1.2 重定向的原理

在解说重定向原理前,我们需要明白文件形貌符的分配规则,即从0下标开始,寻找最小的没有使用的数组位置,它的下标就是新打开文件的文件形貌符。这里没有使用的意思是该下标里面存的是NULL,即没有指向任何一个文件对象。下面通过一段代码来为大家展示重定向的原理。
// mytest.c
int main()
{
    close(1);
    int fd = open(FILE_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
      perror("open");
      return errno;
    }

    const char* str = "Hello Linux!\n";
    int cnt = 5;
    while(cnt--)
    {
      write(1, str, strlen(str));
    }
    return 0;
}
代码分析:上面这段代码就完美的展示了重定向的原理。起首调用 close 系统调用将 1 号下标对应的文件关闭,关闭的意思就是将 1 下标里的内容置为 NULL,原本 1 下标里面存储的内容是体现器文件对象的地点,也就是尺度输出 stdout,紧接着调用 open 打开了一个文件,根据文件形貌符的分配规则,新打开的这个文件的文件形貌符就是 1,即文件形貌符表(file*的数组)1 号下标里面存储的就是新打开的文件对象的地点。接下来调用 write 接口,向 1 号文件形貌符中进行写入,原来 1 号文件形貌符对应的是体现器文件,原本向体现器文件中写入的内容,此时就被写入到新打开的文件中,没有向体现器文件中写入,因此屏幕上就不会出现字符串,至此整个重定向的过程就结束啦。
https://i-blog.csdnimg.cn/blog_migrate/a3f495fa4bcaaf529ec51cba6cba6006.png
总结:重定向的本质是对数组下标里面的内容进行修改。
https://i-blog.csdnimg.cn/blog_migrate/f63729cbf3c23668a0ae41fff73fa289.png
1.3 dup2

上面介绍了重定向的原理,下面介绍一下实现重定向的系统调用 dup2。
#include <unistd.h>
int dup2(int oldfd, int newfd);
dup2 的具体实现并不是向上面代码中那样,先将一个文件形貌符关闭,然后紧接着再打开一个文件。dup2 的使用方法是,用户在调用 dup2 接口前,正常打开一个文件,不用将体现器文件关闭,此时新打开文件的文件形貌符就是 3。接下来调用 dup2 ,将新打开文件的文件形貌符作为 oldfd,将体现器文件的文件形貌符也就是 1,作为 newfd。我们知道,文件形貌符本质上就是数组下标,dup2 函数中执行的工作就是将 oldfd 下标里存储的文件对象地点拷贝到 newfd 下标里面,至此重定向工作就完成了。
https://i-blog.csdnimg.cn/blog_migrate/555cb2e3def24886f76b8b6b8ee2f512.png
小Tips:dup2 的函数形参有一个误导,我们可能会觉得新打开文件的形貌符是 newfd,其实否则,这里的 newfd 是将要被覆盖的文件形貌符,oldfd 是新打开文件的形貌符。
int main()
{
    // close(1);
    int fd = open(FILE_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
      perror("open");
      return errno;
    }
    dup2(fd, 1);
    const char* str = "Hello Linux!\n";
    int cnt = 5;
    while(cnt--)
    {
      write(1, str, strlen(str));
    }
    return 0;
}
https://i-blog.csdnimg.cn/blog_migrate/eeb57a9f74c4ff06bd920895b0b94c3a.png
代码分析:上面就是输出重定向的实现原理,追加重定向只需要把 O_TRUNC 替换成 O_APPEND。
1.4 输入重定向结果演示

https://i-blog.csdnimg.cn/blog_migrate/368e45e41f6e1f6eedc236da64f24bd3.gif
分析:cat 指令原来是从键盘文件中获取输入然后写入体现器文件中,采用输入重定向 < 后,是从 log.txt 文件中获取输入然后写入体现器文件中。
1.5 输入重定向代码实现

// 输入重定向
int main()
{
    int fd = open(FILE_PATH, O_RDONLY);
    if(fd < 0)
    {
      perror("open");
    }
    dup2(fd, 0);

    char str;
    ssize_t ret = read(fd, str, sizeof(str) - 1);
    if(ret > 0)
    {
      str = '\0';
      printf("echo: %s", str);
    }
    return 0;
}
https://i-blog.csdnimg.cn/blog_migrate/c21fbf7f0c14802e4b71437b010097f1.png
小Tips:进程历史打开的文件与进行的各种重定向关系都和未来进行的程序替换无关,程序替换并不影响文件访问。进程打开文件和何种重定向工作,本质上都是进程管理的模块,而程序替换只会把用户空间的代码和数据完全被新程序替换,不会影响到进程管理。
二、再来明白尺度输出和尺度错误

int main()
{
    fprintf(stdout, "Standard output messages\n");
    fprintf(stdout, "Standard output messages\n");
    fprintf(stdout, "Standard output messages\n");

    fprintf(stderr, "Standard error messages\n");
    fprintf(stderr, "Standard error messages\n");
    fprintf(stderr, "Standard error messages\n");
    return 0;
}
https://i-blog.csdnimg.cn/blog_migrate/72fad72ea8f219a7fad95748c2b19fcc.png
代码分析:> 是输出重定向,也就是对尺度输出(1号文件形貌符)进行重定向。尺度错误对应的2号文件形貌符并没有进行重定向,因此尺度错误消息仍旧打印在了屏幕上。
2.1 同时对尺度输出和尺度错误进行重定向

./mytest 1>output.txt 2>error.txt
小Tips:这段代码就是将1号文件形貌符对应的尺度输出文件重定向到 output.txt 文件,将2号文件形貌符对应的尺度错误文件重定向到 error.txt 文件。这样以来屏幕上就不会有任何输出。
https://i-blog.csdnimg.cn/blog_migrate/95984ad667cfb5d39c83d9cb6ebaed63.png
2.2 将尺度输出和尺度错误重定向到同一个文件

./mytest 1>all.txt 2>&1
https://i-blog.csdnimg.cn/blog_migrate/d42448989587ec31cb64c21fb0efc947.png
小Tips:将尺度输出和尺度错误都重定向到 all.txt 文件中。
三、再看一切皆文件

所有操纵计算机的动作,都是通过进程去执行的,所有的访问文件操纵,都是通过进程去实现的,目前所有对文件的操纵都依赖于进程。
https://i-blog.csdnimg.cn/blog_migrate/e4f41edf3dd31ddb36e20baf9212cc03.png
小Tips:所有的外设都被抽象成了文件,每个外设都有自己的读写方法,不同的外设读写方法一定是不同的。但是我们在对文件进行读写操纵的时间,始终调用的都是 read 和 write 方法,这是由于操纵系统为我们提供了一个方法集类型 file_operations,该结构体里面都是函数指针类型,指向外设的各种方法,这就是多态的雏形。所谓的一切皆文件,就是操纵系统帮我们封装了一层文件对象,进程对各种外设的操纵,全都变成了对文件的操纵。
sszie_t read(int fd)
{
        task_struct->files->fd_array->f_op->read();
}
四、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多风趣的文章,接待小搭档们前往点评,您的支持就是春人前进的动力!
https://i-blog.csdnimg.cn/blog_migrate/d2d9215140dceba7c020cc83c254e2cd.gif

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【Linux取经路】底子I/O之重定向的实现原理