当前位置: 首页 > article >正文

【Linux内核系列】:进入文件系统的世界

🔥 本文专栏:Linux
🌸作者主页:努力努力再努力wz

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

那么从本篇文章开始就要进入文件系统的学习了,那么之前的内容主要围绕的是进程的相关概念以及进程控制有关的系统调用接口的介绍,以及最后结合之前所学的知识以及系统调用接口自己用c语言简单实现了一个shell外壳程序,当然这个shell的外壳程序由于之后的文件以及信号的知识还没学,所以实现的shell外壳程序是一个阉割版,有很多功能是缺失的,那么我们在往后的学习中,可以逐渐在我们之前完成的shell外壳的基础上去做相应功能的完善,那么进程是我们Linux操作系统学习上的一道大山,那么我们已经顺利跨越,而今天包括之后讲的文件系统则是第二道大山,那么越往后走,我们对于Linux操作系统的内核就了解的越深入,那么废话不多说,进入我们文件系统的学习

★★★ 本文前置知识
文件权限
进程的概念

重识文件

那么文件这个话题,从我的第一篇Linux文章开始就已经谈论过,而我们应该对于Linux操作系统有一个基本的共识,那么就是我们Linux上的一切事物,都可以一文件的视角来看待,那么我们的各种软件也就是进程,他们都有自己对应的文件,包括最底层的硬件,那么在上层也有自己的文件的映射,比如显示器以及键盘都有自己对应的文件

那么对于这各种类型的文件,那么我们可以按照一个标准来将这所有的文件分为两类来看待,那么就是按照状态来分类,那么我们可以将文件分为打开的文件和未被打开的文件,那么大学的c语言的课程学习中,那么我们就认识了我们可以在代码层面上调用我们的诸如fopen库函数来打开一个文件,然后用fread库函数来读取一个文件的内容

那么我们要打开一个文件,我们就得在代码层面上取调用相应的库函数来打开相应的一个文件,那么我们既然是在代码层面上来实现打开一个文件,所以也就意味着我们打开一个文件只能是通过我们的进程来打开,因为得要运行相应的代码来打开文件,那么运行相应的代码意味着创建一个进程,那么也就说明了打开一个文件的对象一定是我们的进程来打开的,而我们知道进程的元数据是加载到内存中的,那么它要打开一个文件,那么必定是要访问该文件相关的数据,比如要进行读取或者进行写入,那么要访问该文件的数据,那么首先就得将该文件的各种元数据加载到内存上,才能方便进程快速访问获取到文件的相关数据

那么一个进程可以打开不只有一个文件,理论上单个进程与打开的文件的数目比可以是1:n的,而每一个进程都可以打开文件,那么也就意味着我们内存会加载很多文件的相关数据,所以操作系统就必然要管理内存中这些庞大的文件数据,那么它管理的方式也就是我们最熟悉的先描述,再组织的方式,也就是它会为每一个打开的文件建模,也就是定义一个file结构体,每一个打开的文件都会有各自对应的file结构体,那么这些file结构体内部就会封装关于该文件的相关属性,比如文件指针以及文件的偏移量等等,那么每一个file结构体之间就通过一个指针进行串联形成一个双链表的数据结构从而组织起这些文件,那么操作系统管理我们的文件系统就转换为对双链表该数据结构的增删改查即可


我们知道我们整个计算机体系结构是一个层级的结构,最底层是我们的硬件往上依次是我们的驱动层然后操作系统而最上层则是我们的用户,而我们用户要访问底层的相关数据,那么按照这个计算机的层级结构,必然得从上往下贯穿我们的操作系统,那么我们的库函数比如fopen以及fread函数之所以能够访问文件的相关数据,那么它们的内部实现必然就得封装了操作系统提供的系统调用接口,所以我们要在Linux系统上能够访问以及获取和修改文件的数据,那么我们必然就得掌握操作系统提供的相关的系统调用接口
在这里插入图片描述

那么我们对于文件有一个重新的认识之后,那么我们再来认识一下文件操作相关的系统调用接口

文件操作的系统调用接口

1.open系统调用接口

那么我们要对一个文件进行写入或者读取等行为的时候,那么在进行这些行为之前,首先第一步就是我们得打开这个文件,那么就如同你要使用电脑,那么你得先给电脑开机你才能进行比如打游戏或者写博客之类的活动

而这里我们打开一个文件的系统调用接口就是我们的open系统调用接口,那么在介绍我们open系统调用接口怎么使用之前,我们得先认识一下open系统调用接口的一个原理,那么首先open系统调用接口的一个参数就是一个字符指针,那么指向一个字符串,那么该字符串就是我们要打开的目标文件的文件名,而我们的文件系统是按照一棵多叉树的数据结构来组织的,那么我们要寻找定位到我们的目标文件,那么就得从这个多叉树的根节点往下遍历,而多叉树的根节点也就是对应文件系统的根目录,那么定位到目标文件在文件系统中的位置后,下一步便是将其在外部设别也就是磁盘中的元数据加载到内存中,然后创建该文件所对应的file结构体,那么这个过程其实和我们创建一个进程是十分类似的

