文件系统 --- 文件结构体,文件fd以及文件描述符表

序言

 在编程的世界里,文件操作是不可或缺的一部分。无论是数据的持久化存储、日志记录,还是简单的文本编辑,文件都扮演着至关重要的角色。然而,当我们通过编程语言如 C、Java 等轻松地进行文件读写时,背后隐藏的复杂机制和底层细节往往被我们所忽略。
 本文将带着大家理解文件操作在底层的样子,原来文件不仅仅是被简单地读入或写入内存中。


1. 系统调用接口 🔁 用户编程接口

 操作系统(简称 OS )是计算机系统的核心软件,它管理计算机的硬件和软件资源,为上层应用程序提供一个稳定、统一的运行环境。
 如果将你的电脑比作财产的话,那么操作系统就是你的管家,而且还是一个强势的管家。

1.1 系统调用接口

1. 系统调用接口的概念

 当你想要访问你的计算机的软硬件资源时,必须符合操作系统的规定,而系统调用就是操作系统为用户空间程序提供的一种服务接口。

2. 为什么要存在系统调用接口

 我的电脑我想干嘛就干嘛呀! 😤 为什么操作系统管的这么严呀,还要按照他的要求来使用,到底谁是主人呀!
 操作系统的存在就是避免用户不合法的行为(比如:错误的修改数据,不正确的使用)导致争整个系统的崩溃!所以正是想要用户得到一个良好的运行环境,才约束用户的行为。

3. 系统调用接口的作用

 系统调用是计算机操作系统中非常核心的概念,它的作用包含但不限于如下内容:

  • 硬件保护与隔离:系统调用作为用户程序和硬件设备之间的中介,确保用户程序不能直接访问硬件,从而保护硬件资源免受非法访问和破坏。
  • 资源管理与分配:操作系统通过系统调用来管理CPU时间、内存、文件和设备等系统资源,确保资源的公平分配和有效利用。
  • 实现操作系统功能:系统调用是操作系统实现各种功能(如进程管理、内存管理、文件系统、网络通信等)的基础。

1.2 用户编程接口

1. 用户编程接口的概念

 用户操作接口接口(API)是操作系统或库函数提供给程序员的接口,在 C 语言环境中,API 通常以库函数的形式出现,这些函数封装了系统调用的细节,为程序员提供了更为简便、易用的编程接口。

2. 为什么要存在用户编程接口

API 提供了一种对系统调用抽象和封装。通过将复杂的系统细节隐藏起来,API 只暴露用户需要的功能和接口,使得用户(包括程序员)可以更容易地理解和使用这些功能。这种抽象和封装降低了直接与系统底层交互的难度和复杂性。

3. 编程接口的跨平台性

 除了简化用户对系统调用的使用,编程接口还具有一个非常重要的性质,那就是 跨平台性
 系统调用接口对应不同的系统如 Linux, Windows,Mac等,实现的细节肯定是不一致的。所以你在 Linux 系统下使用了系统调用的程序,在 Mac 下就不一定能运行。
 但是 API 通过特定的方法(如条件编译配置)可以实现在不同的平台下都能正常运行。


2. 利用系统调用接口进行文件操作🐧

 在使用 C 语言进行文件操作时,我们利用 fopen 函数以特定的方式打开一个文件,利用 fread 读取文件或者是利用 fwrite 写文件,最后利用 fclose 函数关闭文件。
 这都是将系统接口进行封装过后的 API,今天我们尝试直接使用系统接口,这更加接近底层,更好的帮助我们引出后续的内容。😀

2.1 open 函数

 系统提供的 open 函数有两个版本:

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

