Linux 文件系统(上)

news2024/9/22 19:43:25

目录

 一.预备阶段

1.认识文件

2.OS对内存文件的管理

3.C库函数和系统调用接口

a.C库函数——fopen

b.系统调用接口——open

二.理解文件描述符

1.一张图,详解文件描述符的由来

2.fd的分配规则

3.从fd的角度理解FILE

三.重定向和缓冲区

1.前置知识——理解向文件写入数据的底层逻辑

2.重定向

a.什么是重定向?

b.输出、输入重定向

c.重定向的底层逻辑

d. dup2

3.缓冲区

a.什么是缓冲区?

b.缓冲区谁提供的??

c.为什么要有缓冲区?

d.缓冲区的刷新机制

e.重点样例


 一.预备阶段

1.认识文件

一个文件,通常包括文件的内容数据(文件内存储的东西)和文件的属性数据(描述该文件本身状态的字段),所以,我们对文件进行操作,本质就是对文件内容和文件属性进行操作。

同理,文件的内容是数据,属性也是数据,所以,存储文件必须既存储内容数据,又要存储属性数据 ( 默认指的是磁盘中的文件)。

当我们要访问一个文件的时候,都是要先把这个文件打开,文件打开前,它是普通的磁盘文件。打开文件,就是将文件加载到内存

所以,根据文件所处位置,我们可以将文件分成两类:①普通的磁盘文件;②加载到内存的文件。

众所周知,我们的磁盘或内存中,会存在大量的文件,由于OS是计算机软硬件资源的管理者,所以OS需要管理这些文件,即能够高效的对这些文件进行增删查改操作,这就是文件系统!!

而在该文章中,咱们主要了解的是“OS对内存文件的管理”,至于磁盘中的文件,博主会在下一篇博客中详解。

2.OS对内存文件的管理

那么,操作系统是如果管理内存文件的? --- 先描述,再组织!!

看过博主往期博客的都知道,在操作系统中,一旦涉及到“管理”这两个字,那一定就有这六个字,先描述,再组织!!

什么是先描述?文件在加载到内存前,OS就需要在内核中创建对应文件的结构体对象,该结构体内存有大量有关对应文件的属性信息,这样一来,当文件被加载到内存后,OS就可以把这个结构体当成对应的文件。

什么是再组织?当描述文件的结构体对象被创建后,OS会把这个结构体放到特定的数据结构中,以便后续OS对该结构体进行操作,这样一来,OS对内存文件的管理,就变成了对特定数据结构中某一结构体对象的增删查改!!

文件是如何加载到内存的?

我们知道,文件被加载到内存后,就变成了系统资源的一部分,而进程是“承担分配系统资源的基本实体”,所以,文件想要被加载到内存,一定是因为系统中的某个或某些进程需要它!!

进程通过操作系统打开文件,由于操作系统内核不允许任何人直接干涉,所以,我们想要使用操作系统的某一功能时,就只能使用操作系统对上层提供的系统调用接口,来间接使用操作系统的功能。

故,我们所学习的C语言打开文件的库函数,其底层一定封装了系统调用接口!!!

d.一个进程可以打开多个文件吗?多个进程可以打开多个文件吗?加载到内存中,被打开的文件可能会存在多个

3.C库函数和系统调用接口

a.C库函数——fopen

FILE*  fopen(const char* path, const char* mode);   

这个函数的功能是,以读或写的方式打开某一文件(绝对路径或相对路径)。

若不写路径,只写文件名,则path是写在cwd(当前工作目录)下的,而 ls /proc/pid 可以查看某一进程的属性数据,包括cwd,代码中,用chdir(“new_path”)可以改变cwd。

例1:FILE* fp = fopen("log.txt","w");

以写方式打开文件,若文件不存在,则先创建再打开;若文件存在,则先清空再打开。

可知:其与 输出重定向">"的功能相似。

例2:FILE* fp = fopen("log.txt","a");

以追加的方式打开文件,从文件的结尾处以追加的方式写入数据,不清空原有数据。

可知:与追加重定向 ">>" 的功能类似。

b.系统调用接口——open

int open(const char* filename, int flags, mode_t mode);

