Linux——进程中被打开的文件

	C语言中有着许多对文件操作的函数,包括其他语言也有,但是从语言来了解文件有点浅显
	计算机的一切都离不开操作系统,那么文件跟操作系统也有着密切的关系,所以我们从系统
	层面来了解文件(在进程中打开的文件)

文章目录

  • 1.系统中被打开文件的认识
  • 2.C语言中的文件接口
    • a. fopen/fclose
    • b.fwrite/fread
    • c. 部分演示
  • 3. 系统调用接口open/close
    • a. open的参数和返回值
      • path和返回值
      • 标志位
      • mode
    • b. open的返回值fd再认识
    • c. 标准输入输出
    • d. 一切皆文件
  • 4.对文件结构体认识
  • 5. fd的再再认识
    • a. 追加重定向
    • b. 输入重定向
    • c. 问题的引入

1.系统中被打开文件的认识

  我们首先要知道文件 = 文件内容 + 文件属性
  其次为什么要从系统来了解文件?我们使用编程语言写好的程序也能打开文件对文件的数据进行修改。但是我们要知道,文件刚开始是在磁盘里的,语言写好的程序跑起来之后是一个进程,一个进程要打开文件,他能直接打开吗?很显然是不能的,因为文件没被打开前一般是在磁盘里的,而cpu只跟内存打交道,所以要打开一个文件必须要将它加载到内存中,那么谁来管理磁盘跟内存的交互呢?那肯定是操作系统。所以从语言来了解文件是不够本质的。并且从这里也就把文件分成两类:打开的文件和未打开的文件。而我们研究被打开的文件,也就是内存中的文件。
  再想一个问题,一个进程能带开多个文件吗?多个进程能打开多个文件吗?答案是可以的,Linux中万物皆文件,在一个进程跑起来的时候会默认打开三个流:标准输入、标准输出、标准错误。而这三个其中标准输入是键盘,标准输出和标准错误是显示器,而计算机中肯定会有着许多的进程。关于这些详细内容先不细谈,目前只要知道这三个流其实也是文件就ok了,所以一个进程可以打开多个文件,多个进程也可以打开多个文件。进程与打开的文件是1:n的关系
  这么说来,内存中会存在多个被打开的文件,而哪些文件被哪些进程打开了这些都需要被管理,所以文件需要被管理,操作系统管理的方法就是先描述,再组织,所以当一个文件要被打开时,除了要将文件加载到内存中,还要用文件结构体将文件描述好,这个结构体里面有着文件的信息可能有磁盘中的存储位置、文件名等等。然后再用数据结构比如链表将它们管理起来。也说明进程打开文件也是凭借操作系统来打开的,那么操作系统一定要封装系统调用才能让进程打开文件

2.C语言中的文件接口

  在这里我们只认识部分函数

a. fopen/fclose

  这两个是成对出现的,而fclose就是关闭一个文件,我们主要看fopen:
在这里插入图片描述
  它是一个库函数,它的参数第一个是文件的路径(这个路径可以是相对路径也可以是绝对路径),第二个是文件被打开的模式。它的返回值是一个库里封装的FILE的指针,这个指针会指向被打开的文件,如果打开失败会返回空值针。
  我们再看看文件可以有哪几种打开方式:

在这里插入图片描述

r模式:以只读方式打开文件,并且文件被指向的位置是文件内容的开头
r+模式:以读写方式打开问价,并且文件被指向的位置也是文件内容的开头
w模式:以读写的方式打开文件,在打开的时候会先将文件内容清空,然后指向文件内容的开始
w+模式:以读写的方式打开文件,在打开的时候会先将文件内容清空,然后指向文件内容的开始,当文件不存在的时候会创建文件
a模式:以追加(其实也是写)的方式打开文件,但是不会将文件内容清空,而且指向文件内容的末尾,当文件不存在时创建文件
a+模式:以读和追加的方式打开文件,不会清空文件内容,当读文件的时候从文件的开始读,写的时候从文件的末尾开始写。文件不存在时创建文件

b.fwrite/fread

  接下来我将演示打开文件,并对文件进行读写。在此之前我们还要认识一个函数:fread、fwrite
