文件描述符2

文件描述符

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来
描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进
程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

图解
在这里插入图片描述

文件描述符的分配规则

文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
当我们在open一个文件之前,close(1),我们会发现这个文件的fd为1,如果我们关闭的是0,那么新打开文件的文件描述符就是0

重定向

输出重定向

看代码:

  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <fcntl.h>
  7 
  8 int main()
  9 {
 10     close(1);
 12 
 13     int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
 14     if(fd<0)
 15     {
 16         perror("open");
 17         return 1;
 18     }
 19 
 20     printf("fd:%d\n",fd);
 21     printf("fd:%d\n",fd);
 22     printf("fd:%d\n",fd);
 23     printf("fd:%d\n",fd);
 24     printf("fd:%d\n",fd);
 25     printf("fd:%d\n",fd);
 26     printf("fd:%d\n",fd);
 27 
 28     fprintf(stdout,"hello fprintf\n");
 29     const char* s="hello fwrite\n";
 30     fwrite(s,strlen(s),1,stdout);
 31     fflush(stdout);                                                                                                                             
 32     close(fd);
 33     return 0;
 34 }

我们会发现,原本应该输出到显示器上的内容,全都输出到了log.txt文件中,这种现象叫做输出重定向。常见的重定向有:>, >>, < 。
为什么会发生这种情况呢?
首先我们知道,给进程会默认打开三个文件,其中默认输出stdout对应的文件描述符就是1,对应的文件是显示器文件,而我们在创建文件log.txt之前close了1,这就会导致对应文件描述符表中下标1对应的指针置空,所以close就相当于关闭进程与显示器文件之间的联系,根据文件描述符的分配规则,当我们新建一个文件时,自然就会把文件描述符1分配给log.txt,这时我们再调用C库函数,printf或者fprintf(stdout,···)的时候,因为stdout里面对应的_fileno为1,所以在底层调用系统接口函数的时候就把1传进函数,自然原本对显示器的操作就变成了对文件log.txt的操作,这就是重定向。
重定向的本质,其实就是在OS的内部,更改fd对应的内容的指向

在这里插入图片描述

输入重定向

  1 #include <stdio.h>                                                                                                                              
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <fcntl.h>
  7 
  8 
  9 int main()
 10 {
 11     close(0);
 12     int fd=open("log.txt",O_RDONLY);
 13     if(fd<0)
 14     {
 15         perror("open");
 16         return 1;
 17     }
 18     printf("%d\n",fd);
 19     char buffer[64];
 20     fgets(buffer,sizeof(buffer),stdin);
 21     printf("%s\n",buffer);
 22 
 23     return 0;
 24 }

追加重定向

把输出重定向中的O_TRUNC改为O_APPEND即可

dup2函数

#include <unistd.h>
int dup2(int oldfd, int newfd);

在这里插入图片描述
dup2函数的作用就是把文件描述符表中的oldfd位置的指针,拷贝到newfd中。
所以当我们进行重定向的时候,就不必先close掉0,1再打开文件,我们可以先打开一个文件,获取到文件描述符,然后把这个文件描述符作为oldfd,0或1作为newfd,这样就可以完成重定向的过程

  8 int main(int argc,char* argv[])
  9 {
 10     if(argc!=2)
 11     {
 12         return 2;
 13     }
 14     int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
 15     if(fd<0)
 16     {
 17         perror("open");
 18         return 1;
 19     }
 20 
 21     dup2(fd,1);                                                                                                                                 
 22     fprintf(stdout,"%s\n",argv[1]);
 23 
 24     return 0;
 25 }

在这里插入图片描述

理性认识——Linux下一切皆文件

这里的文件,其实就是一种对象,一切皆文件,其实也是一种面向对象的思想
那我们知道,Linux是由C语言写的,那么C语言是如何实现面向对象的呢?,面向对象的实现,就是依靠类,
类里面既有成员属性,也包括成员方法,但是C语言的struct结构体中不能包含方法,那我们怎么办?
虽然不能有成员方法,但我们可以用一个指针指向外面写好的方法,这就是函数指针,综上在C语言中,我们可以在struct结构体中利用函数指针,实现面向对象。
struct file就是这样,我们抽象的描述一下:
在这里插入图片描述
既然说一切皆文件,文件就是读写呗,所以对于不同的外设,比如硬盘,显卡,显示器,键盘,网卡等,每一个设备的核心访问函数,都可以是read、write,同时对于底层不同的硬件,一定对应的是不同的操作方法。
所以对于所有的设备,都会有自己的read和write,但是代码的实现,一定是不一样的
利用函数指针,当我们在操作系统中打开不同的文件的时候,我们可以把对应struct file结构体中的函数指针指向对应文件的读写函数,这样在上层看来,就不存在任何硬件的差别了,所有看待文件的方式,都统一成为了struct file,这就是Linux下一切皆文件。(VFS)
在这里插入图片描述