而我们知道我们打开一个文件肯定是有目的的,也就是你打开该文件所要进行的行为,而open接口的第二个参数就是你要对该文件进行的操作,那么该参数是一个整形,那么该整形的二进制序列中的每一个二进制位就代表着其中一个模式或者行为,比如第一个二进制位为1,那么就代表读模式,第二个二进制位为1就代表写模式,而这一组二进制位对应的整形通常被定义成了宏定义在<fcntl.h>头文件中,那么在编译的预处理阶段会替换成相应的整形,那么一下就是常见的open的宏定义:

  • O_RDONLY:以只读模式打开文件。

  • O_WRONLY:以只写模式打开文件。

  • O_RDWR:以读写模式打开文件。

  • O_CREAT:如果文件不存在则创建文件。需要指定权限参数。

  • O_EXCL:与 O_CREAT 一起使用时,确保文件不存在。如果文件已存在,则 open 调用失败。

  • O_TRUNC:如果文件已存在并且成功打开为写入模式(O_WRONLY 或 O_RDWR),则清空文件。

  • O_APPEND:以追加模式打开文件。写入的数据会被追加到文件末尾。

那么我们这个第二个参数也就是我们打开该文件要进行的行为,那么获取到宏定义之后就会与我们该打开的文件的权限进行检查,那么如果我们该用户不具备对该文件有相应操作的权限,那么它会open失败并且返回一个小于0的值,那么如果满足该权限的话,那么意味着我们可以在open函数之后可以进行相应的操作比如读或者写操作,并且打开之后我们对该文件的操作将不在会重复去检查权限,也就意味着权限的检查只会在open接口调用时进行,那么之后都不会在进行检查,因为会存在效率问题,并且在我们进行open函数还会根据我们的模式对我们的文件的属性进行一定的初始化,比如写模式,那么它会一般将我们该文件的内容清空,并且将文件偏移量给置于文本开始,而追加模式则是从当前文件偏移量所保存的读写位置开始进行

其次我们对于打开的文件进行的操作可能不只有一个,那么我们可以进行结合多个模式来指定对该文件的多个行为,因为我们的模式是对应一组二进序列中特定的二进制位,所以我们可以通过或运算来进行多个模式,例:

O_RDWR | O_CREAT, S_IRUSR | S_IWUSR

​ O_WRONLY | O_CREAT | O_APPEND


而我们的open还有第三个参数,那么如果说我们打开的目标文件不存在,那么我们open则会在我们当前进程的工作目录下创建该文件,那么我们就可以通过第三个参数来初始化该文件的一个权限,而我们知道文件的三个角色也就是拥有者和所属组和其他的权限可以用三个8进制数字来表述,所以我们第三个参数就是传递一个8进制序列


而关于open接口的返回值,那么如果我们因为权限等问题open失败会返回一个小于0的返回值通常是-1,而open调用成功所返回的返回值,那么该返回值可不仅仅是意味着返回成功,它还意味着返回了该进程的文件描述符

那么这里就得引入文件描述符的概念:

我们知道我们一个进程可以打开n个文件,所以我们进程也得管理自己打开的文件,所以在进程对应的task_struct结构体里就有一个字段struct files_struct * files,它是一个指针,指向的是一个指针数组,那么该指针数组也有一个专业术语就是文件描述表,本质就是一个指针数组,那么这个指针数组的每一个元素就指向一个打开的文件的file结构体,而数组的下标就是文件描述符,那么我们该进程每次打开一个进程,那么它会从数组的起始位置也就是下表为0的位置往后线形扫描,看是否有可分配的空位置,那么有,则将该位置的指针指向该文件所对应的file结构体,并且返回该位置的数组下标
在这里插入图片描述
而这里我们每一个进程在被创建的时候,会默认打开三个文件,也就是键盘文件,和显示器文件,和保存错误信息的显示器文件,那么他们会默认在数组的下标为0,1,2这三个位置,所以我们经常说我们编写的c语言文可执行文件会默认打开三个输入输出流,那么这三个输入输出流本质上就是这三个文件,那么任何语言都是在操作系统的上层,那么必定他们都会直接或者间接的打开这三个文件,所以进程之后每打开一个文件都是从下标为3位置处线性往后扫描分配。

-open

头文件:<fcntl.h>

函数原型:

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

2.write系统调用接口

