基础IO 五

73 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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 直接把数据刷新到内核缓冲区不行吗 ❓

    上层只要把数据写到用户层缓冲区中就不用管了,剩下的就由操作系统来完成,所以对用户来讲,就完成了用户层和内核层之间的完全解耦。而用户要自己拷贝数据到内核层,还需要提升权限,效率太低。

    所以用户层中存在缓冲区可以让用户和底层之间的差异屏蔽掉,以此来提升效率。同理内核层中存在缓冲区也有着解耦、提高效率的意义。