缓冲区

  1. 什么是缓冲区?
    就是一段内存空间
  2. 为什么要有缓冲区?
    提高整机效率,主要是为了提高用户的响应速度。

缓冲区的刷新策略

  1. 立即刷新
  2. 行刷新(行缓冲 \n)
  3. 满刷新(全缓冲)
    特殊情况:
  4. 用户强强制刷新(fflush)
  5. 进程退出

缓冲区在哪里

我们先看一段代码

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <fcntl.h>
  7 
  8 int main()
  9 {
 10     printf("hello printf\n");
 11     fprintf(stdout,"hello fprintf\n");
 12     const char* s="hello fputs\n";
 13     fputs(s,stdout);
 14 
 15     const char* ss="hello write\n";
 16     write(1,ss,strlen(ss));
 17 
 18     //fork();                                                                                                                                   
 19                          
 20     return 0;            
 21 } 

当没有后面fork的时候,运行程序不管是输出到屏幕上,还是重定向到文件中,输出内容都是那四行,但是当我在后面添加fork的时候
在这里插入图片描述
结果变成了这样,而且在文件中的结果hello write在最前面,只被打印了一次,其他的都被打印了两次,这是怎么回事呢?

关于缓冲区的认识

所有的设备,永远都倾向于全缓冲,因为缓冲区满了才刷新,这需要更少次数的IO操作,也就意味着更少次的外设访问,这会提高效率
因为和外部设备IO的时候,数据量的大小不是主要矛盾,和外设预备IO的过程是最耗费时间的!
一般而言,行缓冲的设备文件为显示器,全缓冲的文件设备为磁盘文件

这时我们分析一下,上面的问题。
从输出结果来看,系统接口并不会受到影响,如果有所谓的缓冲区,那这个缓冲区是由谁维护的呢?C标准库还是OS?
我们所说的缓冲区,绝对不是OS提供的,如果是OS提供的,那么我们上面的代码,表现应该是一样的,而不是说系统接口函数打印了一次,而C IO函数打印了两次

  1. 如果是向显示器打印的时候,刷新策略就是行刷新,那么我们这几个CIO函数,遇到\n就直接把缓冲区刷新了,当执行到fork的时候,数据已经被刷新完了,所以fork没有任何意义
  2. 如果我们是重定向,要向磁盘文件打印,那么刷新策略就变成了满刷新,就算遇到了\n数据也不会从缓冲区被刷新出去,当fork的时候,这几个函数虽然执行完毕,但是缓冲区的数据并没有被刷新出去,子进程和父进程此时是共享这部分缓冲区数据的,当子进程或父进程结束的时候,此时就要刷新缓冲区,这其实也是一种写操作此时就会发生写时拷贝,父子进程各有一份缓冲区数据,父子进程结束,就会把这两份数据都刷新出去,此时我们就会看到,Cio操作的内容被打印了两次,write没有所谓的缓冲区,所以就没有变化。

综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。
当每个文件fopen的时候,返回了一个FILE*,FILE是一个结构体,里面不仅封装了fd,还有该文件fd对应的语言层的缓冲区结构。
在这里插入图片描述

其实write就是直接写入了操作系统,而C写操作函数,就相当于先拷贝到对应的缓冲区空间中,按其对应的刷新策略再write到操作系统中,不要认为write就是直接写到对应的硬件上面了。
当我们以写的方式打开文件的时候,C语言会自动为其分配缓冲区并且指定缓冲区的类型,缓冲区的类型涉及到刷新策略,对于上面的代码,其实我有个疑问,重定向的时候不是仅仅改变了文件描述符的指向吗,但是对于C库函数来说它应该察觉不到这个变化啊,并且此时stdout对应FILE结构体中的缓冲区也没变化,类型也没变化,那么刷新策略怎么就变化了,可能是因为刷新策略也不仅仅是由缓冲区类型决定的,肯定跟文件描述符指定的具体的文件类型也有关系,就算你的缓冲区类型为行刷新,但是我是一个磁盘文件,默认刷新方式为满刷新,那么最后的刷新类型也为满刷新。但底层是如何实现的,仍未可知。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值