目录
重定向的本质
1.重定向的现象
2.dup2接口
3.追加重定向
4.输出重定向
缓冲区
1.缓冲区是什么
首先写一段代码展现出重定向的现象。
1 #include
2 #include
3 #include
4 #include
5 #include
6
7 int main()
8 {
9 int fd = open("test.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
10
11 if(fd < 0)
12 {
13 perror("open");
14 return 1;
15 }
16
17 fprintf(stdout,"打开成功,我的文件描述符是%d\n",fd);
18
19 close(fd);
20
21 return 0;
22 }
我们都知道文件描述符的规则,不晓得的可以去看一眼我上一篇博客。将最小的没使用的数组下标,分配给文件,所以我们这时的fd(文件描述符)是3,因为我目前只打开了一个文件。
我们这时可以关闭stdin(fd=0)这个文件,则给我们的test.txt分配的fd则是0,这毋庸置疑。
那我们这时关闭stdout(fd=1)这个文件,那么显示器上输出什么呢?还会不会是“打开成功,我的文件描述符是1”。
结果:
为什么什么都没打印?原先我的文件stdout作为标准输出fd是指向显示器的,我把fd这时关了,将它分给了新的文件,那么这时的fprintf会将输出到显示器的内容输出到了文件内,看看我们的分析对不对。
为什么文件里还没有?完蛋讲错了?
我们可以刷新一下缓冲区,神奇的结果出来咯。至于为什么呢,下面讲到缓冲区时会讲到。
这种往一个地方打印输出,最终变成朝指定文件打印的现象,不就是重定向嘛。
我们都知道stdout是个FILE*的指针指向一个struct FILE,这个结构体其中封装了fd(1),我们这时只改变了fd(1)的所属,它变成了另外一个文件的文件描述符。也就是上层只关注文件描述符,只通过文件描述符(0,1,2,3,4,5...)进行文件操作,不管下层指向的文件是什么。所以,我们如果要进行重定向,我们可以通过os中某种接口调整fd指向的文件,就可以完成重定向。
我们来见识一下这个接口。
作用:
让新的fd成为旧的fd的copy,如果必要的话可以先把新的fd关了,但是注意以下情况:如果旧的fd不是有效的文件描述符,这个调用将会失效,新的fd将会被关闭。如果旧的fd是有效的文件描述符,新的fd和旧的fd如果值相同,这个函数将什么也不做,返回新的fd。
我们想让输出到显示器的内容输出到test.txt中,怎么去使用dup2这个函数,究竟谁是newfd谁是oldfd呢?
可以这样理解,既然是拷贝(注意这里的拷贝不单单是两个数来拷贝而是fd对应文件关系的拷贝),那么我想让这俩fd都指向文件,也就是1作为newfd来拷贝oldfd,fd就是oldfd,所以就要这样传参:
dup2(fd,1);
代码如下:
结果:而且我们的文件描述符也为3了,注意我们现在没有close(1)。
这样也能重定向,总之不要管什么newfd和oldfd,重定向的去向放第一个参数,被改变的放第二个参数。
至于追加重定向更简单,在open时,传标记位时传
int fd = open("test.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
再运行时就是追加重定向了。
至于输出重定向呢,我们要从test.txt中读数据,所以dup2()中应该传fd,0
9 int main()
10 {
11 int fd = open("test.txt",O_RDWR);
13 if(fd < 0)
14 {
15 perror("open");
16 return 1;
17 }
18 dup2(fd,0);
19
23 char a[128];
24 while(fgets(a,sizeof(a),stdin) != NULL)
25 {
26 printf("%s",a);
27 }
28
29 fflush(stdout);
31 close(fd);
33 return 0;
34 }
结果如此:
缓冲区的本质就是一段内存,用来存储将要输出的内容。
2.缓冲区出现的原因
①解放使用了缓冲区的进程的时间。这个进程在cpu中进行读写操作时,要输出到对应硬件,需要去等待硬件,这个时间取决于这个硬件是否已经完成上一个任务,这个时间损耗是不可估量的。现在有了缓冲区,可以先将数据存到缓冲器中,进程可以继续运行下去,不用等待,等待缓冲区满了或者刷新了缓冲区就能输入到对应的硬件。
②减少对硬件的操作次数
3.缓冲区在哪儿
先写段代码验证一下缓冲区在哪里
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7
8
9 int main()
10 {
11 printf("我是printf\n");//默认输出到显示器 std->1
12
13 const char * inform = "我是write\n";
14 write(1,inform,strlen(inform));
15 sleep(5);
16 }
那我们把printf中的\n去掉会如何呢?
printf("我是printf");
为什么呢?为什么\n一没有,write先打印呢?printf在其后打印。
难道是write带了\n的缘故? 我们去掉试一下。注意“我是write”打印完之后,等待了5秒之后,“我是printf”才打印出来。说明进程结束了,“我是printf”才打印出来。
我都知道printf是封装了write的c语言函数,write立马打印,而封装了printf的函数没有立马打印,说明什么?这个缓冲区肯定不在wirte中,因为write直接打印无论带没带\n,而只有printf带了\n才能立马打印出来,不带\n刷新不了缓冲区才在程序结束后缓冲区刷新随后打印出“我是printf”。
证明了什么?缓冲区不在内核(系统)层面(write是系统提供的接口),而在printf所在的语言层面。缓冲区只是C语言提供的。
printf fprintf fputs 都是输出到stdout中,而stdout是个FILE*指针指向FILE结构体,这个结构体里就要我们要的缓冲区。
既然缓冲区在struct里是否跟fd没关系,我们在刷新之前将fd关闭,会出现什么情况呢?
结果:
证明同一个结构体中的fd和缓冲区有关联 ,证明了我们上文的一个问题。我们上文在close(fd)之前就要刷新缓冲区,是因为缓冲区与fd有关,关闭了fd系统访问不到对应的缓冲区。