初识Linux · 重定向和缓冲区

打印 上一主题 下一主题

主题 947|帖子 947|积分 2841

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录
媒介:
准备知识
缓冲区 + 重定向

媒介:

其实有了文件2的准备知识,我们已经初步相识了文件描述符fd是什么,底层是如何运作的了,那么本文,我们通过文件描述符对重定向和缓冲区有一个更深层次的明白,对于重定向,我们最开始只是知道系统将我们本该输出到A的内容输出到了B,但是我们并不知道是如何运作的,以是本文的第一个目的:明白重定向是如何实现的?那么对于第二个目的,就是加深对缓冲区的明白
以上是本文的概念,那么进入主题吧。

准备知识

我们介绍重定向从一个函数开始:

我们从close函数开始,close函数的参数是fd,也就是文件描述符,结合Linux中万物皆文件的头脑,如果我们我们往显示器这个文件输出东西,把该文件关了是不是就打印不出来了?
加上默认打开了三个流,stdin stdout stderr,分别对应的就是0 1 2,我们一个一个尝试:
  1. int main()
  2. {
  3.     close(0);
  4.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);
  5.     printf("fd:%d\n", fd);   
  6.     return 0;
  7. }
复制代码
当我们关闭了0这个,结果还是可以正常打印:

但是不同的是为什么打印出来的是0?
我们再把2关了试试:
  1. int main()
  2. {
  3.     // close(0);
  4.     close(2);
  5.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);
  6.     printf("fd:%d\n", fd);   
  7.     return 0;
  8. }
复制代码

此时打印出来的居然是2?我们试试1:
  1. int main()
  2. {
  3.     // close(0);
  4.     // close(2);
  5.     close(1);
  6.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);
  7.     printf("fd:%d\n", fd);   
  8.     return 0;
  9. }
复制代码

相信现象我们也能猜出来,因为0 1 2分别对应的是stdin stdout stderr,我们将默认的输出流关了,以是显示器上没有东西。
而,文件描述符fd,对于我们新创建的文件来说,文件描述符既然是我们close掉的?
以是,这里可以的出来一个结论是,文件描述符的匹配规则实际上是从files_struct里面找没有利用的最小的文件描述符分配给新开的文件
我们既然利用的printf函数,没有利用文件函数,我们不妨试试文件函数fprintf:
  1. int main()
  2. {
  3.     // close(0);
  4.     // close(2);
  5.     close(1);
  6.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);
  7.     printf("printf,fd:%d\n", fd);   
  8.     fprintf(stdout,"fprintf,fd:%d\n",fd);
  9.     return 0;
  10. }
复制代码
现象自然是不会在显示屏上打印东西,毕竟1已经关闭了,可是我们是知道的,1这个文件描述符是给的新开的文件log.txt,那么你说,我们打印的东西会不会出现在log.txt呢?

还真的会。
那么这个现象希奇吗?其实并不算希奇,因为我们知道文件描述符1虽然被关闭了,但是实际上只是没给stdout而已,给了新开的文件log.txt,那么,这是不是一种重定向呢?
答案:是!通过改变文件描述符,改变我们要输出的内容。但是光如许我们的明白并不是很深刻,我们再利用函数fflush看看:
  1. int main()
  2. {
  3.     // close(0);
  4.     // close(2);
  5.     close(1);
  6.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);
  7.     printf("printf,fd:%d\n", fd);   
  8.     fprintf(stdout,"fprintf,fd:%d\n",fd);
  9.     //fflush(stdout);
  10.     close(fd);
  11.     return 0;
  12. }
复制代码
我们将1文件描述符关闭之后,往stdout里面打印东西,但是1因为已经被关闭了,以是自然不会在stdout上打印东西,但是根据上面的描述,打印的内容会打印到log.txt文件里面,与上文代码不同的是,在代码的最后,我们close了fd,结果如何呢?

发现log.txt的内容大小为0,打印出来看看:

也确实什么都没有。
那么如果我们加上fflush呢?
  1. int main()
  2. {
  3.     // close(0);
  4.     // close(2);
  5.     close(1);
  6.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);
  7.     printf("printf,fd:%d\n", fd);   
  8.     fprintf(stdout,"fprintf,fd:%d\n",fd);
  9.     fflush(stdout);
  10.     close(fd);
  11.     return 0;
  12. }
复制代码

结果居然是打印出来了,岂非是因为我们没有把缓冲区革新干净吗?
那么我们带着这个标题引出缓冲区 + 重定向的概念。

缓冲区 + 重定向

不知道各位同学是否还记得,历程终止章节的exit和_exit,我们通过实行,知道了exit实际上是调用的_exit,因为库函数是没有资格调用系统层面的东西的,并且,我们调用_exit之后,我们确定了我们利用exit革新的缓冲区肯定不是在系统层面的,那么在那篇文章,我们知道了exit革新的是上层的缓冲区,和我们上文所说的缓冲区是否是同一个呢?
答案:是的!
我们在这里利用的fllush,exit革新的其实都是语言层面的缓冲区。

