一、理解文件

抛一个概念:

文件 = 内容 + 属性

1. 那么,空文件有大小吗?答案是有的。因为空文件指的是文件内容为空,文件属性也要占据大小啊。

将来对文件操作,无非分为两类:

1.对文件内容做修改

2.对文件属性做修改

2. 在学习C语言时,我们访问一个文件,都要先把对应的文件打开,为什么?

因为要访问文件的内容和属性,内容和属性都是数据,所谓的访问就是对文件进行增删查改,都是由CPU执行你的代码进行访问,根据冯诺依曼体系结构,要对文件进行操作,你的文件必须在物理内存里,所以,我们把文件打开本质上是把文件加载到内存中

3. 文件有很多啊,被打开的文件在物理内存上,如果一个文件没有被打开呢?没有被打开的文件就在磁盘上。

所以,将来学习文件就被分为两种:被打开的文件和没有被打开的文件(文件系统)

4. 谁打开的文件?

CPU在调度这个进程时,执行到 fopen 的时候就打开文件。文件是在磁盘上的,磁盘是硬件,谁能去访问磁盘呢?是操作系统。所以,是用户通过bash,启动进程,进程通过操作系统打开文件的

那么,这时候就有人说了,我用的是C语言的库函数啊,没调用系统调用怎么访问的操作系统。是因为库函数底层对系统调用进行了封装

5. 一个进程可以打开多个文件吗?答案是可以的。那如果有多个进程呢?

所以,OS内一定同时存在大量被打开的文件,OS要不要对这些文件进行管理呢肯定是要的。怎么管理?先描述在组织

所以,OS一定存在一种数据结构体,描述被打开的文件,如同PCB一样

6. 进程有 task_struct ,未来进程也有打开的文件,我们平时研究打开文件,是在研究什么?

本质是研究进程与文件的关系

二、回顾C文件接口

1. 回顾C接口

//pathname (路径)+文件名
//mode 打开模式
//成功返回文件指针,失败返回NULL
FILE* fopen(const char* pathname, const char* mode);//打开文件
//关闭文件,成功返回0,失败返回EOF
int fclose(FILE* stream);
mode:
r:以读的方式打开文件,定位在文件的开始
r+:以读写的方式打开文件,定位在文件的开始
w:以写的方式打开文件,文件不存在就创建文件,或者清空文件,定位在文件开始
w+:以读写的方式打开文件,文件不存在就创建文件,反之清空文件,文件指针定位在文件开始
a:以追加(写)的方式打开文件,文件不存在就创建文件,定位在文件末尾
a+:以读写的方式打开文件,文件不存在就创建文件,写入时,在文件末尾,读取时,文件开始。

. 以 w 模式打开文件

在这里插入图片描述

. 以 a 模式打开文件

在这里插入图片描述

剩下的就不再枚举了,大家可以自己验证一下。

2. 文件读取是有读取位置的,那么怎么理解这个呢?

所谓的文件我们可以把它看做成一个“一维数组”,文件的位置不就是数组下标吗

这时候就有人不理解了,文件是怎么被看做是一个一维数组的

我们可以把文件里的内容看做是一个长字符串,只不过这个长字符串里面有许多换行符而已。这样应该理解了吧。

接下来看下面的现象,又是怎么回事呢?

在这里插入图片描述

这是怎么回事呢?当我们向文件里写入内容时,肯定要先打开文件,当识别到 > 符号时,就会以 w 的方式打开文件,所以就可以进行写入或者清空文件了

在这里插入图片描述

相同的道理,>> 符号就会以 a 的方式打开文件,在文件末尾进行追加数据

补充:

向显示器写入12345,是写入了一个int 12345 还是向显示器写入了 ‘1’,‘2’,‘3’,‘4’,‘5’?

通过键盘输入12345,输入了一个 ‘1’,‘2’,‘3’,‘4’,‘5’,还是输入了 int 12345?

答案是输入了一个个字符。所以显示器和键盘也叫字符设备

