Linux·文件与IO

1. 回忆文件操作相关知识

        我们首先回忆一下关于文件的一些知识。        

        如果一个文件没有内容,那它到底有没有再磁盘中存在?答案是存在,因为 文件 = 内容 + 属性,即使文件内容为空,但属性信息也是要记录的。就像进程的组成是代码和数据 + PCB一样。

        下面看一段经典的文件操作的代码,回忆一下文件操作的函数。

                        

        这是向文件 log.txt 中写入5行 hello file ,如果当前工作目录没有log.txt那就新建一个。

        我们发现如果我们想对一个文件进行读写操作,首先就要打开文件,那为什么要先打开文件,打开文件到底是在干什么呢?

         我们知道文件是在机器的磁盘上存储的,但是对文件进行操作的却是在进程当中,而进程的所有信息都应该在内存上存储的,也就是说如果想操作文件就要把它加载到内存中通过进程去操作,而加载内存这一行为在代码上的表现就是fopen

        文件被加载到内存之后肯定也要被OS管理起来,也就是文件也要被先表述再组织,具体来说,在内存中文件肯定被OS用 文件的内核结构+文件的内容 挂起来再用数据结构串起来,便于之后进程对文件进行操作,也就是说我们研究文件操作,就是在研究文件和进程的关系。

        文件有两种:1. 被打开的文件(在内存中)    2. 没被打开的文件(在磁盘中)    这两种文件我们后面都要研究。

        fopen函数

        选项 r:只读打开文件,从文件开头处开始

        选项 r+:读写打开文件,从文件开头处开始

        选项 w:将文件截断(清空),若不存在则新建文件,从文件开头处开始

        选项 w+:读写打开文件,将文件截断(清空),若不存在则新建文件,从文件开头处开始

        选项 a(append):向文件追加,如果不存在则新建文件,从文件末尾处开始

        选项 a+:读写打开,如果不存在则新建文件,读从文件开头处开始,写从文件末尾处开始

        在此我们回忆一下关于输出重定向的内容

                

        符号 > 就相当于选项 w 打开文件,符号 >> 就相当于选项 a 打开文件

        关于文件操作的更多内容C语言·文件操作-CSDN博客文章浏览阅读1k次,点赞25次,收藏21次。本文详细讲解了C++中文件的概念、不同类型文件(程序文件、数据文件),以及二进制文件和文本文件的区别。涉及文件的打开、关闭、流(包括标准流和文件指针)、顺序读写、随机读写(如fseek、ftell、rewind)以及文件结束判定(feof、ferror)。还介绍了文件缓冲区的工作原理。https://blog.csdn.net/atlanteep/article/details/134894644?spm=1001.2014.3001.5502        关于输出/输入重定向的更多内容

Linux·基本指令(下)-CSDN博客文章浏览阅读971次,点赞22次,收藏21次。本节讲解了剩余的一些指令,有mv、cat、重定向的> >> < 、less按页查看、head tail 管道| 、date、cal日历、find、which、alias指令别名、zip unzip tar压缩解压、sz rz操作系统间传文件、bc计算器、uname -r/-a 查询机器相关参数、shutdownhttps://blog.csdn.net/atlanteep/article/details/138714286?spm=1001.2014.3001.5502

        任何一个进程在启动的时候都默认要启动三个输入输出流,分别是 stdin(标准输入) stdout(标准输出) stderr(标准错误) 。这三个文件流对应的设备分别是键盘、显示器、显示器,我们又知道Linux下一切皆文件,因此这三个设备其实就是三个文件,它们有对应的文件流也是合理操作。

        到目前为止我们学过的打印到显示器的方法有如下四种

                

                

        这些都是C语言提前给我们封装好的文件操作接口,并不是系统层面上的接口,接下来我们看看系统级别的文件操作函数。

2. 文件操作系统接口

2.1 open

