赞
踩
送给大家一句话:
人真正的名字是:欲望。所以你得知道,消灭恐惧最有效的办法,就是消灭欲望。 – 史铁生 《我与地坛》
上一篇文章我们复习了C文件IO相关操作,了解了linux下的文件系统调用(open write read
),认识了文件描述符fd值,今天我们来学习重定向和缓冲区,这个缓冲区之前遇到过很多次,比如进度条项目的刷新缓冲区操作。然后我们可以来尝试封装一下系统调用,模拟C语言的文件库。
接下来我们来了解重定向!
首先我们来看fd文件描述符的分配规则,我们写一段代码来看:
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<unistd.h> 6 #include<string.h> 7 #include<stdlib.h> 8 9 const char* filename = "log.txt"; 10 11 12 int main() 13 { 14 15 int fd = open("myfile", O_RDONLY); 16 if(fd < 0){ 17 perror("open"); 18 return 1; 19 } 20 printf("fd: %d\n", fd); 21 close(fd); 22 return 0; 23 }
我们运行来看:
这和我们的预期是一样的,我们文件操作那篇文章讲解了fd 的 0 1 2 分别代表了标准输入,标准输出,标准错误。那么在创建的文件描述符很自然的就使用了3! 那么加入我们关闭012中的文件呢,那么新打开的文件描述符会是3吗???
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<unistd.h> 6 #include<string.h> 7 #include<stdlib.h> 8 9 const char* filename = "log.txt"; 10 11 12 int main() 13 { 14 close(0); 15 int fd = open("myfile", O_RDONLY); 16 if(fd < 0){ 17 perror("open"); 18 return 1; 19 } 20 printf("fd: %d\n", fd); 21 close(fd); 22 return 0; 23 }
来看:
我们新创建的文件的文件描述符就成了 0 !
再来试试:
close(2)
-->新创建的文件的文件描述符就成了 2close(1)
-->就什么也打印不出来(标准输出被关闭自然打印不出来)close(2)close(0)
--> 新创建的文件的文件描述符就成了 0这样我们大致可以总结出来一个结论:
文件描述符的分配规则:进程会查自己的文件描述符表,分配最小的并且没有被使用过的 fd
刚才我们看到了文件描述符的分配规则,也发现关闭1 (标准输出)就我们打印出来,我们再来探究一下:如果我们关闭了 标准输出,并打开了一个文件,那么该文件就成为了1 ,来看看会发生什么现象:
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<unistd.h> 6 #include<string.h> 7 #include<stdlib.h> 8 9 const char* filename = "log.txt"; 10 11 12 int main() 13 { 14 15 close(1); 16 17 int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC , 0666); 18 if(fd < 0){ 19 perror("open"); 20 return 1; 21 } 22 printf("fd: %d\n", fd); 23 fprintf(stdout,"fprintf fd :%d\n",fd); 24 25 fflush(stdout); 26 close(fd); 27 return 0 ;
来看效果:
我们发现并没有在显示器打印出来,而是在新文件log.txt中打印出来了!!!
这种技术就叫做 重定向,也就是把本应该打印到显示器的内容打印到了一个其他文件中。
其本质就是在内核中改变文件描述符表特定下标的内容,和上层无关!
可是如果不加入fflush
呢???结果是log.txt文件里也什么都没有?!这就涉及缓冲区的内容了。
首先 一个文件都有一个方法表和内核文件缓冲区。同样在C语言中 (stdin stdout stderr都是struct FILE* 的指针,)文件结构体里面一定封装了fd描述符,而且也封装了语言级的缓冲区。以往的 printf fprintf都是先讲内容写到语言级的缓冲区里在写到文件内核缓冲区了,所以fflush作为一个系统调用,就是刷新文件内核缓冲区,使其输出到文件中!!!
而为什么不加入fflush
呢结果是log.txt文件里也什么都没有呢??? 就是因为内容写入到文件内核缓冲区里还没有刷新就被close关闭了,所以还没刷新就文件被关闭了,还怎么打印到文件中。而且我们不写fflush
不写close
就可以成功打印到文件中!!!
完成重定向的操作肯定不是像我们上面做的那样简单粗暴(又要删除,又要创建新文件),我们有一个系统调用dup2
NAME
dup, dup2, dup3 - duplicate a file descriptor
SYNOPSIS
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);
每次我们使用dup2 就可以实现重定向 ,来看其功能描述。
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary
通过描述可以知道:
dup2( fd , 1 )
就是将fd指向的文件拷贝到1 (标准输出)里。这样通过dup2既可以完成重定向:
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<unistd.h> 6 #include<string.h> 7 #include<stdlib.h> 8 9 const char* filename = "log.txt"; 10 11 12 int main() 13 { 14 15 int fd = open(filename , O_CREAT | O_WRONLY | O_TRUNC); 16 17 dup2(fd,1); 18 19 printf("Hello world!\n"); 20 fprintf(stdout,"Hello world!\n"); 21 close(fd); 22 return 0; 23 }
来看效果:
这样也实现了重定向的功能!!!比简单粗暴的关闭stdout 再打开新文件好多了!!!
我们也可以将O_TRUNC
换成O_APPEND
,这样每次都是追加内容,所以我们的命令也有了对应:
>
相当于 O_TRUNC
覆盖>>
相当于 O_APPEND
追加就这么简单!!!
缓冲区分为:用户级缓冲区 和 内核缓冲区。缓冲区的作用是:解耦和提高使用者效率。
类比生活中,缓冲区就是类似一个超市,我们不需要去工厂进行采购,这样十分麻烦,而直接去超市就解决了问题。也可以比作顺丰快递,我们想要寄东西,只需要交给快递站就可以,我们不需要考虑快递怎么到达目的地!
所以我们操作系统与语言层中,我们的printf 和 fprintf就不需要考虑我们如何将内容写入到文件中,这不是他们需要关心的事情!!!
那为什么会拷贝两次呢???为什么会有两个缓冲区, **因为系统调用是有成本的!**操作系统可能正在执行其他任务,所以为了注重用户体验,就需要缓冲区(也就提高printf fprintf 的效率,因为我们实际上还没有将内容打印到文件,只是打印到了缓冲区,可能调用10次pringtf ,但是只需要刷新一次,是不是刷新IO的效率就高了)
语言层:fflush() , 系统调用:fysnc(int fd)
相当于无缓冲截图内核的刷新策略我们不关心,就针对用户层面来研究。
我们再来与先前的进程控制结合一下,来看:
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<unistd.h> 6 #include<string.h> 7 #include<stdlib.h> 8 9 const char* filename = "log.txt"; 10 11 12 int main() 13 { 14 15 int fd = open(filename , O_CREAT | O_WRONLY | O_TRUNC); 16 17 18 19 printf("Hello printf!\n"); 20 fprintf(stdout,"Hello fprintf!\n"); 21 22 const char *msg = "hello write!\n"; 23 write(1,msg,strlen(msg)); 24 25 fork(); 26 close(fd); 27 return 0; 28 }
我们运行一下来看效果:
啊???这是什么现象???
缓冲区就在struct file 内部!每个文件都有自己的缓冲区。
首先:我们写的程序本质都是对数据进行处理(计算,储存…)那么只要对数据进行处理,就要有几个问题:
所以为什么要有 0 1 2三种标准文件呢?通过上面三个问题我们可以简单回答出来:
为什么要有0 标准输入 与1 标准输出:是为了程序可以更好的处理数据(动态处理查看数据)
0 标准输入 :解决了数据从哪里来
1 标准输出 : 解决了数据去哪里
那 2 标准错误是用来做什么的呢???
#inlcude<stdio.h>
int main()
{
fprintf(stderr ," hello printf stderr\n");
fprintf(stdout ," hello printf stdout\n");
return 0;
}
我们执行编译执行这个程序,会发现标准输出与标准输入都写到了屏幕上:
我们在做一个实验,我们进行一个重定向来试试会发生什么现象:
啊???怎么stderr的信息没有重定向到log.txt
,为什么还是打印到了显示器?
因为重定向操作符>
是标准输出重定向
,只会更改 fd: 1
对应的内容,不会改变fd: 2
中的内容(stderr)。
那么这是为什么呢?因为我们运行程序一般会产生两种信息:正确的和错误的。通过1
与2
就可以储存这两种信息,然后通过一次重定向完成正确信息与错误信息的分离。
./a.out 2> log.txt
这样的操作就可以了(重定向正确信息使用>
或1>
) ./a.out > log.txt 2>&1
就可以,2>&1
表示2指向的文件更改为1
指向的文件。其中的原理如图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。