下面多出的 mode 参数用于指定新创建文件的权限,所以我们使用下面那个的版本。我们看看他整体的参数:

  • pathname :指向要打开的文件的路径名
  • flags : 用于指定打开文件的方式以及其他选项(如是否创建文件)。这些选项可以通过位或操作组合起来。
  • mode : 指定新创建文件的权限 。
  • 返回值为一个 int,叫做文件描述符(fd

我们在这里详细介绍 flags 参数 以及 mode 参数:

flags 参数 — 位图

 某些函数需要我们传递标志位,该函数根据标志位执行特定的功能,就比如:

  1 #include <iostream>2 using namespace std;3 4 5 int Func(int num, int flag1, int flag2, int flag3){6     if(flag1 == 1){7         num = num + 1;8     }9     if(flag2 == 1){10         num = num - 1;11     }12     if(flag3 == 1){13         num = num * 2;14     }15 16     return num;17 }18 19 20 int main(){21     int num = 10;22     int flag1 = 1, flag2 = 0, flag3 = 1;23 24     num = Func(num, flag1, flag2, flag3);25     cout << "num = " << num << endl;                                                                                                                                                                          26 27     return 0;28 }

我们根据 3 个标志位质的不同执行不同的逻辑,但现在只是 3 个标志位,如果是 10 个,20 个,那我们也设置相同数量的形参吗,这太麻烦,也太浪费空间了。

那怎么办呢?位图。一个 int 变量在该环境下是 32 位,是否可以表示为 32 种状态呢?为了简单,就拿 8 位举例:

#define FLAG1 1 // 0000 0001
#define FLAG2 2 // 0000 0010
#define FLAG3 4 // 0000 0100

不同的状态对应的位置我就取 1 ,如果这 3 个状态我都想要呢?那就利用 | 或对他们进行运算:

FLAG1 | FLAG2 | FLAG3 // 0000 0111

可以看到,这就代表这三个状态我都表示,并且不同的状态之间是相互不干扰的。

根据这个方法,我们更新上述代码:

  1 #include <iostream>2 using namespace std;3 4 #define FLAG1 1 // 0000 00015 #define FLAG2 2 // 0000 00106 #define FLAG3 4 // 0000 01007 8 // 利用 & 操作,判断是否选取该状态9 int Func(int num, int flags){10     if(flags & FLAG1){11         num = num + 1;12     }13     if(flags & FLAG2){14         num = num - 1;15     }16     if(flags & FLAG3){                                                                                                                                                                                        17         num = num * 2;18     }19 20     return num;21 }22 23 24 int main(){25     int num = 10;26 27     num = Func(num, FLAG1 | FLAG3);28     cout << "num = " << num << endl;29 30     return 0;31 }

flags 参数就采用了位图的方式,我将列举我们常用的选项:

  • O_RDONLY:以只读方式打开文件。
  • O_WRONLY:以只写方式打开文件。
  • O_RDWR:以读写方式打开文件。
  • O_CREAT:如果文件不存在,则创建它。需要 mode 参数来指定新文件的权限。
  • O_TRUNC:如果文件已存在且为只写或读写模式打开,则清空文件内容。
  • O_APPEND:以追加方式打开文件,数据会被写入到文件尾。

mode 参数 — 文件权限

 关于文件权限的知识在这里就不展开细说了,这样的话篇幅太长了,也不易消化😖。我专门有一篇文章 👉点击查看 详细地介绍了文件权限相关的知识,大家有兴趣可以看一下哈。

 前置知识结束了,现在我们可以进入正题了,我们以写的方式创建一个新的文件:

 16     // 写方式打开文件,并且文件不存在就创建一个,文件的权限是 rw-rw-r--17     int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);18     if(fd == -1) perror("open");

注意:这里就可以看出系统调用接口和编程接口的区别,在 C语言中的 fopen 函数,当文件不存在时会为我们自动创建一个文件,而系统调用接口就需要我们指定。

2.2 write 函数

write 函数的参数就稍显简单一些:

ssize_t write(int fd, const void *buf, size_t count);
  • fd : 要写入数据的文件描述符。
  • buf:指向要写入数据的缓冲区的指针。
  • count:要写入文件的字节数。
  • 返回值:写入错位时返回 -1

我们就直接写入内容吧:

 20     // 写入指定信息21     const char* msg = "Its a test!!!\n";                                                                                                                                                                      22     ssize_t ret = write(fd, msg, strlen(msg));23     if(ret == -1) perror("write");

2.3 运行结果

 系统按照我们的需求创建了一个指定权限的文件:
在这里插入图片描述
并且为我们写入了相应的内容:
在这里插入图片描述


3. 提出几个为什么

 我们需要根据现有的现象来发现问题,不断地问自己为什么,这才能提升自己。

3.1 fd 文件描述符是什么?

 我们通过接受 open 函数返回的 fd 文件描述符,并将 fd 传入到 write 函数的参数中,就可以向指定文件写入内容,这是为什么呢?怎么就可以根据一个 fd 的整数向指定文件写入内容呢?

3.2 open 函数的返回值?

 我们先看一段代码:

 21 int main(){22     int fd1 = open("log1.txt", O_WRONLY | O_CREAT, 0666);23     printf("fd1: %d.\n", fd1);24 25     int fd2 = open("log2.txt", O_WRONLY | O_CREAT, 0666);26     printf("fd2: %d.\n", fd2);27 28     int fd3 = open("log3.txt", O_WRONLY | O_CREAT, 0666);29     printf("fd3: %d.\n", fd3);30 31     int fd4 = open("log4.txt", O_WRONLY | O_CREAT, 0666);32     printf("fd4: %d.\n", fd4);                                                                                                                                                                                33     return 0;34 }

这段代码的输出结果是:

fd1: 3.
fd2: 4.
fd3: 5.
fd4: 6.

我们知道 open 函数的返回值是 fd ,但为什么他们的值是连续的?并且唯独缺少 0, 1, 2,这是巧合吗?如果一次运行时这样,有理由怀疑是巧合。但是,我们多次运行还是这样,那就肯定不是了。


4. struct file 结构体

 当一个文件没有被任何进程调度时,该文件就静静的躺在你的磁盘中。但是当文件被调度时,就会被送往内存中,所以内存中的文件就只是文件的内容吗?肯定不是!同一时间,内存中肯定存在着大量需要操作的文件,操作系统需要高效的管理文件那怎么办呢?
 使用 结构体 + 双链表 的结构,类似于管理进程的结构 ,在这篇文章,有较详细的说明👉点击查看。

4.1 struct file 中的内容

 该结构体中包含了文件的基本信息,包括:

  • f_op:指向一个 file_operations 结构体的指针,该结构体包含了指向文件操作函数的指针,如 read、write、open、release 等。
  • f_count:表示有多少进程或文件描述符引用了这个文件。当 f_count 降到 0 时,文件将被关闭。
  • f_flags:包含文件的标志,如 O_RDONLY、O_WRONLY、O_RDWR 等,表示文件的打开模式。
  • f_mode:表示文件的访问模式(只读、只写、读写)。
  • f_pos:当前的文件偏移量,表示下一次读写操作将从哪个位置开始。
  • f_owner:包含文件的所有者信息,用于权限检查。


4.2 相关的内核级别的缓存区

 还有一个空间叫做 缓冲区 尽管不直接受到该结构体的管理,但是和结构体紧密相关:
在这里插入图片描述
可以看出该 缓存 就是一块在内存中的空间,那有啥用呢?用处可大了!

当用户请求读取文件时,内核会首先检查缓冲区中是否已经缓存了所需的数据。如果是,则直接从缓冲区中读取数据,避免了磁盘访问的延迟。如果不是,内核会从磁盘读取数据到缓冲区中,并更新缓冲区的内容。

当用户更新文件内容时,内核并不会直接将该内容立马更新到磁盘上,而是先放在缓冲区等到累积到一定的量,在一次写入磁盘,减少 I/O 操作。

所以对于内存这种告诉设备来说,内核级缓冲区通过减少对磁盘等低速设备的直接访问次数,显著提高了文件I/O操作的性能。

4.3 理解 Linux 一切皆文件

 相信大家肯定听过一句话,Linux 中一切皆文件。但是对于我们的键盘,鼠标,显示屏来说,他们确实是实实在在的硬件呀!我该怎么把它看作文件呢?

属性 + 方法

 对于任何的硬件设备都离不开两个概念 属性 + 方法,但是本章节我们主要关注该结构体的 方法Linux 将一切的对象都视作一个文件,那么就拿键盘举例吧,请问键盘这个文件有什么方法呢?无非就是 读方法 或者是 写方法!键盘你能读我理解,写方法 又是什么呢?键盘确实没有写方法,该方法置空不就好啦!

所以我们可以这么看待硬件:
在这里插入图片描述
对于操作系统来说,对于普通文件的管理使用的是一个 struct file ,里面包含文件的基本属性以及读写方法等,对于硬件我也可以一同看待呀!也还不是硬件的基本属性以及读写方法:
在这里插入图片描述
所以说当操作系统调用一个文件的时候,才不关心该文件本质是硬件还是啥,我该使用读方法就使用读方法,改写就使用写方法,反正所有文件的操作函数接口是一致的。

这就是使用 struct 封装的好处,尽管底层千差万别,但是上层的调用都是一致的!


5. fd 含义以及文件描述符表

 上面说到,操作系统会将系统调度的所有文件的 struct 利用双链表的形式管理起来。我们的一个进程可能同一时间调度多个文件,又该如何管理呢?

5.1 文件描述符表

 进程会将自己所控制的所有文件的 struct 结构体的指针放在一个文件描述表中:(注:该表中包含其他信息,但在本文中不关注。
在这里插入图片描述
该表包含一个结构体指针数组,每一个存储的元素就是你所控制文件结构体的指针,该表可以在进程的 PCB (在 Linux 下叫做 struct_task)中找到。

5.2 fd 的含义

 到这里就不难理解 fd 的含义了,他为你想要操作文件的结构体指针在结构体指针数组里面的下标。依靠他,就能找到想要操作文件的位置,环环相扣!

5.3 fd 的分配方式

 那进程新加入文件描述符表的文件,怎么分配 fd呢?会从上到下遍历该表,哪个位置是空闲的就放到哪个位置。


6. 解答疑惑

 有了一定的知识基础之后,我们尝试解决在 3 中提出的问题。

6.1 fd 文件描述符是什么?

 进程新使用的文件会将该文件的结构体指针放入进程描述符表的结构体指针数组中,该 fd 就是指针放入结构体指针数组的下标位置。有点绕口哈😇

6.2 open 函数的返回值?

 为什么 open 函数的返回值缺少 0, 1, 2 呢?因为这几个文件描述符已经被标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)在进程启动时占用。

  • 0 通常被分配给标准输入 键盘stdin
  • 1 通常被分配给标准输出 显示器stdout
  • 2 通常被分配给标准错误输出 显示器stderr

怎么证明呢?
证明 1:

   14 int main(){15 16     const char* msg = "Its a test!!!\n";17     ssize_t ret = write(1, msg, strlen(msg));                                                                                                                                                               18 19     return 0;20 }

我们直接向 1 对应的文件写入信息,运行,成功在显示器打印:

Its a test!!!

证明 2:

 13 int main(){14 15     umask(0);16 17     close(1);// 关闭显示器文件18 19     // 写方式打开文件,并且文件不存在就创建一个,文件的权限是 rw-rw-r--20     int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);21     if(fd == -1) perror("open");22     printf("fd = %d.\n", fd);        23                                                                                                                                                                                                               24                                                          25     return 0;                         26 }

在这里我们关闭 1 对应的文件,之后我们打开一个文件,并打印。
最后我们发现,并没有输出任何内容到屏幕上,反而内容在 log.txt 文件上?

大家思考一下这是为什么?


7. 总结

 在这篇文章中介绍了部分文件系统的内容,希望大家有所收获!

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

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

相关文章

自动化运维工具之Ansible

一、Ansible Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、管理上千台主机…

ADS环境下的ARM汇编程序设计实验报告

ADS环境下的ARM汇编程序 一、 实验目的 1&#xff0e;了解 ARM汇编语言的基本框架,学会使用ARM的汇编语言编程。 2&#xff0e;熟悉ADS1.2下进行汇编语言程序设计的基本流程&#xff1b; 3. 了解AXD中调试功能。 二、 实验环境 硬件&#xff1a;PC机 软件&#xff1a;ADS…

基于VScode和C++ 实现Protobuf数据格式的通信

目录 1. Protobuf 概述1.1 定义1.2Protobuf的优势 2. Protobuf 语法3、序列号和反序列化3.1 .pb.h 头文件3.2 序列化3.3 反序列化 4、测试用例 Protobuf详细讲解链接 1. Protobuf 概述 1.1 定义 protobuf也叫protocol buffer是google 的一种数据交换的格式&#xff0c;它独立…

熵权法确定权重

熵权法&#xff08;Entropy Weight Method, EWM&#xff09;是一种在综合考虑各因素提供信息量基础上计算综合指标的数学方法&#xff0c;属于客观综合定权法&#xff0c;在确定权重时更有说服力。该方法主要根据各指标传递给决策者的信息量大小来确定权重。在信息论中&#xf…

[RoarCTF 2019]Easy Calc1

打开题目 查看源码&#xff0c;看到 看到源代码有 calc.php&#xff0c;构造url打开 看到php审计代码&#xff0c; 由于页面中无法上传num&#xff0c;则输入 num&#xff0c;在num前加入一个空格可以让num变得可以上传&#xff0c;而且在进行代码解析时&#xff0c;php会把前…

库存超卖问题解决方式

文章目录 超卖问题解决方式什么是库存超卖问题&#xff1f;乐观锁和悲观锁的定义超卖问题解决方式一、悲观锁1.jvm单机锁2.通过使用mysql的行锁&#xff0c;使用一个sql解决并发访问问题3.使用mysql的悲观锁解决4. 使用redis分布式锁来解决 二、乐观锁解决1.版本号2. CAS法&…

数据结构第1天作业 7月31日

2.3按位置操作 1&#xff09;按照位置插入数据 void Insert_seqlist_single(Seqlist* sq,int arr_sub,int num){if(sq->posN ){ //判断顺序列表是否为满printf("error");return;}else if(arr_sub<0||arr_sub>sq->pos){printf("error…

React组件生命周期

一张图解释 React 类组件生命周期方法 React 类组件的生命周期可以分为三个主要阶段&#xff1a; 挂载&#xff08;Mounting&#xff09; 更新&#xff08;Updating&#xff09; 卸载&#xff08;Unmounting&#xff09; 挂载阶段 在组件实例被创建并插入到 DOM 中时调用…

SpringSecurity登录认证流程及源码分析

目录 一 作用 二 流程及源码分析 一 作用 spring security作为spring家族中的一员&#xff0c;它的主要作用有两个&#xff0c;分别是认证和授权。 我们以前在实现登录功能的时候&#xff0c;前端会传来用户名和密码&#xff0c;然后我们根据前端传来的数据从用户表中的数据进…

Java高级Day18-集合

62.集合 之前保存多个数据元素使用数组&#xff0c;但数组有以下缺点&#xff1a; 长度开始必须指定&#xff0c;指定后不可修改 保存的必须为同一类型的元素 使用数组进行增加/删除元素的代码比较麻烦 集合 可以动态的保存任意多个对象 提供了一系列方便操作对象的方法 …

河南萌新联赛2024第(三)场:河南大学

传送门&#xff1a;河南萌新联赛2024第&#xff08;三&#xff09;场&#xff1a;河南大学_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ B 正则表达式 思路&#xff1a;模拟 代码&#xff1a; #include<bits/stdc.h> using namespace std; typedef long lo…

vue3+fetch请求+接收到流式的markdown数据+一边gpt打字机式输出内容,一边解析markdown语法+highlight.js实现代码高亮

这个问题终于解决了&#xff01;好开心。 先看最终效果&#xff1a; video_20240724_141543_edit 项目背景&#xff1a;vue3 场景&#xff1a;像gpt一样可以对话&#xff0c;当用户发送问题之后&#xff0c;ai回复&#xff0c;ai是一部分一部分回复&#xff0c;像打印机式输出…

Moving Object Segmentation: All You Need Is SAM(and Flow) 论文详解

系列文章目录 文章目录 系列文章目录前言摘要1 引言2 相关工作3 SAM Preliminaries4 帧级分割Ⅰ&#xff1a;以流作为输入5 帧级分割Ⅱ&#xff1a;以流为提示6 序列级掩膜关联7 实验7.1 数据集7.2 评价指标7 .3 实施细节7.4 消融实验7.5 定量结果7 .定性可视化 8 结论致谢附录…

Ollama0.3.0 + llama3.1本地部署

Ollama0.3.0 llama3.1本地部署 安装Ollama 官网下载安装包 https://ollama.com/download ​​ 根据系统选择对应版本 安装完成后&#xff0c;电脑右下角会出现ollama程序图标&#xff0c;表示ollama正在运行。 ​​ 打开cmd命令 下载Llama3.1 输入ollama&#xff0c…

从“线缆迷宫”到“数字通途”:一机一网助力成天泰园区网络升级

(文 林海宾/深圳速锦网络科技有限公司) 林海宾,现任深圳速锦网络科技有限公司(以下简称速锦网络)的项目总监,一个入行十年、经验老道的数字化升级”操盘手“。他曾经主导过中国农业银行深圳分行130多个网点以及美的珠海工厂等数字化建设升级项目。在2024年的五一,他帮助深圳市…

【MATLAB源码】机器视觉与图像识别技术(7)续---BP神经网络

系列文章目录在最后面&#xff0c;各位同仁感兴趣可以看看&#xff01; BP神经网络 第一节、BP网络定义第二节、BP网络结构及其特点第三节、信息传播方式 信息的正向传播&#xff1a;实质是计算网络的输出误差的反向传播&#xff1a;实质是学习过程第四节、 BP网络的算法流程…

视频怎么在尽量不损害画质的前提下压缩?试试这4款视频压缩神器

4个视频压缩神器&#xff0c;帮你在不损画质的前提下满足压缩需求&#xff1a; 1、嗨格式压缩大师 关键词&#xff1a;高效、批量 直达链接>>yasuo.hgs.cn 嗨格式压缩大师是一款免费的文件压缩工具&#xff0c;支持视频、图片、PDF、PPT等文件快速、批量压缩&#xff…

代码随想录 day 28 贪心

第八章 贪心算法 part02 贪心 局部最优解推出全局最优 &#xff0c;而且想不到反例&#xff0c;那么就试一试贪心 将问题分解为若干个子问题 找出适合的贪心策略 求解每一个子问题的最优解 将局部最优解堆叠成全局最优解 只要想清楚 局部最优 是什么&#xff0c;如果推导出全局…

XR-Frame 计算相机与场景物体的距离

如下哦 const cameraTransform this.scene.getElementById(camera).getComponent(transform)const modelTransform this.scene.getElementById(yourNodeId).getComponent("transform");if (cameraTransform.worldPosition.distanceTo(modelTransform.worldPosition…

pip install albumentations安装下载遇19kB/s超级慢细水管解决办法

albumentations 是一个用于图像增强的 Python 库&#xff0c;它提供了丰富的图像变换功能&#xff0c;可以用于数据增强&#xff0c;从而提高深度学习模型的泛化能力。 直接安装命令&#xff1a; pip install albumentations但是如果半夜遇到这种19kB/s的下载速度 为头发着想&…