文章目录
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 函数

dup 和 dup2 的唯一区别是,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
🔥值得注意的是: 1 和 normal.log、2 和 err.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、缓冲区字段、维护信息等,都存储在自己的缓冲区里
希望读者们多多三连支持
小编会继续更新
你们的鼓励就是我前进的动力!

2324