int x = 100 , printf(“%d”,x); ,int 占4个字节,%d表明是一个整数,所以 printf 在向显示器打印的时候会将数据拆分成一个个字符,输出到显示器上,所以 printf 也叫格式化输出。同理,scanf 函数从字符设备上一个个读取字符,所以也叫做格式化输入

那么,显示器和键盘是文件吗当然是文件。它和我们在软件层上创建的文件获取数据和输出数据是类似的,它就是一个文本文件

那么什么是二进制文件呢?

以二进制形式存储数据的文件

我们在C语言中学习的stdin,stdout,strerr是文件吗当然也是了

在这里插入图片描述

它们都是FILE* 的文件指针。我们常说进程在启动的时候会默认打开这三个文件流(其实是打开三个文件),这是为什么呢

大部分进程是需要使用CPU资源进行计算的,而计算就需要数据,计算结果有时候也是需要输出的,也有可能计算错误。所以为了方便起见,进程会默认打开对应的文件

看下面的几个函数。

在这里插入图片描述

这说明了什么呢?本质向显示器打印,就是向stdout中写入,就如同向文件写入,因为stdout也是FILE*

文件是在磁盘上的,而打开文件就需要将文件加载到内存里,只有操作系统才可以,访问操作系统就必须经过系统调用。所以接下来,我们就来看看打开文件的系统调用。

3. 系统调用

//pathname 文件路径+文件名
//flags 打开文件的方式
int open(const char* pathname, int flags);
//mode 文件的权限
int open(const char* pathname, int flags, mode_t mode);

open可以打开文件,如果文件不存在,是否会创建,取决于标志位(flags)

在这里插入图片描述

flags(标志位)有很多,我们列举几个最常用的。

. O_APPEND 追加,文件不存在也会创建

. O_CREAT 创建文件

. O_RDONLY 只读方式打开文件

. O_WRONLY 只写方式打开文件

. O_RDWR 读写方式打开文件

. O_TRUNC 清空文件

在这里插入图片描述

可以看到,O_WRONLY是不会创建文件的

在这里插入图片描述

O_CREAT会创建文件

在这里插入图片描述

但是,创建的文件权限怎么是乱码的呢?这是需要你自己手动设置的。

在这里插入图片描述
在这里插入图片描述

但是,文件的权限怎么是644呢还记得umask吗?就是因为它。每个系统的umask可能不一样。

我们也可以调用系统调用来设置umask,并且不会影响系统的umask。按就近原则执行umask

在这里插入图片描述

剩下的选项就不再做详细介绍了。现在,就来聊聊 open 系统调用的返回值吧

在这里插入图片描述

4. 理解文件描述符

可以看到,open 的返回值文件描述符是一个个整数它是什么呢

一个进程是可以打开多个文件的,OS内一定有大量的文件被打开,这些文件也是要被管理的

在OS内,如何描述被打开的文件呢?struct file这个结构体内一定直接或间接的包含被打开文件的内容和属性,以双链表的形式进行管理

自此以后,对文件进行管理就转变为对链表的增删查改

文件是由进程通过OS打开的所以文件与进程之间也是有着密不可分的联系

一个进程可以打开多个文件,多个进程也可以打开多个文件,那么,被打开的文件是属于哪个进程呢

在进程PCB里有一个结构体指针struct file_struct* files,它指向一个struct file-struct(文件描述符表)的结构体,这个结构体内有一个成员struct file* fd_array[],这是一个指针数组,指向struct file。打开文件时,OS分配 struct file 结构体,链入到struct file的链表中,将新申请的struct file结构体的地址填入到fd_array数组中,给用户返回数组下标

总结文件描述符的本质就是数组下标

也就是说,在OS角度,识别打开的文件,只认int fd 文件描述符

在这里插入图片描述

那么,数组下标 0,1,2去哪里了呢?我们说进程默认打开了三个文件流:stdin, stdout, stderr,这不刚好三个吗?没错,0,1,2就是分别给它们三个文件流了

那这时候有人就有疑问了,在学习C语言的时候,我们使用文件接口并没有用到文件描述符 fd 呀,不是说OS只认 fd 吗?C语言的文件接口返回类型是FILE*呀。

那么,FILE是什么呢?它其实就是C语言标准库定义的一个结构体