在这里插入图片描述
  这里它们的第一个参数是指向被写入/读的一段空间,第二个参数是每个元素字节大小,第三个是有多少个元素,第四个是要操作的文件。

c. 部分演示

部分演示:

在这里插入图片描述

在这里插入图片描述
  这里我们使用了fopen并且以w+的方式打开文件,刚开始我们这个目录底下是没有文件log.txt的所以要先创建好文件在向文件中写入上面代码中的要写入的内容。下面也是差不多,不过多解释
在这里插入图片描述

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

在这里插入图片描述
  我们发现我们上面的三个文件打开方式和之后的读写和Linux的重定向好像差不多。

3. 系统调用接口open/close

a. open的参数和返回值

我们上面知道,进程中打开文件,对文件的操作一定离不开操作系统,那么操作系统一定有着封装好的系统调用,来让进程能够对文件进行操作,而上面的进程是C语言编译好的程序,其中C语言中的文件打开对文件的读写肯定也是封装了系统调用。那么我们来认识两个系统调用open和close:

在这里插入图片描述
close不多介绍了就是关闭文件描述符所对应的文件。
在这里插入图片描述

首先是open,可以看到他有两个,这种好像是C++中的重载,怎么会在C语言中呢?其实这只是一种宏替换实现的并不是函数重载。
我们来介绍它的参数和返回值:

path和返回值

返回值:该函数打开文件成功后返回对应的文件描述符fd,失败返回-1
pathname:被打开文件的路径

标志位

后面两个参数我们要着重介绍一下,既然C语言中有着文件打开的方式,俺么系统调用里一定也有,这个就是文件打开的方式,但是它跟C语言中的传的方式不一样,这里使用了位操作的方式传,我们用代码来展示一下位操作传的标志问参数如何使用:
在这里插入图片描述

在这里插入图片描述
这就是标志位的使用,使用一个比特位就能描述一种状态。
这样我们就能介绍这个open中的flag参数怎么使用了,而手册里也告诉我们几种不同的打开方式,这里我们只介绍部分:

O_CREAT:文件不存在的话创建文件。
O_RDONLY:以读方式打开文件。
O_WRONLY:以写的方式打开文件。
O_TRUNC:打卡文件的时候会清空文件内容,并指向文件的开头。
O_APPEND:打开文件会指向文件的末尾。

我们看到这些参数也是全部以横杠+大写的方式命名,说明他们也只是一群宏常数罢了。

mode

接下来是第三个参数,mode:
我们打开一个文件log.txt。不在的时候创建它,在这里我们呢什么都没干,就把他打开后关闭了。
在这里插入图片描述

在这里插入图片描述
当我们看到如上结果的时候,发现文件的权限里怎么会出现S,并且文件是红的,这是因为文件的权限控制其实是乱码,而open中的mode就是控制这一权限的(被进程中创建的文件)。
在这里插入图片描述

在这里插入图片描述
这样就可以了,但是我们又发现,被进程创建好的文件设定的权限和实际的权限不一样,这是因为系统中有着掩码:
在这里插入图片描述
我们也可以在代码中设置进程中进程的掩码:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
但是不推荐这么做

b. open的返回值fd再认识

上面说到,open打开一个文件之后,会返回一个与之对应的文件标识符fd,文件标识符是什么?在C语言中使用fopen打开一个文件之后会返回一个指向被打开文件的FILE的一个指针,它与fd又有什么关系?接下来我们来一一解惑,既然涉及到了文件和进程那就又得从系统开始了解:
我们目前知道被打开的文件会被描述成文件结构体然后用数据结构组织起来(假设链表),那么在一个进程中,可能会需要打开多个文件,那进程和被打开文件又是如何联系起来的呢?其实它们是有着如下图的关系:
在这里插入图片描述
这其中文件如何加载到内存中先不说。
进程pcb中会有一个指针,这个指针指向一个指针数组,这个数组里面存储着被打开文件结构体的指针,这个数组也叫进程文件描述符表,那么这一张表让进程和文件而实现了解耦,就跟进程和内存的页表一样。我们现在来打开一下文件:
在这里插入图片描述
在这里插入图片描述
我们发现,文件标识符是一串连续的整数,这里直接提出结论:文件标识符就是进程文件标识符表的下标,操作系统访问文件就是根据文件标识符来访问
那么C语言中的FILE其实也肯定封装了fd,稍后我们可以进一步验证。
现在我们发现上面的数字只有3、4、5、6。0、1、2呢?我们知道进程在运行的时候会默认打开三个流:标准输入、标准输出、标准错误,那么结果显而易见,012就是这三个。代码说话:
在这里插入图片描述
在这里插入图片描述