第一个参数,filename:即想要打开文件的文件名。

第二个参数,flags:

O_RDONLY(以只读的方式打开)

O_WRONLY(以只写的方式打开)

O_RDWR(以读写的方式打开)

O_CREAT(文件不存在,则创建)

O_TRUNC(若文件存在,则清空文件内容)

O_APPEND(若文件存在,则以追加的方式写入数据)

第三个参数,mode:只有当该文件是新创建的,才会用到这个参数,mode是默认权限,可以给新创建的文件设置文件权限。

返回值:若创建失败,则返回-1;若创建成功,则返回对应文件的文件描述符

比特位级别的标记位使用方式 --- Linux中常用的传参方式

示例1:

int fd = open("log.txt",O_WRONLY | O_CREAT); 

新创建一个文件,将其命名为 log.txt,但是由于没给该文件设置权限,导致文件权限乱码:

修改后:

int fd = open("log.txt",O_WRONLY | O_CREAT, 0666);  

文件不存在的话,就创建该文件,并且将log.txt文件的默认权限设为0666.

示例2:

int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC, 0666);

以该方式打开文件,运行后,会将文件内原有的内容清零

示例3:

int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);:

以该方式打开文件,运行后,会在文件内容后,追加数据

综上所述,我们能够发现,系统调用中的open接口,与C库中的 fopen,有着极其相似的功能,咱们可以得出一个结论:C库中文件相关操作的库函数,其底层都封装了系统调用接口。

二.理解文件描述符

1.一张图,详解文件描述符的由来

通过上图,我们可以发现,所谓的“文件描述符表”,其本质就是一个struct files_struct*类型的数组而已,数组内存放的是文件struct file结构体的地址,而所谓的“文件描述符fd”,它本质就是文件描述符表内元素的下标而已。

例如:

int fda = open("loga.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);  // fda = 3

int fdb = open("logb.txt",O_WRONLY | O_CREAT | O_APPEND, 0666); // fdb = 4 

int fdc = open("logc.txt",O_WRONLY | O_CREAT | O_APPEND, 0666);  // fdc = 5

int fdd = open("logd.txt",O_WRONLY | O_CREAT | O_APPEND, 0666); // fdd = 6

fda、fdb、fdc、fdd它们分别是3、4、5、6,意味着:新打开文件的struct file地址,是从文件描述符表的3号下标开始填充的。

那0、1、2去哪了呢?

程序在运行的时候,默认是把下面这三个程序(文件)打开的:

①标准输入  键盘       stdin       0号下标

②标准输出  显示器    stdout    1号下标

③标准错误  显示器    stderr     2号下标

所以,一个进程最新打开的文件的fd,是从3开始的!!

OS/C语言为什么默认要把0、1、2、stdin、stdout、stderr打开呢??--- 就是为了让程序员默认进行输入、输出和代码编写!!

2.fd的分配规则

当进程新打开一个文件时,OS会遍历该进程对应的文件描述符表(下标由低到高依次遍历),当OS找到表中空余位置时,无论该位置后面是否存在元素,OS都会将新创建文件的struct file地址填充到该位置,并向进程返回该位置对应的数组下标。

3.从fd的角度理解FILE

那FILE究竟是什么东西??

它是一个C语言提供的结构体类型,其内部必定封装了文件描述符!!!

举例验证:

printf("stdin -> fd: %d\n", stdin->_fileno);//0 

printf("stdout -> fd: %d\n", stdout->_fileno);//1

printf("stderr -> fd: %d\n", stderr->_fileno);//2

说明,_fileno 字段,就是 FILE 内部封装的文件描述符!

三.重定向和缓冲区

1.前置知识——理解向文件写入数据的底层逻辑

在学习重定向和缓冲区之前,我们不妨先思考一下:当我们调用read、write、fread、fwrite时,OS底层究竟做了哪些是?一张图,咱们扒光它!!

无论读取还是写入数据,都要先把文件数据加载到文件缓冲区内!!

用户对文件中数据的读写的本质:用户通过fd变量,找到struct file* fd_array[]数组中的文件结构体地址,通过地址找到对应的结构体,再通过结构体中的数据属性,找到该文件对应的文件缓冲区(内存中存放文件内容数据的一块空间),对文件缓冲区中的数据进行读取或写入。

2.重定向

a.什么是重定向?

简单来说,就是将本该写入A文件中的数据,转而写入B文件中,这就是重定向(输出)。

b.输出、输入重定向

我们知道,在文件描述符表中,0、1、2三个文件是默认打开的,而根据fd的分配规则我们可以知道,如果我们将1号fd关闭,当我们接着打开一个新文件时,OS为该文件分配的fd就是1。

由于C库中的printf()函数本质是向1号文件(屏幕)写入数据,所以,如果我们此时调用printf的话,就会将本该打印到屏幕上的数据重定向写入到新打开的文件中。

而这,其实就是输出重定向的底层原理。代码如下:

c.重定向的底层逻辑

①上述代码中,为啥显示器上什么都没输出??

--- 因为1号文件描述符,也就是显示器被关了

②为啥本该输出的数据被写到了FILE_NAME文件里??

--- 因为printf()函数只认stdout——标准输出流,而stdout只认1号文件描述符,由于原先1号文件描述符对应的显示器被关了,而新打开文件的文件描述符根据fd的分配规则,被重新分配到了1号位置,所以本该在显示器上数出的数据跑到了FILE_NAME文件中。该过程便可称为输出重定向!

③上述代码中,为啥本该从键盘上读取数据的操作,变成了读取FILE_NAME文件中的数据??

--- 因为0号文件描述符,即键盘文件被关了,而以读的方式打开FILE_NAME文件时,根据fd的分配规则,0号位置被重新分配给了FILE_NAME文件,由于stdin——标准输入流,只认0号数组位置,所以本该从键盘上读取数据的操作变成了从FILE_NAME文件中读取数据。该过程便可称为输入重定向!

所以,重定向的本质,是文件描述符级别的数组内容的拷贝!

那么,我们的输入、输出重定向都需要先关闭0号或1号文件,然后再打开新的文件吗?--- 重定向的底层逻辑都是这样,但是这个操作有人已经为我们封装成了函数接口(dup2),咱们可以直接使用函数来实现重定向操作。

d. dup2

dup2(int  dest_fd, int  src_fd); 

让本该向src_fd文件输入、输出的数据,改向dest_fd文件进行输入、输出。

即在文件描述符表中,让src对应的struct file* 被dest对应的struct file*覆盖!

输出重定向:

输入重定向:

标准输出流和标准错误流

解释:将mytest文件中“原本向标准输出”打印的内容重定向写到normal文件中,将“原本向标准错误流”中打印的内容重定向写到err.log文件中,实现打印错误信息和打印正常信息的分流!

3.缓冲区

a.什么是缓冲区?

--- 缓冲区本质是一块内存空间,我们可以把它看成一个小水池,池水就是数据,当水池内的水不够多时,水池蓄水(数据存储);当水池内的水达到一定条件时,咱们就开闸放水(数据刷新)。

b.缓冲区谁提供的??

--- 若该内存是由用户开辟出来的,如变量、数组等,则称为用户缓冲区若该缓冲区是C语言提供的,则称为C标准库缓冲区若是由操作系统提供的,则称为操作系统缓冲区

c.为什么要有缓冲区?

--- 无论是用户与内核间的数据IO,还是内核与磁盘间的数据IO,它们都是需要花费一定成本的,IO次数越少,所消耗的成本也就越低,所以,缓冲区的存在就是为了减少IO次数,从而降低IO所带来的开销问题。

d.缓冲区的刷新机制

① 无缓冲 --- 缓冲区内一有数据就直接刷新.

② 行缓冲 --- 只有向缓冲区内写入换行字符,即'\n'时,才会刷新数据,否则不刷新.

③ 全缓冲 --- 只有把缓冲区写满了,才刷新;否则不刷新.

④ 强制刷新 --- 调用fflush(stdout),或当进程退出时,OS会自动刷新该进程内缓冲区的数据.

一般对于显示器文件 --- 行缓冲;

对于磁盘上的文件 ---全缓冲.

示例:

答:因为向显示器文件中写入数据的刷新准则是行刷新,而上述代码的 pirntf 中并没有 换行符'\n',所以 printf 仅仅是将 hello world 写入C库缓冲区或内核缓冲区,其内容并没有写入显示器文件中,而sleep 3秒后,代码执行完毕,进程退出,缓冲区内的数据会被强制刷新到显示器文件中(屏幕)。

e.重点样例

为什么多一个fork()时,使用C库函数写入文件的内容打印了两次,而使用系统调用接口写入文件的内容不变??fork()对C库函数的输出函数又起到了怎样的影响??

理解样例:

①当我们直接向显示器打印的时候,显示器文件的刷新方式是行刷新!由于我们的代码输出的所有字符串之前都有\n, 所以在fork()之前,缓冲区上的数据就已经全被刷新出来了,包括systemcall(系统调用)

②重定向到log.txt文件,本质是向磁盘文件写入,系统对于数据的刷新方式已经由行刷新,变成了全缓冲!

③全缓冲意味着缓冲区变大,实际写入的简单数据,不足以把缓冲区写满,fork执行的时候,数据依旧在缓冲区中!

④C语言库函数输出的内容写在C库缓冲区,而系统调用接口输出的数据是直接写到内核缓冲区的fork后会创建子进程。

⑤当进程退出时,即使我们的数据没有达到刷新的条件,OS也会自动刷新C标准库缓冲区对于多进程(父子进程)来说,刷新缓冲区属于对其一进程进行“清空”或“写入”操作,所以,fork退出时,刷新缓冲区,就要进行写时拷贝!!

而write是系统调用,没有使用C的缓冲区,直接写入到操作系统,不属于进程了,不发生写时拷贝!

用C语言库函数对文件写入的实质:调用C函数会先把要写的内容写入C缓冲区,当C缓冲区满足刷新条件时,会将缓冲区里的内容通过系统调用接口(write(1,BUFFER))拷贝到对应的文件缓冲区内。

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

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

相关文章

网络安全-CSRF

一、环境 DVWA网上找 二、简单介绍 这个漏洞很早之前了,但是为了避免大家在面试等等的时候被问到,这里给大家温习一下 CSRF全程是没有黑客参与的,全程都是用户自己在操作 三、环境演练 这个是DVWA的提交表单页面,我这里伪造…

【2020工业图像异常检测文献】PaDiM

PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization 1、Background 在单类学习(仅使用正常数据(即“单一类”)来训练模型)环境中的异常检测和定位任务方法中,要么需要深度神经网…

结合HashMap与Java 8的Function和Optional消除ifelse判断

shigen坚持更新文章的博客写手,记录成长,分享认知,留住感动。个人IP:shigen 在文章的开头我们先从这些场景进入本期的问题: 业务代码中各种if-else有遇到过吗,有什么好的优化方式;java8出来这么…

鸿蒙开发(NEXT/API 12)【跨设备互通特性简介】协同服务

跨设备互通提供跨设备的相机、扫描、图库访问能力,平板或2in1设备可以调用手机的相机、扫描、图库等功能。 说明 本章节以拍照为例展开介绍,扫描、图库功能的使用与拍照类似。 用户在平板或2in1设备上使用富文本类编辑应用(如:…

学习 git 命令行的简单操作, 能够将代码上传到 Gitee 上

首先登录自己的gitee并创建好仓库 将仓库与Linux终端做链接 比如说我这里已经创建好了一个我的Linux学习仓库 点开克隆/下载: 在你的终端中粘贴上图中1中的指令 此时他会让你输入你的用户名和密码,用户名就是上图中3中Username for ....中后面你的一个…

预付费计量系统实体模型

1. 预付费计量系统实体模型 A generic entity model for electricity payment metering systems is shown in Figure 2. Although it provides a limited perspective, it does serve to convey certain essential concepts. 关于电子式预付费电表系统的实体模型见图 2…

李宏毅结构化学习 03

文章目录 一、Sequence Labeling 问题概述二、Hidden Markov Model(HMM)三、Conditional Random Field(CRF)四、Structured Perceptron/SVM五、Towards Deep Learning 一、Sequence Labeling 问题概述 二、Hidden Markov Model(HMM) 上图 training data 中的黑色字为x&#xff…

如何备份SqlServer数据库

第一步:登录你要备份的服务器数据库ssms 第二步:选择你要备份的数据库 此处已PZ-SJCS 数据库为例 右键该数据库-->任务-->备份 第三步:选择你备份的类型备份组件等,目标磁盘 ,点击添加选择将你备份的文件备份那…

全面详尽的 PHP 环境搭建教程

目录 目录 PHP 环境搭建概述 在 Windows 上搭建 PHP 环境 使用集成环境 XAMPP 安装步骤 配置和测试 常用配置 手动安装 Apache、PHP 和 MySQL 安装 Apache 安装 PHP 安装 MySQL 配置 PHP 连接 MySQL 在 Linux 上搭建 PHP 环境 使用 LAMP 方案 安装 Apache 安装 …

【25.6】C++智能交友系统

常见错误总结 const-1 如下代码会报错 原因如下: man是一个const修饰的对象,即man不能修改任何内容,但是man所调用的play函数只是一个普通的函数,所以出现了报错。我们需要在play函数中加上const修饰,或者删除man对…

《论分布式存储系统架构设计》写作框架,软考高级系统架构设计师

论文真题 分布式存储系统(Distributed Storage System)通常将数据分散存储在多台独立的设备上。传统的网络存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满…

FreeRTOS-时间片调度

FreeRTOS-时间片调度 一、时间片调度简介二、时间片调度实验 一、时间片调度简介 同等优先级任务轮流的享有相同的CPU时间(可设置),叫时间片,在FreeRTOS中,一个时间片就等于SysTick中断周期,所以说时间片大小取决于滴答定时器中断…

windows安装Anaconda教程

一、简介 Anaconda 是一个开源的 Python 和 R 语言的分发平台,专为科学计算和数据分析设计。它包含了包管理器 Conda,可以方便地安装和管理库、环境和依赖项。此外,Anaconda 还附带了许多数据科学工具和库,如 Jupyter Notebook 和…

【HTTPS】中间人攻击和证书的验证

中间人攻击 服务器可以创建出一堆公钥和私钥,黑客也可以按照同样的方式,创建一对公钥和私钥,冒充自己是服务器(搅屎棍) 黑客自己也能生成一对公钥和私钥。生成公钥和私钥的算法是开放的,服务器能生产&…

iOS17找不到developer mode

iOS17找不到开发者模式 developer mode 下载过app之后、弹窗Developer Mode Required之后,这个菜单就出现了(之前死活找不到)。 背景:用蒲公英分发测试app,有个同事买了新机(iphone 15 pro max),添加了白名…

双虚拟机部署php项目

前言 经过前面的学习,我们对分布式部署有了一定的了解,这次我们尝试做些东西 准备 我打算用虚拟机部署一个外联网盘 一台虚拟机安装php另一台安装MySQL,但是之前已经安装过 MariaDB 了,就不打算改了。 通常MariaDB与MySQL兼容性很好,可以作为替代使用。彩虹外链网盘项目…

OpenAI的O1模型达到AGI二级,类人推理能力被提示危险,细思极恐!

大家好,我是Shelly,一个专注于输出AI工具和科技前沿内容的AI应用教练,体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具,拥抱AI时代的到来。 今天让我们一起来聊聊最近科技圈的大新闻—…

Java笔试面试题AI答之设计模式(4)

文章目录 16. 简述什么是观察者模式?基本概念主要特点实现方式应用场景优缺点 17. 请列举观察者模式应用场景 ?18. 请用Java代码实现观察者模式的案例 ?19. 什么是装饰模式?定义与特点结构与角色工作原理优点应用场景示例 20. 请用…

队列的各种接口的实现(C)

队列的概念 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头 队列的实…

【GlobalMapper精品教程】088:按点线面空间位置选择案例

按点线面空间位置选择的原则为:点线面的排列组合。 文章目录 一、选择线要素附近的点二、选择相交或触碰所选线的区和线三、选择包含点的区要素四、选择选定区域内的点要素一、选择线要素附近的点 启动该工具之前,首先要选择线,例如,选择某一段铁路5km范围之内的县城驻地。…