2.1.1 参数

                

        第一个参数 pathname 一个字符串,可以选择带或不带路径的文件名,不带路径的话就只能打开当前工作路径的文件。

        第二个参数 flags 标记位,常见的标记位一共有5个

O_RDONLY(只读)    O_RDONLY(只写)    O_RDWR(读写)    O_CREAT(创建)  O_APPEND(追加)

O_TRUNC(清空)

        这5个标记位都是宏,它们之间是可以随意组合的。组合的目的就是一次性传递更多信息,而实现的方法就是 flag 是一32位的位图,每一个比特位都代表一个开关。

        这五个标记位每人对应一个比特位,将它们 按位或 也就是将它们组合,最后的结果给到位图flag分析,通过每一位上的1和0标记出某一个功能的开启或关闭。

        第三个参数 mode 设置权限。系统接口没有默认权限这一说的,因此我们在O_CREAT创建文件的时候,一定要带上第三个参数来设定好权限,否则权限就会出现乱码。而mode的值就是权限码,当然这个值后来会被权限掩码所影响。

        关于文件的权限,权限码,权限掩码是什么参考:

Linux·权限与工具(上)-CSDN博客文章浏览阅读798次,点赞22次,收藏21次。本节讲解了,shell操作系统的外壳程序是什么,Linux中怎么设置删除用户,文件和目录的权限属性:读写可执行,拥有者拥有组其他人,以及如何修改这些参数,umask权限掩码是什么,粘滞位t是什么权限https://blog.csdn.net/atlanteep/article/details/140466975?spm=1001.2014.3001.5502                真实权限码 = 设置权限码 & umask按位取反

        下面我们实操一下

                        

                

        可以看到我们设置的权限码虽然是 0666 但是因为权限掩码是 0002 因此最后表现出来的真实权限码就是 0664 ,这个666前面必须要有0表示这是一个八进制数

        如果不想让OS使用权限掩码影响我们对文件权限的表述

        可以在代码中使用 umask() 接口更改权限掩码

                        

                        

                

        可以看到文件的权限变成了666 ,因为这个程序是在子进程中跑的,因此更改的umask并没有影响到shell上的umask值。

2.1.2 返回值

        我们前面看到open函数的返回值是int类型的数字

        打开失败返回 -1

        打开成功返回descriptor(文件描述符),跳转到 2.2 close 看看文件描述符是什么

2.2 close

        man 2 close查询

                

        通过文件描述符关闭某个文件。

        我们这里直接说结论fd就是数组的下标,这个数组是让进程数据结构和文件数据结构产生关联的一张表(文件描述符表)。

        我们使用open多次打开多个文件之后会发现这样一个现象:

        三个默认启动的输入输出流也都有各自对应的硬件文件,但因为它们是默认启动的,因此它们的fd是固定的0、1、2

                stdin对应标准输入匹配是键盘文件,fd=0

                stdout对应标准输出显示器文件,fd=1

                stderror对应标准错误显示器文件,fd=2

        之后再打开的文件直接pushback到数组结尾,也就是从fd=3开始依次增加。

        最后我们说一下出现这种现象的原理,以及fd所在的表到底是什么

                        

        当文件被加载进内存之后并不是在进程模块中,而是自成一派。由file结构体存储文件的打开信息,就像PCB一样,加载一个进程进来就用链表穿起来,最后组织成一个链表file list

        那进程和文件又是如何耦合起来的呢?

        PCB中有一个指向 file_struct结构体 的 指针files,这个结构体中有一个非常重要的指针数组 fd_array[ ]文件描述符表 ,文件描述符表中每个元素指向在该进程中打开的文件结构体地址,因此这个数组的下标就是所谓的 fd ,因为三个文件读写流是默认启动的,因此这个数组的前三项总是它们,也就是0、1、2总对应着这三个流

2.3 write

        man 2 write查询

        

        w w+ 式写入

        ​​​​​​​

        ​​​​​​​        