那么我们一旦调用了open系统调用接口,那么它会返回一个文件描述符,那么这个文件描述符就能够让后续的系统调用接口来使用,那么我们的write就是写操作的系统调用接口,那么它的第一个参数是我们的文件描述符,那么第二个参数则是一个字符指针,指向要一个字符串,第三个参数则是写入的字符串的长度,那么这里要注意的就是通常在我们的c语言的规定下,我们的字符串末尾必须带有一个\0来标记字符串结尾,但是我们在向文件写入的时候,不用显示的去写入一个\0,因为\0是c语言为了便于string类的库函数的工作从而这样规定的,但是它不是一个有效信息,所以我们可以不用加\0,所以在写入字符串长度的时候,会有意识的将写入的长度给减一

那么write的返回值则是我们实际写入的长度,那么如果调用失败,那么write会返回-1

-write

头文件:<unistd.h>

函数原型:

size_t write(int fd, const void *buf, size_t count);

3.read系统调用接口

那么read系统调用接口则是用来读取一个文件的内容

那么read系统调用接口的第一个参数则是一个文件描述符,那么它会获取到文件描述符然后利用该进程的文件描述符到指针数组的对应位置去访问到file结构体得到该文件的文件偏移量,然后该文件偏移量所保存的位置往后来进行读取,那么读取的长度则是第二个参数是一个无符号整形的参数n,那么它会往后读取n个单位的长度,然后将其保存到一个任意类型的数组中,因为文件的内容本质就是一个二进制序列,那么我们read实则是获取到该数据对应的二进制序列,那么至于怎么看待解析这个二进制序列,那么则具体看你保存的数组的数据类型

-write

头文件:<unistd.h>

函数原型:

size_t read(int fd, void *buf, size_t count);

4.close系统调用接口

那么我们既然有文件的打开,那么相应的我们就一定对应有文件的关闭,那么close系统调用接口就是关闭一个打开的文件,那么知道我们进程打开的所有文件,那么他们都会被保存带文件描述表当中,那么文件描述表本质上就是一个指针数组,那么其指向一个打开的文件的file结构体,那么我们关闭一个文件,那么就是将该文件在指针数组对应的下标位置的指针给置空,而该打开的文件的file结构体中还有一个属性,那么便是引用计数count,那么我们将该文件对应的指针数组置空之后,我们还为将该文件的引用计数给减一,因为一个文件它与进程的关系可不是一对一的,而是多对多的关系,我们每一个进程可以打开n个文件,那么每一个文件同理也可能被n个进程所打开,那么其中的引用计数count就是记录其被多少个进程所打开,那么一旦引用计数count为0,那么我们操作系统便会释放该文件的资源,其中就包括它在内存当中的元数据以及对应的file结构体

所以我们的close的参数就是一个在文件描述表中范围内指针不为空的数组下标,那么它就会关闭该下标对应的文件,关闭成功返回值为0,失败返回值为-1

-close

头文件:<unistd.h>

函数原型:

int close(int fd);

结语

那么这就是本篇文章的全部内容了,那么其中详细解析了文件的概念,以及与文件操作相关的几个系统调用接口,那么下一篇文章我还会在补充几个系统调用接口,那么有了文件知识储备的基础以及文件的系统调用接口,那么我们就可以重新来认识输出以及输入重定向,并且来完善我们的shell外壳程序

那么我会持续更新,希望你多多关注,如果本篇文章有帮组到你,那么还请三连加关注哦,你的支持就是我创作的最大的动力!在这里插入图片描述


http://www.kler.cn/a/574536.html

相关文章:

  • 激光雷达市场观察2-美国 PLI 发展脉络与核心技术解析2025.3.7
  • 安铂克科技 APPH 系列相位噪声分析仪:高性能测量的卓越之选
  • Sqlserver安全篇之_手工创建TLS用到的pfx证书文件
  • Halcon:HObject与opencv:Mat互转
  • 【目标检测】【CVPR 2025】DEIM:具有改进匹配机制的DETR以实现快速收敛
  • Scala:case class(通俗易懂版)
  • DeepSeek大模型深度解析:架构、技术与应用全景
  • Pandas实现Excel的vlookup并且在指定列后面输出
  • Hadoop项目中的问题(1)——NetworkManager 和 network 服务冲突
  • AutoGen学习笔记系列(七)Tutorial - Managing State
  • 亚信安全发布2024威胁年报和2025威胁预测
  • Ubuntu20.04本地配置IsaacLab 4.2.0的G1训练环境(二):训练与推理
  • 兼容移动端ios,安卓,web端底部软键盘弹出,输入框被遮挡问题
  • C++————类和对象(一)
  • CentOS7安装 FFmpeg
  • 2024最新版python+pycharm安装与配置(mac和window都有讲)
  • 传统架构与集群架构搭建LAMP环境并部署WordPress服务
  • 【LeetCode 热题 100】3. 无重复字符的最长子串 | python 【中等】
  • Potplayer 怎么用鼠标左键单击播放暂停
  • GitHub教程