大致的样子就是如许,那么,与之前不同的是我们知道了缓冲区不但有一个,似乎有多个?
在语言层面来说,我们写下的所有代码,都是给多个语言层面的缓冲区,以是,当我们关闭了1,此时1给了我们新开的文件,文件对应的就是该缓冲区,注意,我这里描述的是该文件对应的缓冲区是1所对应的。也就是原来stdout的缓冲区被用了,可是,为什么我们革新了之后,我们想要的内容就打印出来了?
这是因为,我们没有fflush之前,所有的内容都是放在的语言层面的缓冲区,当我们fflush,将里面的内容革新出去,到了内核层面的缓冲区,就不关我们的事儿了,那是OS的工作了,那么OS自然是会将内容革新到对应的磁盘部分。
那么,这,是不是一种重定向呢?是!因为改变了文件描述符!!
这里我们得到一个重要结论,也就是缓冲区有很多个,用户层面将内容写入到缓冲区里面,由库里面的函数进行操纵,将语言缓冲区写入到内核里面,再由OS将内容写入到磁盘。
但是标题来了,为什么我们不能直接将内容写到OS,或者说直接和OS进行交互呢?这是因为OS忙!!OS忙着调度,忙着接纳呢。以是系统调用每每都是比高级语言的调用慢的,成本有点高的。具体的后面一点点介绍。
那么我们现在已经明白了重定向,再加深一点印象,我们介绍一下,dup2这个函数:

对于dup2函数来说,参数只有两个,oldfd,newfd,那么,当我们改变文件描述符,比如上面的1从stdout给到了log.txt,我们应该dup2(fd,1) 还是 dup2(1,fd)呢?
我们结合fprintf,fprintf的参数为:

原本printf是将内容打印到1上,fprintf的第一个参数改变了1,改成了对应的文件对象,以是结合fprintf,dup2函数的参数应该是dup2(fd,1)。我们也可以通过文档描述看看:

dup2将让newfd成为oldfd的副本,本质上就是让原本文件描述符的指向改变了。
我们不妨来利用试试:
  1. int main()
  2. {
  3.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  4.     if(fd < 0)
  5.     {
  6.         perror("open fail!\n");
  7.     }
  8.     dup2(fd,1);
  9.     printf("Hello linux!\n");
  10.     fprintf(stdout,"Hello world!\n");
  11.    
  12.     return 0;
  13. }
复制代码
结果也是不出我们所料,成功写入到了log.txt文件里面:

就是因为我们将文件进行了重定向。
那么,对于重定向来说,我们已经有了一个较深的明白,现在,我们来引入一段较为希奇的代码,通过结果来引出缓冲区的概念:
  1. int main()
  2. {
  3.     int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  4.     if(fd < 0)
  5.     {
  6.         perror("open fail!\n");
  7.     }
  8.     dup2(fd,1);
  9.     printf("Hello linux!\n");
  10.     fprintf(stdout,"Hello world!\n");
  11.    
  12.     char* message = "Hello C++!\n";
  13.     write(1,message,strlen(message));
  14.     fork();
  15.     return 0;
  16. }
复制代码

注意那个fork,我们往1里面写入了三个字符串,但是为什么往一个文件里面,printf和fprintf都打印了两行,但是write函数只打印了一个呢?
这里我们就须要再次用到其时那个图片了:

我们将数据写到了语言层面的缓冲区是printf fprintf,write因为是系统调用,以是直接写到了内核层面的缓冲区,而我们创建了子历程之后,因为子历程是要继承父历程的代码和数据的,系统层面的肯定是继承不了的。而创建历程之后,历程竣事之后是会革新缓冲区的,也就是将语言层面的革新了两次,从而导致,log.txt里面,有两份一样的代码。
以是,我们从这个现象,引出三段论:
缓冲区是什么? 缓冲区为什么存在? 缓冲区怎么做的?
缓冲区是一块空间,但是本质上,缓冲区实际上是结构体,为什么我这么说呢?因为缓冲区的源码如下:

FILE实际上是_IO_FILE的typedef,stdout实际上就是文件指针,那么前文所提及的,1占据了stdout对应的缓冲区哦!!缓冲区不止一个,每个打开的文件都有对应的缓冲区,每个打开的文件都有本身对应的_fileno!!这和我们之前所认为的缓冲区的差异是非常大的。
缓冲区在语言层面有,在系统内核里面也有,在任何一个文件都有,这是我们本篇文章所得出来的一个重要结论。
那么对应的空间在哪里呢?我们不妨看看这个结构体内容,可以发现基本上都是区域的命名,以是我们所谓的写入数据,革新数据,其实都是从这些开辟的空间里面写入,革新,读取。
缓冲区是什么我们就说清楚了。
那么为什么存在缓冲区
答案非常简朴,是为了进步上一层的利用体验。
你想,如果我们直接和系统交互,就像我们翻山越岭一样,只为了给好朋侪一件礼物,非常的浪费人力和物力,但是如果我们利用顺丰,一次性,能运输几百件快递,岂不美哉?
以是在系统层面来看,它是为了进步高级语言层面的利用舒服度,而高级语言层面的缓冲区就是为了进步用户层面的利用舒服感。
那么缓冲区如何操纵的
缓冲区最重要的肯定就是革新操纵,以是我们要讨论的是革新策略的标题:
1 立即革新  2 行革新 3 全缓冲革新 4 特殊情况革新
对于1来说,就是我们上面说的,翻山越岭,只为了给好朋侪的plus版本,不外是多了一个中间站,我们给中间站一个东西,中间站给好朋侪一个东西,服从还是蛮低的。
对于2来说,行革新的策略现在碰到的有显示器,显示器就是行革新,这实际上是为了更符合人眼的观看,如果一次性全部革新出来,人眼也看不外来,如果是1个字符一个字符的打印,那体验就非常差了,以是显示器为了用户体验,利用的是行革新。
对于3来说,全缓冲革新就是等缓冲区塞不下了,这个时候才革新出去。
对于4来说,比如历程碰到了exit,意外终止了,终止之后就会革新缓冲区。
这是缓冲区的怎么做。
以上是对重定向和缓冲区的一个简朴明白。

感谢阅读!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张国伟

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表