O_TRUNC控制每次打开先截断文件。

        这里write写入文件的时候不要把字符串的\0也写进去,因为这个字符串结束符是C语言定义的,文件不认,因此只要把干干净净的字符串送进文件中就行。

        a a+ 式写入

        

        ​​​​​​​        

        

2.4 read

        man 2 read查询

                ··

        把fd文件中的内容读到buf中去,期望读到的字节数位count,最多能读count个数据

        返回值是实际读到的字节数,sssize_t就是有符号的整数

        ​​​​​​​        ​​​​​​​        ​​​​​​​

        这段代码的功能就是从标准输入流也就是键盘中读取数据到buffer数组中。

        

3. VFS虚拟文件系统

        VFS(virtual file system) 虚拟文件系统,是使用函数指针的方法屏蔽底层不同硬件操作上的差异的方法。也是我们所说Linux下一切皆文件的核心思路,下面我们看看这个VFS到底是怎么实现一切皆文件的。

        首先每个硬件都由自己的驱动把自己的信息加载到OS中的device结构体中,但是每个硬件的读写方法是不一样的,但这都不是问题。在VFS虚拟文件系思路中,将为硬件也创建 file文件结构体 这个结构体中包括了 read 和 write 函数指针,指向对应硬件的读写方式。此时就可以调用read write函数调用到对应的硬件读写方法,从而完成对硬件的读写。

        通过虚拟文件系统的思路将硬件模拟成文件,然后将硬件的操作方法映射到文件的操作函数上,此时就可以通过操作文件的方法操作硬件了,使用函数指针屏蔽底层不同硬件操作上的差异。这就是Linux下一切皆文件的实现方案。

        其实这也是C下实现多态的思路,虽然都是read函数,但是作用在不同的文件结构体中可以产生不同的效果。

4. IO的基本过程

        我们知道 file 结构体用来描述文件,包括其属性集和操作方法表等,同时还由一个指向文件内核缓冲区的指针。

        文件的IO过程都要通过这个缓冲区作为中间商,平衡磁盘和内存之间读写速度上的差异。

        读就是将文件内容从磁盘加载到文件缓冲区,然后通过read函数将文件缓冲区的内容提取到某个数组或者变量中去。

        写就是通过write函数先将变量或者数组中的内容存放到文件缓冲区中,之后OS自己决定何时将文件缓冲区中的内容刷新到磁盘中去。

        这也是为什么我们在操作word文档的时候,如果突然文档进程挂掉了,我们没保存的东西可能就没了,因为我们写文档的时候只是把内容write到了文件缓冲区中,而文件缓冲区是在被进程管理着的,进程挂了缓冲区直接就被释放了,根本来不及把内容写到磁盘中去。