那么这张图也说明了012对应的那三个标准输入输出流,FILE结构体里也封装了fd。
那么问题就来了:

为什么要默认打开这三个流?
文件描述符对应着着硬件,一切皆文件到底怎么理解?

c. 标准输入输出

首先来说明第一个问题:
默认打开这三个流,是为了让程序员能够默认进行输入输出的代码的编写(printf,scanf)。

d. 一切皆文件

第二个问题:
我们在使用open,fopen打开文件对文件进行各种操作,然后操作系统定时刷新文件缓存区,将操作得到的文件覆盖到磁盘中的文件中,说白了这些操作不还是和磁盘之间的交互吗?磁盘具有读写的功能,磁盘也是硬件,操作系统会和键盘交互,和网卡、麦克风、屏幕交互。那对于键盘来说该硬件只有读的功能,而屏幕只有写的功能。网卡可以读写,麦克风也只能读。这些功能肯定是在各自驱动中,有着对应的函数。而我们操作系统中的文件结构体中也肯定有着读写的函数指针,创建好一个文件对象的时候,我们让这个文件结构体的读写方法指向硬件的读写方法上:
在这里插入图片描述
每个硬件的读写的实现的方式肯定是不一样的,但是我们都是通过文件结构体对象来统一调用read和write方法来实现对硬件的读写,这种情况下我们把文件结构体看成父类,下面的键盘、屏幕等等看作子类,这不就是多态吗?而这一中调用的方式也叫做虚拟文件操作系统。所以对Linux来说一切皆文件。

4.对文件结构体认识

我们现在要知道当我们在进程中打开一个文件的时候,是怎么打开的:
我们打开一个文件,肯定是要对它进行各种操作,而文件无非就是文件内容+文件属性。所以文件结构体中应该会有文件的属性,以及对文件操作的各种方法集,操作文件不会在磁盘中操作,cpu不跟外设打交道,所以需要将文件加载到内存中,但是文件的加载又不是全部加载进来,而是,在创建好文件结构体的时候内存中还会构建一片文件缓冲区,文件的数据会加载到这里面。而这一系列操作都是由操作系统完成的。
那么我们使用系统调用打开文件后,返回系统标识符,用户根据这个文件标识符,对文件进行操作,而操作系统会到进程pcb中找到标识符指针数组的指针来找到那个数组,然后通过标识符下标来找到那个文件,再执行用户的操作。
在这里插入图片描述
那操作系统是如何对文件实现读写操作的呢?
首先是读:读肯定是读文件缓冲区的内容,当缓冲区中没有内容的的时候就会触发缺页中断,再让磁盘向缓冲区中加载(拷贝)。
写:我们在增加文件数据的时候,无非是开始增加,或者追加增加,这些都不需要加载文件到缓冲区,但是删除文件数据呢?这时候就需要将文件加载到缓冲区中了。而对文件内容的增加或删除在文件缓冲区中执行,然后由操作系统定时刷新文件缓冲区内容到磁盘中(也是一个拷贝的过程)。
那至此,我们得到,不论是文件的读还是写,都需要将文件加载到文件缓冲区中,并且用户对文件的读写对操作系统来说,也只是文件缓冲区的来回拷贝而已。

5. fd的再再认识

我们上面知道,进程创建好之后,会默认打开三个流,那假如我们关闭其中一个,再打开一个文件会怎么样呢?
在这里插入图片描述
在这里插入图片描述
关闭2呢?
在这里插入图片描述

