文件描述符和缓冲区

news2024/10/7 14:33:40

文章目录

      • 文件操作符
          • 系统调用接口
        • 文件接口的简单实用
          • 实验一:打开文件写入信息
          • 实验二:read接口读取文件
        • 返回值和fd
            • 联想到数组下标
            • OS怎么管理文件呢?先描述再组织。
          • 一切皆文件
          • 012的操作
          • 文件描述符的分配规则:
          • 输出重定向的原理。
          • 追加重定向是怎么实现的呢?
          • 输入重定向:
          • 验证FILE的结构体中,有一个fileno
          • 是不是非得关闭之后才能重定向呢?不是,用dup2()
          • dup2()完成输入重定向
          • 执行exec*程序替换时,会不会影响曾经打开的所有文件?
      • 缓冲区
          • 用户到OS的刷新策略:
          • 解决重定向有区别的问题:
          • 再来:write&&printf
          • 问题三:刷新策略改变之下的写时拷贝导致的C缓冲区打印两次

文件操作符

所有的文件操做表现上都是进程执行对应的函数。就是进程对文件的操作,要操作文件需要先打开文件,就是将文件相关的属性信息加载到内存。

OS系统中会存在大量的进程,进程可以打开多个文件=1:n.系统中更多的存在打开的文件,那么OS管理起来,先描述在组织。用strcut file{}包含各种属性。

C语言层面上的:fopen() fputs() fgets();

w:是清空原来文件重写,a:这个写入方式是 append 追加fscanf 等。

C程序默认会打开三个输入输出流 stdin stdout stderr,FILE* stdin:键盘。stdout:显示器。stderr:显示器

输出重定向,本质是只把stdout的内容重定向到文件中。所以上面两个显示器是不一样的。

在这里插入图片描述

C++中cin cout cerr,fputs向一般文件或者硬件设备都能写入,一般文件都在磁盘上也是硬件,一切皆文件。
最终都是访问硬件,OS是硬件的管理者,所有的语言上的对文件的操作都必须贯穿OS。
操作系统不相信任何人,访问操作系统是需要通过系统调用接口的
几乎所有的语言fopen fclose fread fwrite fgets fputs fgetc等底层一定需要使用os 提供的系统接口调用!!

所以要学习文件的系统调用接口:

系统调用接口
  • int open(文件名,打开方式,设立文件权限信息);
    在这里插入图片描述

    int flags,传递标志位,int 32bit ,一个bit 代表一个标志。

  • int close();

在这里插入图片描述

  • 返回的int是文件描述符