5. 深入理解重定向

        首先我们了解一共简单的概念,进程打开文件需要给文件分配文件描述符fd,fd的分配原则是分配最小的未被使用的fd

        ​​​​​​​        ​​​​​​​        

        ​​​​​​​        ​​​​​​​        

        可以看到正常情况下,因为文件描述符 0、1、2 都被占用了,因此后续进程打开的文件,其描述符是从3开始递增的。

        ​​​​​​​        ​​​​​​​        

        ​​​​​​​        ​​​​​​​        

        可以看到当我们把0号文件关闭之后,第一个我们自己打开的文件就从0号描述符开始排队了,这就是所谓分配最小的未被占用的文件描述符。

        在继续谈重定向问题之前我们再明确一个问题,为什么 printf 就是直接把内容打印到显示器上,但是 fprintf 既可以选择stdout打印到显示器,也可以通过 FILE* 指针打印到某个文件中去。

        道理很简单,因为printf函数中写死了写入的文件,就是 fd == 1 的文件也就是显示器文件,因此我们printf的内容就都被固定输出到 1 文件中去了。

        由此我们获得灵感,如果 close(1) 此时 1 号文件(显示器)被关闭,但是 open log1.txt 的时候就会把这个文件的 fd 设置成 1 ,之后的printf又指认 1 号文件,也就是说这么做是不是就完成了printf的重定向

        我们实践一下

        ​​​​​​​        ​​​​​​​        

        ​​​​​​​        ​​​​​​​        

        当我们编译运行之后发现并没有在屏幕上打印fd的信息,这还算正常,因为现在 1 号位中的文件已经不是显示器,而是 log1.txt 了。但是当我们查看 log1.txt 的时候发现这个文件里也啥都没有,这一点就很奇怪了。

                

                

        但是我们使用fflush函数刷新一下就可以看到该写入的东西就写入了,也就是完成了重定向的现象。

        之所以会出现这种现象是因为我们选择的打印函数printf是C语言的函数,而语言级的IO函数也有一块配套的用户级缓冲区,其作用是暂时保存要写入的内容,之后等待时机写入文件的内核缓冲区,而进入文件内核缓冲区之后要写入的数据就被OS管理起来了,什么时候刷新到显示器上就是OS自己决定的了。

        之所以有用户级缓冲区是因为使用系统调用函数的成本是很高的,至少比一般我们在语言层上写的那些的函数要高。因此不能频繁的调用系统调用将数据插入到文件内核缓冲区,于是就诞生了用户级缓冲区暂存数据。

        用户级缓冲区刷新数据的机制:1.如果是显示器文件,遇到\n就刷新,这种机制也叫行刷新。  2. 普通文件,将用户级缓冲区填满再刷新  3. 不缓冲,直接使用系统调用将数据存入文件的内核缓冲区。

        因此之所以会出现我们刚才那种现象,是因为使用printf函数的时候,先是将数据暂存到了用户级缓冲区,到合适的时候会调用write接口刷新到文件内核缓冲区。但是因为我们要写入的内容实在太少了,没有触发写入文件内核缓冲区的机制,但是printf完之后我们就使用系统调用close将文件关闭了,此时用户级缓冲区的内容根本来不急将内容刷新到文件内核缓冲区了。

        当然,如果我们使用C语言提供的fclose函数,在关闭的文件的时候会自动刷新一下用户级缓冲区,或者使用fflush向上面的例子一样在调用系统调用之前手动刷新一下用户级缓冲区。

        

fsync

        将文件内核缓冲区的内容直接刷新到外设文件中。

        因此重定向的过程实际上就是对文件描述符指向的替换,递增的文件描述符指向变化了,但是上层不知道因此使用相同的文件描述符时操作的文件却不同

        ​​​​​​​        ​​​​​​​

        这里还有一个后面有用的小细节,就是当描述符指向替换过后原指向还是有效的,也就是说一个文件是可以同时被多个指针指向的,这个东西后面进程间通讯是有用的。

5.1 dup2 文件重定向

        前面那种手动重定向的方案还是太麻烦了,系统调用中提供了重定向函数man dup2直接查看

        ​​​​​​​        

        这个函数就是将oldfd指向的文件重定向到newfd的位置。

        ​​​​​​​                

        ​​​​​​​        

        dup2重定向之后

        ​​​​​​​        

        ​​​​​​​        

        看这里打印的fd值也可以印证我们前面说的一共文件被两个指针同时指向,之前是3号指针指向log.txt文件,但是我们dup2重定向了之后1号指针也指向了这个文件,但是3号指针的指向并没有被抹除。

        最后我们明确两点,第一重定向应该让子进程自己做,第二程序替换不会影响重定向,因为它们在进程PCB中属不同的模块,互不影响。

5.2 fsync 刷新文件内核缓冲区

        我们知道fflush函数可以将用户级缓冲区的内容手动刷新到文件内核级缓冲区中。同理系统调用 fsync 函数可以将文件内核级缓冲区的内容刷新到文件中去。

        参数很简单,就是文件描述符fd

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

硬件产品经理的开店冒险之旅(下篇)