所以,推测,FILE 结构体里面,一定要封装一个整数,这个整数就是 fd

现在看来,封装,不仅仅是对于系统调用接口的封装,连数据类型也做了封装

在这里插入图片描述

打开文件时,OS只给我们做了上述的工作吗?当然不是了。它还要把磁盘上的文件加载到内存里

文件 = 属性 + 内容OS会给文件属性开辟一段空间,给文件内容开辟一段空间(文件缓冲区)。而struct file 结构体里会间接的找到它们

在这里插入图片描述

我们在使用 write(3, "hello world") 系统调用的时候,是在干什么呢

进程会找到文件描述符表,拿着3号文件描述符找到对应的文件,将数据拷贝到文件内核缓冲区里

所以:write的本质根本就不是写入到文件里,write的本质是拷贝函数,把数据从用户空间拷贝到对应文件的内核缓冲区中

那如果读取文件呢只能从文件缓冲区里面读取

修改文件呢从内核缓冲区里拷贝数据到用户缓冲区,进行修改,再拷贝到内核缓冲区,刷新到磁盘上

所以,我们对任何文件内容进行增删查改,都必须把文件的内容提前预加载到该文件的内核缓冲区中

5. 文件描述符的分配规则

在这里插入图片描述

我们关闭了文件描述符 0 所指向的文件,所以OS给我们分配了 0,那么如果关闭2号文件呢?我们来验证一下。

在这里插入图片描述

由此可见,文件描述符的分配规则是给新打开的文件分配 fd ,从文件描述符表数组中寻找,最小的,没有被使用的数组下标,作为该文件的 fd

那么,有没有人疑惑呢?为什么跳过了 1 号描述符呢

我们来试一下。

在这里插入图片描述

没有打印,这是为什么呢?因为1号文件描述符指向的文件是 stdout ,我们把它关闭了,然后创建了 log.txt 文件,1号文件描述符就被分配给了 log.txt文件,所以就无法向显示器打印了那么,既然 1 号文件描述符分配给了 log.txt,数据是不是在 log.txt 里呢

在这里插入图片描述

可以看到,是没有的,那是怎么回事呢?我们对代码做出些许改动,看看结果。

在这里插入图片描述

现在,我们换一种方式检验结果。

在这里插入图片描述

我们用了两种方式,表达的都是同一个问题。这是为什么呢?

这与缓冲区有关。我们需要等到后面才能解释。

一般,我们不采用这种关闭某个文件,打开另一个文件的做法。所以,接下来,我们需要先了解一个系统调用。

//用于复制文件描述符
//oldfd文件描述符复制到newfd文件描述符上
//如果newfd已经打开,dup2会先关闭它,然后再进行复制。
//成功,返回新的文件描述符newfd
//失败,返回-1,并设置errno
int dup2(int oldfd, int newfd);

利用这个系统调用,我们可以写一个输出重定向的代码。

在这里插入图片描述

有眼尖的伙伴就发现了,它打印的顺序怎么和我们代码所写的顺序不一样呢?这也是因为缓冲区的缘故。

6. 如果我们创建子进程,子进程是如何看待父进程打开的文件的?

父进程创建子进程,OS会给子进程分配PCB,虚拟地址空间,页表...,当然了,也包括今天的文件描述符表(struct file_struct)

PCB,,虚拟地址空间,页表是以父进程为模板的,文件描述符表当然也没有例外,也就是说,父进程打开的文件,子进程也会打开,那么,OS会重新再加载一份文件吗当然不会了。我们是创建了一个新的进程,又不是打开了新的文件。

那么,子进程以父进程为模板,文件描述符表中的struct file* fd_array[]就是浅拷贝,也就意味着子进程和父进程指向的是同一个文件

不知道大家看到这里有没有问题呢?

既然子进程和父进程指向的是同一个文件,那如果子进程关闭了某些文件,是不是父进程也无法使用呢

当然不会了进程是具有独立性的。父子进程指向同一个文件,为了保证进程的独立性,OS采用了引用计数的方法,子进程关闭了某个文件,OS就对计数做 - - 操作。直到计数为0,就会关闭该文件

