携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情
💦 重定向原理
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
//close(0);
close(1);
int fd1 = open("log1.txt", O_CREAT|O_WRONLY, 0644);
int fd2 = open("log2.txt", O_CREAT|O_WRONLY, 0644);
printf("hello bit!: %d\n", fd1);
printf("hello bit!: %d\n", fd2);
fflush(stdout);
close(fd1);
close(fd2);
return 0;
}
-
要想看到数据也很简单,在 close 之前 fflush 强制刷新即可,但这里要注意 fd1 和 fd2 对应 1 和 3,它们都是磁盘文件,printf 时,因为缓冲区没满,所以都在语言层的缓冲区,但是 fflush 之后,就会一次性的把两次数据都往 fd1 指向的文件中刷新。本来 printf 应该往显示器上输出,但 close 1,open 新文件,导致 1 的指向由显示器转换为磁盘,导致最终往文件里输出,本质重定向改变的是底层文件描述符下标指针的内容,上层是不知道的,这种技术叫做输出重定向。当然在《基础IO 贰》中我们还要对重定向进行补充学习。
💦 补充
#include<stdio.h>
int main()
{
//测试1
printf("%d\n", stdin->_fileno);//0
printf("%d\n", stdout->_fileno);//1
printf("%d\n", stderr->_fileno);//2
FILE* fp = fopen("log.txt", "w");
printf("%d\n", fp->_fileno);//3
//测试2
fprintf(stdout, "hello world\n");
fprintf(stderr, "hello world\n");
return 0;
}
-
从测试 1 中可以看出上下层之间的耦合。
-
测试 2 中,虽然 stdout 和 stderr 对应的设备都是显示器,但是它们是两个独立的文件描述符,且作用却大不相同。这里虽然它们最终都往显示器上输出,但是重定向时,却只能对 stdout 重定向,因为底层改的是 1,没有影响 2,所以 ./ctrl_file > temp.txt 当然是继续往显示器上输出。
✔ 测试用例一:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
close(1);
int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);
if(fd < 0)
{
perror("open");
return 1;
}
fprintf(stdout, "hello world!: %d\n", fd);
close(fd);
return 0;
}
-
close 1 后,1 就不再表示显示器文件,而 open log.txt 后,1 就表示 log.txt 文件,所以 fprintf 并不会往显示器上输出,而是会往 log.txt 里输出,可是 log.txt 中没有内容。通常数据流动过程是:先把语言上的数据写到用户层的缓冲区 ➡ 然后数据通过文件描述符在操作系统内核中,找到自己的 task_struct ➡ 然后通过 task_struct 中的 struct files_struct* files 指针找到 struct files_struct 中 struct files* fd_array[] 以文件描述符为下标的位置 ➡ 然后再通过下标的内容找到要写的 struct_file,并把用户层缓冲区的数据拷贝到内核层缓冲区 ➡ 操作系统再由自己的刷新策略和时机通过磁盘驱动刷新到磁盘设备。注意因为用的是 C,所以这里的用户层缓冲区是 C 提供的,如果是其它语言,那么用的缓冲区就是其它语言提供的。所以之所以操作系统没有由用户层把数据刷新到内核层是因为现在 1 指向的是磁盘文件。显示器是行刷新策略,磁盘是全缓冲策略,这两种策略既可以被用户层采纳,也可以被内核层采纳。
-
为什么语言都要在用户层提供一个缓冲区,printf 直接把数据刷新到内核缓冲区不行吗 ❓
上层只要把数据写到用户层缓冲区中就不用管了,剩下的就由操作系统来完成,所以对用户来讲,就完成了用户层和内核层之间的完全解耦。而用户要自己拷贝数据到内核层,还需要提升权限,效率太低。
所以用户层中存在缓冲区可以让用户和底层之间的差异屏蔽掉,以此来提升效率。同理内核层中存在缓冲区也有着解耦、提高效率的意义。