缘起&#xff1a;自己为何想要去寻找职业第二曲线 承接上篇的内容&#xff0c;一名工作13年的普通硬件产品经理将尝试探索第二职业曲线。根本原因不是出于什么高大上的人生追求或者什么职业理想主义&#xff0c;就是限于目前的整体就业形式到了40岁的IT从业人员基本不可能在岗…

【Python】selenium遇到“InvalidArgumentException”的解决方法

在使用try……except 的时候捕获到这个错误&#xff1a; InvalidArgumentException: invalid argument (Session info: chrome112.0.5614.0) 这个错误代表的是&#xff0c;当传入的参数不符合期望时&#xff0c;就会抛出这个异常&#xff1a; InvalidArgumentException: invali…

day-69 使二进制数组全部等于 1 的最少操作次数 II

思路 与3191. 使二进制数组全部等于 1 的最少操作次数 I思路类似&#xff0c;区别在于该题每次将下标i开始一直到数组末尾所有元素反转&#xff0c;所以我们用一个变量可以统计翻转次数 解题过程 从左向右遍历数组的过程中&#xff0c;有两种情况需要进行翻转&#xff1a;1.当…

多媒体(4)

PNG PNG&#xff08;流式网络图像&#xff09;文件采用【无损压缩】算法&#xff0c;压缩比高于GIF文件&#xff0c;支持 图像透明 PNG文件的色彩深度可以是灰度图像的16位&#xff0c;彩色图像的48位&#xff0c;是一种新兴的 网络图像格式 矢量图 矢量图是一组指令集合描述图…

Sentinel 介绍

随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开…

芯知识 | NVH-FLASH语音芯片支持平台做语音—打造音频IC技术革新

随着科技的飞速发展&#xff0c;人们对于电子产品的音频性能要求越来越高。在这种背景下&#xff0c;NVH-FLASH系列语音芯片应运而生&#xff0c;作为音频IC领域的一次重大技术革新&#xff0c;NVH-FLASH系列语音芯片凭借其卓越的性能与灵活的支持平台&#xff0c;正逐步引领着…

Linux——网络层协议

前言 网络层&#xff1a;在复杂的网络环境中确定一个合适的路径 目录 前言 一IP协议 1预备知识 2基本概念 3格式 4网段划分 4.1理解IP 4.2IP组成 4.3划分方式 4.4为什么要网段划分 5特殊的IP地址 6IP地址的限制 7私有IP和公网IP 8NAT技术 9理解公网 10路由 …

使用RNN、LSTM和Transformer进行时间序列预测

文章目录 1 RNN & LSTMRNN结构LSTM结构样本和标签 2 Transformertransformer结构位置编码 1 RNN & LSTM RNN结构 LSTM结构 代码&#xff08;使用CPU&#xff09;&#xff1a; import numpy as np import torch from matplotlib import pyplot as plt from torch impo…

SQLite 上手指南 -- 基础语法

目录 一、数据库操作1.1 新建数据库1.2 查看数据库1.3 查看帮助指令 二、表操作2.1 创建表2.2 表信息2.3 表索引信息2.4 表结构信息2.5 删除表 三、数据记录操作3.1 新增记录3.2 查看记录3.3 不同格式输出 四、运算符4.1 算术运算符4.2 比较运算符4.3 逻辑运算符4.4 位运算符 S…

【热门】用ChatGPT做智慧农业云平台——农业ERP管控系统

随着科技的进步,原有农业种植方式已经不能满足社会发展的需要,必须对传统的农业进行技术更新和改造。经过多年的实践,人们总结出一种新的种植方法——温室农业,即“用人工设施控制环境因素,使作物获得最适宜的生长条件,从而延长生产季节,获得最佳的产出”。这种农业生产方式…

Linux安装 php5.6

Linux安装 php5.6.30 下载-解压-配置-安装 下载到 /usr/local wget http://am1.php.net/distributions/php-5.6.30.tar.gztar -zxvf php-5.6.30.tar.gz cd php-5.6.30#编译配置 ./configure --prefix/usr/local/php --with-curl/usr/local/curl --with-freetype-dir --wit…