我们常说进程默认会打开 stdin, stdout, stderr 这三个文件流,我们在命令行上启动的进程,我们是没有打开这几个文件流的。因此,进程都是通过父进程继承来的

我们可以验证一下。

在这里插入图片描述

7. 如果程序替换,不会创建新进程,会影响我们历史打开的文件吗?

答案是不会

程序替换,加载的是新的代码和数据,跟文件有什么关系。所以,程序替换不会影响文件

在这里插入图片描述

今天的文章分享到此结束,觉得不错的给个一键三连吧。

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

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

相关文章

优化算法专栏——阅读导引

前言 提醒: 文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。 其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展…

[ The Missing Semester of Your CS Education ] 学习笔记 Vim篇

“Writing English words and writing code are very different activities. When programming, you spend more time switching files, reading, navigating, and editing code compared to writing a long stream.” —— < The Missing Semester of Your CS Education &g…

Linux 系统中定时执行指定命令 crontab 定时任务配置

crontab 定时任务配置是 Linux/Unix 系统中用于自动、周期性执行指定命令或脚本的工具&#xff0c;相当于系统的 “定时闹钟”。它可以让系统在预设的时间&#xff08;如每天凌晨、每周一、每月 1 号等&#xff09;自动完成重复性工作&#xff0c;无需人工干预。自动化运维定期…

[ Leetcode ]---快乐数

题目链接 Leetcode快乐数 题目描述 如下图&#xff1a; 题目解析&#xff1a; 1.双指针法 算法核心思路 判断快乐数的关键挑战是如何检测是否进入无限循环。这里使用了快慢指针法&#xff08;Floyd 循环检测算法&#xff09;&#xff0c;这是一种高效检测循环的技巧&#…

智慧社区构建——2

1.实现Token校验## Token校验URLjson GET /checkToken 参数json HttpServletRequest request 返回json {"msg": "操作成功","code": 200,"status": "ok" }{"msg": "操作成功","code": 200,&q…

K-Means聚类:当数据没有标签时,如何让计算机自动“物以类聚”?

K-Means聚类&#xff1a;当数据没有标签时&#xff0c;如何让计算机自动“物以类聚”&#xff1f;&#x1f44b; 大家好&#xff0c;我是小瑞瑞&#xff01;欢迎回到我的专栏&#xff01; 在我们之前的旅程中&#xff0c;解决的问题大多都有一个明确的“目标”&#xff0c;比如…

万事皆可用 GeeLark AI

在今年4月&#xff0c;GeeLark AI 全面接入 DeepSeek AI 大模型&#xff0c;你可以在独立窗口中便捷地使用 GeeLark AI。除了帮助你编写文案等基础内容&#xff0c;在使用 GeeLark 过程中&#xff0c;如果遇到问题&#xff0c;也可以通过询问 GeeLark AI&#xff0c;及时获取帮…

3D 高保真处理:声网让游戏声音随角色动作变化

传统游戏的声音体验像老式收音机&#xff0c;不管声源位置、距离和障碍物&#xff0c;仅靠左右声道机械调音量&#xff0c;毫无方向感和空间感&#xff0c;如同蒙眼听声辨位。射击游戏中敌人从左边来&#xff0c;耳机却两边同响且音量相近&#xff0c;让人晕头转向&#xff1b;…

Nestjs框架: 请求生命周期与应用生命周期

概述 在 NestJS 框架中&#xff0c;中间件&#xff08;Middleware&#xff09;、管道&#xff08;Pipes&#xff09;、过滤器&#xff08;Filters&#xff09;、拦截器&#xff08;Interceptors&#xff09; 均属于请求处理流程的核心组件&#xff0c;它们共同构成了 NestJS 的…

Nastool+cpolar:群晖NAS用户的全场景影音自由方案

文章目录前言1. 本地搭建Nastool2. nastool基础设置3. 群晖NAS安装内网穿透工具4. 配置公网地址小结5. 配置固定公网地址**第二版&#xff1a;技术整合与效率提升导向****第二版&#xff1a;技术整合与效率提升导向****第二版&#xff1a;技术整合与效率提升导向**Nastool与cpo…