在这里插入图片描述
不关闭
在这里插入图片描述
在这里插入图片描述
那我们就得出了fd的分配规则:寻找最小的,没有被使用的数据的位置,分配给指定的打开的文件
再认识两个个函数write/read:
在这里插入图片描述
在这里插入图片描述
返回值先不做介绍,使用起来很简单。
我们先对write和read和标准输入输出流进行简单的使用
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
需要注意的是这里我们没有将斜杠零算上去,那是因为“字符串结束的标志是斜杠零”这个是只在C语言中,对文件不适用。之后再验证。
在这里插入图片描述
我们再对上面进行一些小修改呢?

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们发现两个现象,一个是我们关闭1号文件描述之后对应的文件之后,再次打开一个文件,向1号文件写入时,写到了log.txt里。
第二个是当我们向1号文件里写入字符串时,字符串的斜杠零也被计算入内,但是log.txt中出现了乱码。
要是不计算呢?
在这里插入图片描述
在这里插入图片描述
就不会。
我们再看一个东西:
在这里插入图片描述
我们刚才的行为不就跟重定向一样吗?

a. 追加重定向

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

b. 输入重定向

在这里插入图片描述
在这里插入图片描述
重定向的本质其实就是修改文件描述符表的下标内容
我们上面是直接关闭了01两个流,有没有方法是不通过文件描述符分配规则关闭进行修改呢?
这里就有了dup2函数:
在这里插入图片描述
简而言之就是新的newfd就是最后保留下来的文件下标。
这个函数能实现文件描述符表级别的拷贝

c. 问题的引入

经过上面的了解我们再来一段这样的代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
关于这个问题,我们之后再说明,并且再次详细阐述重定向的问题以及文件机结构体部分具体成员的认识。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

openGauss学习笔记-162 openGauss 数据库运维-备份与恢复-导入数据-通过INSERT语句直接写入数据

文章目录 openGauss学习笔记-162 openGauss 数据库运维-备份与恢复-导入数据-通过INSERT语句直接写入数据162.1 使用openGauss数据库提供的客户端工具向openGauss数据库写入数据162.2 通过JDBC/ODBC驱动连接数据库执行INSERT语句向openGauss数据库写入数据162.2.1 函数原型162.…

在Node.js中MongoDB排序的方法

本文主要介绍在Node.js中MongoDB排序的方法。 目录 Node.js中MongoDB排序使用原生的mongodb驱动程序进行排序使用Mongoose库中的排序 Node.js中MongoDB排序 在Node.js中使用MongoDB进行排序,可以使用原生的mongodb驱动程序或者Mongoose库。 使用原生的mongodb驱动…

SpringBoot之响应的详细解析

2. 响应 前面我们学习过HTTL协议的交互方式:请求响应模式(有请求就有响应) 那么Controller程序呢,除了接收请求外,还可以进行响应。 2.1 ResponseBody 在我们前面所编写的controller方法中,都已经设置了…

我的世界合成表大全(最新完整版)

我的世界合成表配方是什么? 我的世界是一款非常有趣的高自由度的沙盒游戏,游戏中玩家可以根据合成配方制作各种各样的物品。今天小编就为大家带来我的世界合成表大全(最新完整版),希望可以帮到大家。 我的世界合成表大全(最新完整版) 基础物品合成表&a…

力扣第2题-判断一个数值是否是回文数[简单]

题目描述 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 例如,121 是回文&am…

【算法】【动规】乘积为正数的最长子数组长度

跳转汇总链接 👉🔗算法题汇总链接 1.1 乘积为正数的最长子数组长度 🔗题目链接 给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。 一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。 请你返回乘积…

移植LVGL到像素屏,从此玩转像素屏0门槛

硬件方面 先上渲染图 实物图 配置 主控:esp32 micro32 plus主频:240MhzFlash:8MPSRAM:2M 软件方面 众所周知,LVGL是一个十分优秀的图形框架,小到几百kb的单片机,大到Linux都可以运行。既然它…

【第2期】Springboot如何快速集成SpringSecurity

简单介绍 本专栏主要结合实战讲解,不过多介绍细节的概念,概念可以通过搜索引擎查找,一搜一大把,切入正题。 本专栏的实战项目是基于SpringbootSpringSecurityRSAJWTVUE的全栈开发项目,每个环节都会专门讲,…