无mac电脑在苹果开发者上传构建版本

我们登录苹果开发者网站的后台&#xff0c;进入app store后&#xff0c;发现上架的页面需要上传一个构建版本。 这个构建版本的意思就是我们的应用二进制文件&#xff0c;是上架最重要的文件。但是在苹果开发者后台是无法直接上传这个文件的&#xff0c;它提示我们可以使用xco…

R语言机器学习教程大纲

文章目录 介绍机器学习算法监督学习Supervised Learning分类Classification回归Regression 无监督学习 Unsupervised Learning聚类 Clustering降纬 Dimensionality Reduction相关Association 强化学习Reinforcement Learning模型自由 Model-Free Methods模型驱动 Model-Based M…

服务器托管的优缺点有哪些?

由于数字化程度不断提高&#xff0c;服务器在日常业务中发挥着越来越重要的作用。在大多数情况下&#xff0c;服务器由公司自己维护和管理。但对于一些公司来说&#xff0c;托管服务器(将这些任务交给专业人员)是更好的选择。 关于服务器的优缺点&#xff0c;有一点是明确的&am…

【SpringBoot】16 文件上传(Thymeleaf + MySQL)

Gitee仓库 https://gitee.com/Lin_DH/system 介绍 文件上传是指将本地的图片、视频、音频等文件上传到服务器&#xff0c;供其他用户浏览下载的过程&#xff0c;文件上传在日常项目中用的非常广泛。 实现代码 第一步&#xff1a;在配置文件新增如下配置 application.yml s…

浏览器实时更新esp32-c3 Supermini http server 数据

一利用此程序的思路就可以用浏览器显示esp32 采集的各种传感器的数据&#xff0c;也可以去控制各种传感器。省去编写针对各系统的app. 图片 1.浏览器每隔1秒更新一次数据 2.现在更新的是开机数据&#xff0c;运用此程序&#xff0c;可以实时显示各种传感器的实时数据 3.es…

鸿蒙网络编程系列27-HTTPS服务端证书的四种校验方式示例

1. 服务端数字证书验证的问题 在鸿蒙客户端对服务端发起HTTPS请求时&#xff0c;如果使用HttpRequest的request发起请求&#xff0c;那么就存在服务端数字证书的验证问题&#xff0c;你只有两个选择&#xff0c;一个是使用系统的CA&#xff0c;一个是使用自己选定的CA&#xf…

C++初阶

目录 一.命名空间 1.命名空间定义 2.命名空间使用 二.C输入&输出 三.缺省参数 四. 函数重载 五.引用 1.常引用 2.传值、传引用效率比较 3.引用和指针的区别 4.引用和指针的不同点: 小知识点: 六.内联函数 七.auto关键字(C11) 1.auto的使用细则 八.基于范围…

【Spring声明式事务失效的12种场景测试】

文章目录 一.Spring声明式事务是什么&#xff1f;二.Spring事务失效的12种场景1.访问权限问题 小结 一.Spring声明式事务是什么&#xff1f; Spring声明式事务是一种通过配置的方式管理事务的方法&#xff0c;它通过注解或XML配置来声明哪些方法需要事务管理&#xff0c;从而将…

杨氏矩阵(有一个数字矩阵,矩阵的每行从左到右的递增的,矩阵从上到下是递增的请编写一个程序,在这样的矩阵中查找某个数字是否存在)

//杨氏矩阵 //有一个数字矩阵&#xff0c;矩阵的每行从左到右的递增的&#xff0c;矩阵从上到下是递增的 //请编写一个程序&#xff0c;在这样的矩阵中查找某个数字是否存在 // 1 2 3 // 4 5 6 // 7 8 9 #include<stdio.h> int main() {int a[3][3] { 0 };int i 0, j …