从零开始:Kaggle 竞赛实战入门指南

一、Kaggle社区概述 Kaggle 是全球最大的数据科学和机器学习社区&#xff0c;由Anthony Goldbloom于2010年创立&#xff0c;2017年被Google收购。平台专注于数据科学竞赛、开源数据集共享、协作编程以及技能学习&#xff0c;吸引了从初学者到专业数据科学家的广泛用户群体。 …

sqli-labs:Less-16关卡详细解析

1. 思路&#x1f680; 本关的SQL语句为&#xff1a; $uname".$uname."; $passwd".$passwd."; $sql"SELECT username, password FROM users WHERE username($uname) and password($passwd) LIMIT 0,1";注入类型&#xff1a;字符串型&#xff08;…

Lipschitz连续函数

Lipschitz function 一、说明 在数学分析中&#xff0c;Lipschitz连续性以德国 数学家 鲁道夫利普希茨 (Rudolf Lipschitz)的名字命名&#xff0c;是函数一致连续性的强形式。直观地说&#xff0c;Lipschitz连续函数的变化速度有限&#xff1a;存在一个实数&#xff0c;使得对于…

Dynamics 365 business central 与Shopify集成

Dynamics 365 Business Central&#xff08;简称 D365 BC&#xff09; 与 Shopify 的集成&#xff0c;能帮助企业实现前端电商平台&#xff08;Shopify&#xff09;与后端 ERP 系统&#xff08;Business Central&#xff09;之间的无缝数据同步&#xff0c;是一种典型的 ERP 与…

TCP RTO 与丢包检测

TCP RTO 是它 40 多年前唯一丢包检测策略&#xff0c;也是当前最后的丢包检测兜底策略&#xff0c;它几乎从没变过。 有个咨询挺有趣&#xff0c;以其案例为背景写篇随笔。大致意思是&#xff0c;嫌 TCP RTO 太大&#xff0c;游戏场景丢包卡顿怎么办&#xff1f;我提供了几行代…

安装php和配置环境变量

为了简单方便&#xff0c;先下载vscode然后下载对应的php安装包&#xff0c;然后配置环境变量&#xff0c;然后点击运行即可下载对应版本的php&#xff0c;这个版本凑合用然后下载完之后解压配置环境变量搜索环境变量将路径添加到环境变量中然后打开vscode添加变量具体看实际路…

Rabbit MQ的消息模式-Java原生代码

一.简单模式1.1.核心逻辑生产者 → 队列 → 单个消费者&#xff08;1:1 直连&#xff09;&#xff0c;消息被消费后自动从队列删除。1.2.关键特性无交换器&#xff08;其实使用的是默认交换机不是显示指定&#xff09;&#xff0c;直接指定队列 消息默认自动确认&#xff08;au…

【lucene】使用docvalues的案例

下面给出一段 可直接跑通 的 Lucene 8.5.0 示例代码&#xff0c;演示如何1. 建索引时为两个字段启用 DocValues&#xff08;一个 NumericDocValues&#xff0c;一个 SortedDocValues&#xff09;&#xff1b; 2. 用 IndexSearcher 按 DocValues 排序&#xff1b; 3. 用 Facet…

IntelliJ IDEA 配置 Maven 阿里云镜像加速源全流程

1. 为什么要加国内镜像源&#xff1f;国内网络访问 Maven 中央仓库经常超时、依赖下载极慢或失败。配置阿里云等国内镜像后&#xff0c;Java 项目依赖下载飞快&#xff0c;极大提升开发效率&#xff0c;是中国开发者必做优化&#xff01;2. 添加阿里云镜像源的步骤&#xff08;…

【worklist】worklist的hl7、dicom是什么关系

HL7和DICOM在Worklist系统中是互补的关系&#xff0c;它们各自承担不同的角色&#xff0c;但协同工作以实现完整的医疗信息系统集成。HL7与DICOM Worklist的关系1. 功能分工DICOM Worklist (Modality Worklist - MWL)主要用于影像设备获取患者和检查信息基于DICOM协议&#xff…