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.mzph.cn/news/882778.shtml

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

相关文章

基于STM32的Android控制智能家政机器人

基于STM32的Android控制智能家政机器人 基于STM32的Android控制智能家政机器人一、项目背景与意义二、系统设计方案三、硬件电路设计四、软件设计与实现4.1 Android端软件设计4.2 机器人端软件设计 五、系统调试与测试六、结论与展望七、附录 基于STM32的Android控制智能家政机…

从外行人的角度解释1Bit的模型,是怎样改变世界的

一个框架&#xff0c;和一篇论文&#xff0c;改变了模型训练的规则 框架是BitNET 论文https://arxiv.org/abs/2410.16144 有人问我什么是1.58Bit 是这样的。 fp16是一般情况下模型训练后产物的精度。 比如qwen2 8B fp16&#xff0c;文件大小15GB 如果量化成Q_4O&#xff…

24下河南秋季教资认定保姆级教程

教师资格认定前需要做的准备材料 准备身份证户口本 居住证 学生证 教师考试合格证明 普通话证书 学历证书 体检合格证书 近期一寸白底证件照 网上报名 河南24下教资认定 网上报名时间&#xff1a;10月21日-11月1日 现场确认 网上审核未通过的宝子&#xff0c;需要…

html+css+js实现Notification 通知

实现效果&#xff1a; 代码实现&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Notif…

k8s use-context是什么

kubectl 的 use-context 命令用于在 Kubernetes 集群中切换上下文&#xff08;context&#xff09;&#xff0c;从而方便地在多个集群或命名空间之间进行操作。一个上下文定义了 kubectl 使用的 集群、用户 和 命名空间 的组合。 use-context 的作用&#xff1a; 每个上下文&…

AOP学习

corol调用serverce不在是直接调用的是调用底层代理对象&#xff0c;由代理对象统一帮我们处理 AOP常见概念 通知类型 切面顺序

【C++】— 一篇文章让你认识STL

文章目录 &#x1f335;1.什么是STL&#xff1f;&#x1f335;2.STL的版本&#x1f335;3.STL的六大组件&#x1f335;4.STL的重要性&#x1f335;5. 如何学习STL&#x1f335;6. 学习STL的三种境界 &#x1f335;1.什么是STL&#xff1f; STL是Standard Template Library的简称…

Matlab软件进行金融时间序列数据的描述性统计代码

1、数据S&P500的收盘价格&#xff0c;return100*log(pt/pt-1) 方法1&#xff1a;用python代码 import numpy as np import pandas as pddef calculate_log_returns(prices):"""计算价格序列的对数收益率。参数:prices (numpy.array): 价格序列。返回:log_…

【实战指南】Vue.js 介绍组件数据绑定路由构建高效前端应用

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

ChatGPT 现已登陆 Windows 平台

今天&#xff0c;OpenAI 宣布其人工智能聊天机器人平台 ChatGPT 已开始预览专用 Windows 应用程序。OpenAI 表示&#xff0c;该应用目前仅适用于 ChatGPT Plus、Team、Enterprise 和 Edu 用户&#xff0c;是一个早期版本&#xff0c;将在今年晚些时候推出"完整体验"。…

LeetCode 热题100之哈希

1.两数之和 思路分析1&#xff08;暴力法&#xff09; 双重循环枚举满足num[i] nums[j] target的索引&#xff0c;刚开始不知道如何返回一对索引。后来知道可以直接通过return {i,j}返回索引&#xff1b;注意&#xff1a;j应该从i1处开始&#xff0c;避免使用两次相同的元素…

liunx线程

线程的概念 程序中的一个执行路线就是线程&#xff0c;线程就是一个进程内部的控制序列一个进程至少都有一个执行线程线程在进程内部运行&#xff0c;本质是在进程地址空间内运行liunx系统下&#xff0c;cpu眼里的PCB比传统进程更加轻量化透过虚拟地址空间&#xff0c;把进程的…

展会亮点回顾|HMS汽车工业通信解决方案

2024 汽车测试及质量监控博览会&#xff08;中国&#xff09;&#xff08;Testing Expo China – Automotive&#xff09;于 8 月 28 日至 30 日在上海世博展览馆顺利举行。作为汽车测试技术领域的顶级盛会&#xff0c;来自全球的行业领袖和技术专家齐聚一堂&#xff0c;共同探…

即时通讯 离线消息处理初版

离线消息处理 NotOnlineExecute package com.example.im.infra.executor.send;import com.example.im.endpoint.WebSocketEndpoint; import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springf…

Django学习(三)

Django的设计模式及模板层 传统的MVC&#xff08;例如java&#xff09; Django的MTV 模板层&#xff1a; 模板加载&#xff1a; 代码&#xff1a; views.py def test_html(request):#方案一# from django.template import loader# 1. 使用loader加载模板# t loader.get_…

ICP算法理解入门之RT求解

本文仅仅分析了一下两组点云做好匹配好了之后&#xff0c; 如何解算RT&#xff0c; 并不涉及匹配过程&#xff0c;详细的匹配&#xff0c;下次再出一篇博客 ICP 求解旋转矩阵 ( R ) 的步骤 给定两个点云集合 ( P {p_1, p_2, …, p_n} )&#xff08;源点云&#xff09;和 ( Q…

uniapp-uniapp + vue3 + pinia 搭建uniapp模板

使用技术 ⚡️uni-app, Vue3, Vite, pnpm &#x1f4e6; 组件自动化引入 &#x1f34d; 使用 Pinia 的状态管理 &#x1f3a8; tailwindcss - 高性能且极具灵活性的即时原子化 CSS 引擎 &#x1f603; 各种图标集为你所用 &#x1f525; 使用 新的 <script setup> …

多ip访问多网站

作业要求 配置nginx服务通过ip访问多网站 [rootlocalhost ~]# systemctl stop firewalledFailed to stop firewalled.service: Unit firewalled.service not loaded. [rootlocalhost ~]# mount /dev/sr0 /mnt mount: /mnt: /dev/sr0 已挂载于 /run/media/redhat/RHEL-9-3-0-B…

云原生技术:nacos进化到servicemash

面试的时候跟面试官吹嘘说&#xff0c;现在主流的微服务架构&#xff0c;都已经用得熟熟的了&#xff0c;自己技术很不错。进了公司却被分到了API资产管理平台&#xff0c;要做一个类似于网关的东西。经过调研才发现&#xff0c;自己用的微服务架构已经过时了&#xff0c;什么&…

Spring配置/管理bean-IOC(控制反转) 非常详细!基于XML及其注解!案例分析! 建议复习收藏!

目录 1.Spring配置/管理bean介绍 2.基于XML配置bean 2.1基于id来获取bean对象 2.2基于类型获取bean对象 2.3通过指定构造器配置bean对象 2.4通过p名称空间配置bean 2.5通过ref配置bean(实现依赖注入) 2.6注入内部Bean对象&#xff0c;依赖注入另一种方式 2.7 注入集合…