所有的文件操作,表现上是进程执行对应的函数,进程对文件的操作。
要操作文件必须先打开文件!本质就是将文件相关的属性信息加载到内存。
系统中是不是存在大量的进程,代表着进程可以打开多个文件,进程:文件=1:n.
系统当中存在更多的,打开的文件,那么OS要把打开的文件(内存中的)管理起来

  • 如何管理打开的文件呢?先描述再组织。

    C语言中的struct{//包含了打开文件的相关属性//链接属性}
    未打开的文件就在磁盘上由文件管理器管理。

文件接口的简单实用

实验一:打开文件写入信息

int fd = fopen(“./log.txt”,O_CREAT | 0_WRNOLY,0644)

当前路径下的log.txt文件如果没有就创建一个,只写的方式
设置权限是0644

ssize_t write(int fd,const void * buf,size_t count)
写入字符串时你不需要写入\0,作为字符串结束只是C语言的规定,文件更注重的是内容。

在这里插入图片描述

实验二:read接口读取文件

文件中并不需要C/C++中的/0作为分隔符,所以我们写入的时候并不加。在读取的时候读少一个位置,那个位置因为我们读取出来多行字符是将他作为字符串来看待,所以需要手动的添加结尾/0用的。

在这里插入图片描述

返回值和fd

  • 当我一次打开多个文件时,返回的返回值是什么值。

在这里插入图片描述

  • 原因:

    当我们的程序运行起来时,变成进程之后,默认情况下OS会帮助我们打开三个标标准输入输出:
    0 标准输入,键盘。1 标准输出,显示器。2 标准输出,显示器。
    而open的返回值是OS给的。

    联想到数组下标

    进程:文件 =1:n; OS有很多进程对应更多的文件被打开,OS应该对文件进行管理。

    • 如果一个文件没被打开,那么这个文件在磁盘上。进程也是一个可执行文件,没被打开也在磁盘上。
    • 创建空文件占不占磁盘空间?空文件没有内容但是有属性,也是数据,所以占空间。

    所以,磁盘文件=文件内容+文件属性。之前的文件操作可分为:对于文件内容操作(fclosefopenread),对于文件属性操作(更改属性,重命名)

OS怎么管理文件呢?先描述再组织。

设计结构体struct file{
文件的相关的属性信息
}

在这里插入图片描述

  • 双链表的形式将文件结构体相连,哪些是某个进程的呢?

    所以添加了一个容纳数组的结构
    struct file_struct{} ,他当中有一个指针数组
    struct file* fd_array[]; 数组元素就是指向各个文件结构体的指针,fd就是指针在数组中的下标。这个数组的前三位下标为012的三个位置,储存的是默认打开的三个文件 标准输入 标准输出 标注错误
    所以,其他我们新创建的文件的文件描述符都是从下标3开始记录储存的。
    而某一个进程PCB中有一个指向这个结构的指针struct file_struct* fs;指向容纳这个数组的结构。
    所以在进程操作文件的过程,比如write read 等系统调用接口都需要传fd
    就是通过这个结构体指针找到指针数组,通过我们传的文件描述符fd,找到对应下标下的
    文件结构体指针,通过文件指针在很多的文件结构体中找到相应的文件结构体,然后在对文件进行读写等操作。
    小结:fd本质是内核中是进程和文件关联的数组的下标。

一切皆文件

内存和外设进行打交道的时候涉及到IO,外设都具有读写属性。键盘写入是什么意思?具备IO,键盘可以不实现某些功能。外设不同,在底层的实现上一定是不一样的。
各种硬件的读写方法等全部都是在驱动层完成的,帮助我们实现对于各种硬件程序的读写。
所以大家的读写方法是完全不一样的。

  • 那如何做到一切皆文件的呢?

文件虚拟层,VFS(virtual file system) ,刚才的struct file就是一个例子。
每一个硬件或者文件要被打开,OS都会为其生成一个类似struct file{}的文件先进行描述,用双向链表来组织。

在这里插入图片描述

  • C++多态?父类提供一个函数接口,子类继承接口并且进行相应的函数实现。父类只需要有一个指针指向某一个子就可以实现对于子类的调用。(用动物吃的功能帮助理解)
  • C语言上如何实现上述方法?可以在每一个struct file中包含很多函数对应的指针

那么在上层看这一层的文件时,底层的键盘的响应方法在这一层都有函数指针呼应。
那我要写,我就直接调用这个接口指针指向的方法就行,不用管你是什么文件,底层是啥样的也不关心。
上层只需要直到调用键盘的读接口,就能实现键盘的输入就行了,怎么写的不用关心。

所以在VFS往上看,就实现了一切皆文件。

在这里插入图片描述

如果基类是动物,所有的子类都继承并实现动物的函数接口,完成调用,那就可以看做是一切皆动物。多态就可以看作是一切皆的高级版本。
用户要实现某种读写方法,由文件描述符表,找到对应下标下的文件描述结构体的方法,方法接口再找到对应的子类的函数实现,函数再来实现对于外设或者文件的读写等操作。

  • file operation文件操作结构体中就有一个函数表,包含read,write等函数类似于基类虚函数表。

VFS中会包含每一个描述打开文件的结构体file_struct{},里面包含很多的函数指针,在上一层看待就是一切皆文件。

012的操作

默认的012也是可以直接读写的。write可以直接向12直接进行写入,有什么区别?

  • 验证read可以直接0读写呢?

在这里插入图片描述

在这里插入图片描述

文件描述符的分配规则:

给新文件的fd是数组中最小的没有被使用的fd。close(0)close(2)之后,新打开的文件fd就是0或者2.
但是close(1)关闭标准输出时,本来应该显示到显示器中,但是却被显示到你打开的文件内部,就叫做输出重定向。

在这里插入图片描述

原因:printf打印是stdout进行打印,stdout是FILE*类型。

在这里插入图片描述

FILE是C语言层面上的结构体

struct FILE{
	//一定包含了一个整数,是在对应的系统层面的,这个文件的打开对应的fd(1)。
}

在这里插入图片描述

printf是向stdout中写,这个stdout中包含数字1,就是照着这个fd(1)指向的文件使劲打印,只认fd1,语言层只认识012,这样就给重定向提供了操作空间,所以刚才还指向显示器标准输出,后来close(1)关闭了显示器文件又更改为新创建的文件log.txt时,就改变了输出的方向,改为写入到log.txt文件中。这就是

输出重定向的原理。

printf fprintf cin --都是语言层。调用系统调用,open write fd 都是系统层面。所有的语言,进行文件操作时,都需要系统层面的fd,这样语言和系统就产生了关系。

命令行形式输入echo"hello world">log.txt ,就是一条输出重定向语句。

追加重定向是怎么实现的呢?

C语言中的append,a 选项,在尾部进行写入O_APPEND。

在这里插入图片描述

输入重定向:

把fd=0的标准输入文件替换成我们新的文件,将新的文件里面的内容进行写入。采用fgets()按行读取文件内容。

在这里插入图片描述

验证FILE的结构体中,有一个fileno

在这里插入图片描述

是不是非得关闭之后才能重定向呢?不是,用dup2()

在这里插入图片描述

  • 输出显示器->1,如何在已经打开的情况下再完成输出重定向

int dup2(int oldfd,int newfd);fd 1 数组内容全部变成oldfd,将oldfd赋值给newold。将3里面的内容覆盖到1里面。本来应该显示到显示器的内容写入到新的3号文件中。让newfd是oldfd的一份拷贝,两个都变成了oldfd。想让1指向原来3指向的文件。

dup2()是在系统层面上将fd转换,三种语言层面的写入根本不关心系统,只认文件描述符表中的1.

在这里插入图片描述

在这里插入图片描述

dup2()完成输入重定向

在这里插入图片描述

执行exec*程序替换时,会不会影响曾经打开的所有文件?

内核数据结构替换的时候只会替换代码和数据。
命令行式重定向:重定向符号时就会发生输出重定向,将子进程fd拷贝给1,然后再程序替换。

fork()创建子进程时,内核层面的东西都重新生成一份给子进程,都是一样的,也会给他创建新的file_struct{},拷贝父进程的,实现两份完全一样的文件描述符表。
但是打开的文件,文件不会拷贝,还是原来的那些文件 ,会出现父进程子进程可能指向一个文件。
那么,父进程如果曾经打开了标准输入等呢?子进程也会继承下去!!!

  • 为什么所有的进程都会默认打开012呢?

    因为所有进程的父进程是bash,他是命令行啊!bash打开,必然导致012的打开让你输入输出错误,
    子进程全部继承父进程,所以也会默认打开012。

  • 引用计数,数一共多少个进程。因为一个文件可以被多个进程打开,引入了C++中的引用计数。

缓冲区

重定向会将fd=1的打印到文件中,fd=2的标准错误还是会打印到显示器上。

./redir 2>&1 就可以将标准错误也打印到文件当中。
& 是将1的数据进行写入的功能,就是将已经重定向的1里面的重定向的新的文件描述符拷贝到2里面,然后2就跟着1也指向到新文件当中了。

最后是否添加close(1)的区别:

在这里插入图片描述

因为C语言也提供了缓冲区。如果将文件close(1)之后,就无法完后续打印到显示器和重定向的功能。

在这里插入图片描述

我们知道,printf就是向stdout中打印,stdout是FILE*类型,指向一个struct 结构体,结果就是将内容写到C语言提供的用户缓冲区中,定期刷新到OS缓冲区,然后再刷新到磁盘。struct结构体,这个结构体里面一方面封装了一个fd,文件描述符,另一方面与维护了C缓冲区相关的内容。而我们将字符串等在语言层面的操作到文件,都是暂时储存到了这个FILE结构体内的缓冲区里面。

遇到/n等操作时,或者进程退出的时候,会刷新FILE里面的数据到OS缓冲区。

用户到OS的刷新策略:
  1. 立即刷新(不缓冲)
  2. 行刷新,比如显示器打印就是\n这种行刷新
  3. 全缓冲,缓冲区满了才刷新,比如我们往磁盘文件中写入。然后OS->硬件上也是同样使用的。
解决重定向有区别的问题:

显示器 ->log.txt重定向时,本来是行缓冲,现在变成了全缓冲。

  • 就是为什么调用close(1),重定向的时候并没有打印到文件当中

从显示器重定向到文件,刷新方式从行缓冲到全缓冲,
如果在最开始并没有close(1),进程在结束的时候,会自动根据fd将用户层面的缓冲区刷新到文件中,就会看到内容在文件中。
但是如果先close(1),新打开的文件被分配到的fd=1,重定向,触发全缓冲。在进程结束之前,再写上**close(fd)**就将文件等操作关闭了,在进程结束的时候就没有了fd来帮助将用户的缓冲区内容刷新到OS文件中,仍然停止在用户缓冲区。所以就在文件中不可见。
如果在调用close(fd)时,在关闭之前强制刷新一下,fflush(stdout)就将依据还存在的fd,将用户缓冲区内容刷新到OS缓冲区,这样文件中就可见了。

再来:write&&printf

在这里插入图片描述

未加close(1);不构成重定向在进程结束时会将1的用户缓冲区数据全部刷新到OS的缓冲区。
加上close(1);之后,构成重定向,只在文件中有局部内容(比如第一句话)是因为write()是系统调用接口,跳过用户缓冲区直接到OS中文件的系统缓冲区。而C语言提供的接口printf,fprintf()中的内容保存在用户缓冲区,重定向之后,从行缓冲到全缓冲,因为还未达到全缓冲的条件,所以还未送到OS中的文件缓冲区,所以文件中没有。

C语言的接口是打印到stdout,对应的是FILE 类型的结构体,结构体里面有fd ,和用户缓冲区buffer
后续根据fd 将缓冲区里面的东西根据fd刷新涉及到刷新策略,文件类型决定到系统文件缓冲区。

问题三:刷新策略改变之下的写时拷贝导致的C缓冲区打印两次

C语言提供的接口重复出现,系统接口并不受影响。

在这里插入图片描述

如果没有发生重定向,都是行缓冲, 并且每条语句都有\n刷新,正常可见。

C将内容写到用户层缓冲区,在发生重定向时,行缓冲变为全缓冲使得数据暂时在C缓冲区。

然后fork(),这些内容属于父进程,但是子进程也会进行写时拷贝将代码数据什么的自己也有一份
然后等进程退出时,C缓冲区强制刷新到OS缓冲区,两个进程都需要刷新到缓冲区,从而达到写入到文件中两次的效果。
所以fork()还是可能影响上文代码效果。提前刷新缓冲区就行了fflush(stdout)。重定向时写实拷贝,但是缓冲区中没数据,就不会打印两次。

write系统调用接口是没有缓冲区的。

  • 总结:stdout,cin cout iostream fstream CC++流的类(流也是类)里面都是包含缓冲区的。std::endl;的作用就C++中类似\n的作用,就是刷新缓冲区。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/86178.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apache Kyuubi、Spark Thrift Server与Hive Server2

HiveServer2和Spark Thrift Server HiveServer2和Spark Thrift Server,两者其实都是提供一个常驻的SQL服务,用来对外提供高性能的SQL引擎能力,不过两者又有些偏差,主要是HS2是独立的Server,可组成集群,而S…

【进阶】C语言第三课:升级你的指针(2)

目录 🍇前言🍇: 一、数组参数🤠: 1.一维数组传参🍈: 2.二维数组传参🍉: 二、指针参数🤩: 1.一级指针传参🍊: 2.二级指针…

【论文写作】课程总结

文章目录1、前言2、概述3、摘要与关键字4、引言5、相关工作6、理论7、实验8、总结1、前言 《论文写作》不仅是本人认为的在本学期收获较大的一门,也是最重要的课程之一。因为作为研究生,论文是必不可少的一部分。论文是就自己研究方向中所得到的成果的一…

网络设备的运行隐患怎么排除?日常的例行维护绝对不能少,收藏本文,轻松拿捏各种场景!

设备稳定运行一方面依赖于完备的网络规划,另一方面,也需要通过日常的维护发现并消除设备的运行隐患。 日常维护怎么才能进行呢?有哪些必要的步骤呢? 记住这五步: 1、设备环境检查 设备运行环境正常是保证设备正常运…

PreScan快速入门到精通第四十二讲点云传感器

点云传感器(PCS)是一种理想化的传感器,用于构建高数据率和高更新率的点云数据。该传感器的实际应用包括检测算法的开发、激光雷达系统的设计和验证或HIL验证。同时具备竖直方向的FOV相关信息,支持4D成像雷达系统的仿真开发。 该传感器具有固定但可配置的模式,并针对性能(…

分享一些冷门但却很实用的css样式

在平常的代码工作中,有很多冷门不常用的css样式标签。有些偏门、冷门的标签一般都记不住,想起来的时候就又会去现找,很影响工作效率,现在,把这些标签都统一整理一下用的少但是超级实用的css样式。 ::-Webkit-Input-Pla…

0基础转行学软件测试,哪些技术是必须要掌握的?

作为近些年非常热门的IT岗位,软件测试-受到越来越多应届毕业生和诸多转行群体的青睐。为了满足同学们对软件测试的学习要求,测试猿课堂将在本文为大家详细讲述成为自动化软件测试工程师必须要具备的能力体系。 软件测试的学习体系总的来讲可以分为五个阶…

Redis框架(十一):大众点评项目 乐观锁解决超卖问题 悲观锁解决一人一单问题

大众点评项目 基于Session的短信登录需求:乐观锁解决超卖问题 悲观锁解决一人一单问题业务代码总结SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评 主要依照以下几个原则 基础实战的Demo和Codi…

数字IC后端设计如何快速入门?(内附学习视频)

虽然2022年IC行业门槛有所提高,但这也抵挡不住同学们对转行IC行业的热情,数字后端设计的发展前景和高薪也在众多岗位中脱颖而出,那么数字IC后端设计如何快速入门?下面IC修真院就带大家来了解一下。 数字后端工程师是做什么的&…

Docker:自定义镜像上传阿里云

目录 一.jdkv.1.0的制作 启动虚拟机,进入centos 创建文件夹上传jdk的安装包,和在同级目录下编写Dockerfile文件 执行Dockerfile文件,初次依赖镜像的时候会下载相应镜像​​​​​​​ 二.jdk2.0的制作 三.jdk3.0的制作 四.将制作好的镜像上传阿里云…

一文解读机密容器的崛起和发展

在 2022 云栖大会龙蜥峰会云原生专场上,来自阿里云操作系统技术专家冯世舫和Intel 系统软件工程部高级研发经理朱江云分享了《机密容器的崛起和发展》技术演讲,以下为本次演讲内容: 机密容器是 CNCF 的 一个 Sandbox 项目,用于解…

第一章 linux的发展

第一章 linux的发展一、操作系统的出现二、linux的出现三、linux的发展一、操作系统的出现 大部分先进产品的出现必定是为了军事服务的,起初的大型计算机也同样是为了军事服务的,而操作计算机的人也不是程序员,而是科学家。二战时期&#xf…

DVWA靶场中SQL注入

DVWA靶场中SQL注入1.DVWA靶场中SQL注入1.1.SQL Injection1.1.1.Low级别1.1.2.Medium级别1.1.3.High级别1.2.SQL Injection(Blind)1.2.1.方式1.2.2.Low级别1.2.3.Medium级别1.2.4.High级别1.DVWA靶场中SQL注入 1.1.SQL Injection 1.1.1.Low级别 1)判断注入类型当输…

高中数理化杂志高中数理化杂志社高中数理化编辑部2022年第21期目录

高考全关注《高中数理化》投稿:cn7kantougao163.com 直线与圆的方程高考热点赏析 廖永福; 1-4 一道课本例题到一道高考试题的衍变之路 高磊; 4-8 圆的多种定义形式在解题中的应用 李光彬;邵建凤; 9-10 从2021年全国新高考Ⅰ卷第21题说起 王菊;张琥;…

碳交易机制下考虑需求响应的综合能源系统优化运行(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

接口测试(五)—— PyMySQL增删改查、数据库工具类封装

目录 数据库操作应用场景 一、PyMySQL操作数据库 1、安装PyMySQL 2、PyMySQL操作步骤 3、事务的概念 4、PyMySQL连接数据库 4.1 建立连接方法 4.2 入门案例 5、PyMySQL操作数据库 5.1 SQL 语法 5.2 数据库查询 5.3 案例(查询) 5.4 数据库UI…

代码随想录训练营第七天

专题:哈希表 题目:四数相加 题目简单:把四个数组分成两队,然后用map,保存前两个数组的元素之和,(key,val)key保存的是前两个数组的元素之和的数值,val保存的是数值对应…

PDF设置密码保护的两种方法

PDF文件可以根据需要,设置两种密码来保护文件。 需要保护文件内容,不想PDF被随意打开,我们可以设置打开密码,这样只有输入正确的密码才能打开文件。 在编辑器中打开PDF后,找到菜单中【保护】选项下的【密码加密】&am…

SpringCloud01

1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢? 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构:将业务的所有功能集…

33-98-spark-核心编程-RDD算子和任务阶段等

33-spark-核心编程-RDD: 1、RDD的创建,4中方式。分别是从内存中创建,从文件中创建,从其他RDD创建和new RDD,后两者不常用。 创建:big-data-study\Spark-demo\src\main\java\spark\core\com\zh\rdd\builde…