【Linux操作系统】简学深悟启示录:重定向&&缓冲区

1.重定向

1.1 文件描述符分配规则

文件描述符的分配规则是什么?从 0 下标开始,寻找没有用过的数组位置,他的位置就是新文件的文件描述符

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define filename "log.txt"

int main()
{
    close(1);
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    const char* str = "hello Linux!\n";
    int cnt = 5;
    while (cnt)
    {
        write(1, str, strlen(str));
        cnt--;
    }
    close(fd);
    return 0;
}

以上面代码为例,关闭显示器输出文件(即分配符 1 的文件),往显示器文件 1 写入时会发生什么呢?

在这里插入图片描述

看到结果把本来要输出到显示器的内容,都输出到了 log.txt 文件里,这也证实文件符分配的规则

在这里插入图片描述
本来应该输出到显示器上的内容,输出到了文件 log.txt 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有 >>><,那么其本质是什么?

1.2 dup和dup2

其实在实现输入输出重定向时,本质是底层调用用到 dup 或者 dup2 函数

在这里插入图片描述

dupdup2 的唯一区别是,dup 是自动从零开始寻找最小的分配符为 newf,而 dup2 是自己指定一个,这里着重介绍 dup2

请添加图片描述

本质上,是通过将 oldfd 的指针覆盖到 newfd 的指针实现重定向的。如果 oldfd 有效,并且 newfd 已经打开,dup2 会先关闭 newfd,然后再执行复制操作,此时返回 newfd。如果在关闭 newfd 的过程中出现错误,dup2 仍然会执行复制操作,但会返回 -1

🔥值得注意的是:

  • 如果 oldfd 不是有效的文件描述符,那么调用会失败,且 newfd 不会被关闭。
  • 如果 oldfd 是有效的文件描述符,且 newfd 的值与 oldfd 相同,那么 dup2() 不执行任何操作,并返回 newfd

1.3 多文件重定向

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main()
{
    // 向标准输出(stdout)打印 5 次正常消息
    fprintf(stdout, "hello normal message\n");
    fprintf(stdout, "hello normal message\n");
    fprintf(stdout, "hello normal message\n");
    fprintf(stdout, "hello normal message\n");
    fprintf(stdout, "hello normal message\n");

    // 向标准错误(stderr)打印 5 次错误消息
    fprintf(stderr, "hello erro message\n");
    fprintf(stderr, "hello erro message\n");
    fprintf(stderr, "hello erro message\n");
    fprintf(stderr, "hello erro message\n");
    fprintf(stderr, "hello erro message\n");

    return 0;
}

以以上代码为例,补充一些重定向的知识

在这里插入图片描述

运行重定向之后可以发现,代码被写入了 normal.log 这个文件,这两输出的路径各自独立,并不会互相影响,所以只有 stdout 被修改会部分改变

那如果想要把这两部分内容各自输出到不同文件里呢?

在这里插入图片描述

./test > normal.log 2>err.log 其实是 ./test 1>normal.log 2>err.log 的简写,这条命令的意思是将 ./test 一部分通过文件分配符 1 重定向到 normal.log,一部分通过文件分配符 2 重定向到 err.log

🔥值得注意的是: 1normal.log2err.log 之间输出格式不能有空格

那如果想要把两个不同输出路径的内容输出到同一文件呢?

在这里插入图片描述

./test > all.log 2>&1 表示 hello normal message 输出到 all.log,文件分配符 1 里的内容就是 all.log 文件的地址,所以 2>&1 就表示把 all.log 地址复制到文件分配符 2 里面,即重定向,这就完成了把两个不同输出路径的内容输出到同一文件

1.4 再理解"一切皆文件"

在这里插入图片描述

早在初学 Linux 的时候就说过操作系统中一切皆文件这个概念,那么究竟是怎么理解的呢?

我们知道用户要访问外设要创建进程 PCB,然后通过文件分配符表找到对应的 file 结构体,通过内部的指针找到对应的方法实现表,最后访问外设统一的读写接口。可以理解这种统一的实现方法为多态,file 结构体那一层叫做 VFS(虚拟文件系统),往上为用户层面,所以一切皆文件

2.缓冲区

我们将通过三组对比,对缓冲区原理进行详细的解析

2.1 仅有 \n

✏️示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main()
{
    const char *fstr = "hello fwrite\n";
    const char *str = "hello write\n";

    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    fwrite(fstr, strlen(fstr), 1, stdout);
    // close(1);

    write(1, str, strlen(str));

    // fork();
    return 0;
}

💻结果:

在这里插入图片描述

可以看到仅有 \n 的情况下,缓冲区的内容都被正常刷新出来了

2.2 没有 \n 且调用 close()

✏️示例:
在这里插入图片描述

💻结果:
在这里插入图片描述

通过该示例可以发现,在没有 \n 的情况下,普通的 C 接口函数在缓冲区里不会被刷新出来,但是系统接口的就可以,这是为什么呢?

在这里插入图片描述

实际上,用户层和系统层都有一个缓冲区,普通的 C 接口函数刚开始会存储在用户层的缓冲区,由于没有 \n 不能立即刷新,且后续在代码结束前把 fd=1 的文件关掉了,就无法调用 write 写到系统缓冲区上,因为底层函数依赖 fd=1 的文件。而直接用系统函数写入时,直接就在系统缓冲区里了,当程序结束退出时会自动刷新系统缓冲区输出到显示器上,目前我们认为,只要数据刷新到内核了,数据就可以到硬件上

2.3 有 \n 且调用 fork()

✏️示例:

在这里插入图片描述

💻结果:
在这里插入图片描述

通过结果我们可以发现普通的 C 接口函数输出了两次,这是因为 fork 之后,父子进程数据代码共享,也就是共享缓冲区,当父子进程最终刷新缓冲区时(比如程序结束时),它们会从同一块共享的缓冲区中读取到相同的数据,并各自输出一次,导致数据重复

2.4 缓冲区分类

  • 无缓冲: 直接刷新,比如 stderr 输出错误需要立马被看见
  • 行缓冲: 直到碰到 \n 才刷新,比如显示器打印,符合人的阅读习惯
  • 全缓冲: 缓冲区满了才刷新,比如向文件写入,文件通常不需要被查看就能写入

2.5 为什么需要缓冲区?

  • 每次的IO都需要消耗设备的性能,为了减少这类消耗,就设置了缓冲区填满输出的机制,提高效率
  • 有时输出会用到 %d%s 这类占位符,需要缓冲区对这类格式进行处理,统一输出

2.6 FILE类型的本质

在这里插入图片描述

当我们调用普通的 C 接口函数时,返回的是 FILE 类型的指针,本质上 FILE 是一个结构体,里面包含了 fd、缓冲区字段、维护信息等,都存储在自己的缓冲区里


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

评论 32
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值