音频DAC,ADC,CODEC的选型分析,高性能立体声

想要让模拟信号和数字信号顺利“交往”,就需要一座像“鹊桥”一样的中介,将两种不同的语言转变成统一的语言,消除无语言障碍。这座鹊桥就是转换器芯片,也就是ADC芯片。ADC芯片的全称是Analog-to-Digital Converter, 即模拟数字转换…

MATLAB 计算两片点云间的最小距离(2种方法) (39)

MATLAB 计算两片点云间的最小距离 (39) 一、算法介绍二、算法实现1.常规计算方法2.基于KD树的快速计算一、算法介绍 假设我们现在有两片点云 1 和 2 ,需要计算二者之间的最小距离,这里提供两种计算方法,分别是常规计算和基于KD树近邻搜索的快速计算方法,使用的测试数据如…

遥感图像分割系统:融合空间金字塔池化(FocalModulation)改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像分割是遥感技术领域中的一个重要研究方向,它的目标是将遥感图像中的不同地物或地物类别进行有效的分割和识别。随着遥感技术的不断发展和遥感…

OceanBase数据库部署

文章目录 OceanBase基础概念集群、Zone和OB ServerRootService总控服务(RS)多租户机制:资源隔离,数据隔离每个租户拥有若干资源池(Resource Pool) 部署形式部署流程OceanBase客户端工具 学习体验部署实现 O…

挑战52天学小猪佩奇笔记--day24

52天学完小猪佩奇--day24 ​【本文说明】 本文内容来源于对B站UP 脑洞部长 的系列视频 挑战52天背完小猪佩奇----day24 的视频内容总结,方便复习。强烈建议大家去关注一波UP,配合UP视频学习。 注:这集开始变成一段一段的猜台词,加…

区块链的可扩展性研究【06】Plasma

1.Plasma:Plasma 是一种基于以太坊区块链的 Layer2 扩容方案,它通过建立一个分层结构的区块链网络,将大量的交易放到子链上进行处理,从而提高了以太坊的吞吐量。Plasma 还可以通过智能合约实现跨链交易,使得不同的区块…

Element的安装以及基本使用

Element是基于Vue的网站组件库,用于快捷构建网页 像上面这样的样式 官网地址 Element - 网站快速成型工具 安装 npm i element-ui -S 装包命令 npm install babel-plugin-component -D 安装好之后会在package.json里面显示版本 在node_modules中会自动初始化一个 …

云原生之深入解析OOM和CPU节流

一、前言 使用 Kubernetes 时,内存不足 (OOM) 错误和 CPU 节流是云应用程序中资源处理的主要难题,这是为什么呢?云应用程序中的 CPU 和内存要求变得越来越重要,因为它们与云成本直接相关。通过 limits 和 requests ,可…

Java数据结构篇——单链表的基本操作

1. 前言 在上一篇《Java数据结构篇——实现顺序表的增删查改》,我们已经熟悉了 ArrayList 的使用并且进行了简单的模拟实现。ArrayList底层使用数组来存储元素,由于其底层是一段连续的空间,当ArrayList 任意位置插入或者删除元素时&#xff…

使用下载代替物理串口输出-STM32 Debug (printf) Viewer

使用下载代替物理串口输出-STM32 Debug 硬件要求配置方法代码要求打印输出结果 硬件要求 STM32的PB9、PB10引脚的串口1通常用作其他功能使用后,无法通过printf()函数打印输出想要调试输出查看变量或调试信息。现已使用另外一种方法实现printf()函数打印输出。 ST…

AutoGen多代理对话项目示例和工作流程分析

在这篇文章中,我将介绍AutoGen的多个代理的运行。这些代理将能够相互对话,协作评估股票价格,并使用AmCharts生成图表。 我们创建对话的目的是要求代理分析特定公司的股票价格,并制作股票价格图表。 为了实现这一目标,…

oracle DG 三种应用机制

首先理解不管是哪种机制,oracle都不是从主库直接传归档文件到备库,而是通过网络将主库的redo数据传输到备库: 1、普通DG是主库发生日志切换,备库把接收到的redo数据在备库通过归档进程生成为归档文件进行应用 2、ADG则是备库把接收…