处理VFS对象以及标准函数(生动理解文件系统)

一、处理VFS对象

文件系统操作

        首先,我们从标准库用来与内核通信的系统调用来研究。尽管文件操作对所有应用程序来说都属于标准功能,但是对于文件系统的操作只限于少量几个系统程序,即用于装载和卸载文件系统的mount和umount程序。

文件系统和目录树的区别

        用户正常访问文件是通过目录树的路径来查找,但是从上面的表格中我们发现文件系统也有一个“路径”。原因如下:

        开始的时候,设备节点不包含文件系统,此时设备节点仅提供扇区读写能力,如使用dd命令读写扇区。当使用mkfs.ext4 /dev/sda1命令将磁盘格式化之后,这块磁盘就被创建了下面这些ext4文件系统结构:

  • 超级块(记录文件系统整体信息)
  • inode 表(存储文件元数据)
  • 数据块区域(存储实际文件内容)

        此时这个/dev/sda1就被文件系统组织起来。也就是说,用户既可以从目录树访问文件,也可以从文件系统如磁盘文件系统(直接关联的磁盘设备,如/dev/sda1)访问数据。假如某个磁盘文件系统管理的是/dev/sda1磁盘,但是当我们将这个文件系统挂载到目录树的某个节点的时候,比如/mnt,那么用户就可以通过/mnt开始访问这块磁盘。

注册文件系统

        文件系统的注册发生在格式化磁盘之前,内核启动时,就已经完成操作。在文件系统注册到内核时,文件系统编译为模块,或者持久编译到内核中。fs/super.c中的register_filesystem用来向内核注册文件系统。

        以下结构体用来描述文件系统的结构定义:

        一个文件系统不能注册两次,否则,将新的文件系统的对象至于链表末尾,这样就完成项内核注册。         

1. 文件系统注册的对象
  • 文件系统类型(如 ext4
    • 内核通过 register_filesystem() 函数注册文件系统类型(如 ext4FATNTFS 等),全局仅需注册一次
    • 例如:当内核支持 ext4 时,无论有多少个 ext4 分区(如 /dev/sda1/dev/sdb2),ext4 文件系统类型仅在启动时注册一次。
  • 文件系统实例(如 /dev/sda1
    • 每个磁盘设备(如 /dev/sda1)或存储介质上的文件系统实例(如 U 盘的 ext4 分区),需要通过 mount 命令装载到目录树中,但无需重复注册文件系统类型
2. 为什么不能重复注册同一类型?
  • 内核维护文件系统类型表
    • 内核使用全局的文件系统类型表(如 file_systems 链表)记录所有支持的文件系统类型。
    • 如果尝试注册已存在的类型(如 ext4),内核会报错(如 EEXIST)。
  • 示例
    • 用户执行 mkfs.ext4 /dev/sda1 时,依赖已注册的 ext4 文件系统类型;
    • 若手动调用 register_filesystem(&ext4_fs_type),内核会拒绝重复注册。

 装载(挂载)和卸载

        目录树的装载和卸载比仅仅注册文件系统复杂得多,因为后者只需要像一个链表添加对象,而前者需要对内核的内部数据结构执行很多操作。文件系统的装载由mount系统调用发起。我们需要阐明在现存目录树中装载新的文件系统必须执行的任务。还需要用于描述装载点的数据结构。

  1. 注册文件系统(Register)

    • 这是静态的声明过程,类似在图书馆登记一本书的存在
    • 通过register_filesystem()函数完成
    • 内核将文件系统类型(如 ext4、xfs)添加到全局链表file_systems
    • 此时文件系统还未关联任何存储设备
  2. 挂载文件系统(Mount)

    • 这是动态的激活过程,类似把书从仓库放到书架上
    • 通过mount()系统调用触发
    • 需要完成:
      • 分配内核数据结构(vfsmount、dentry 等)
      • 建立与物理存储的连接
      • 更新目录树结构
      • 设置访问权限和挂载标志

         vfsmount结构:采用一种单一的文件系统层次结构,新的文件系统可以集成到其中,使用mount命令可以查询目录树中各种文件系统的装载情况。

vfsmount结构(文件系统层次结构,包含各种文件系统类型),具体如下:

        将文件系统挂载到一个目录时,挂载点的内容被替换为即将挂载的文件系统的相对根目录的内容 ,前一个目录数据直接消失,直到新文件系统卸载才重新出现。

        vfsmount结构描述一个独立文件系统的挂载信息,每个不同挂载点对应一个独立的vfsmount结构,属于同一个文件系统的所有目录和文件隶属于同一个vfsmount,该vfsmount结构对应于该文件系统顶层目录,即挂载目录。

一、生动理解 vfsmount

可以把 vfsmount 想象成文件系统的 “挂载登记卡”:

  • 作用:每次把一个文件系统(如 U 盘的文件系统、光盘的文件系统)挂载到目录树某个位置(挂载点)时,内核就会创建一个 vfsmount 结构。它专门记录 “这个文件系统从哪里来”“挂载到了哪个目录” 等关键信息,就像快递点登记 “包裹从哪里来”“要放到哪个货架格子”。
  • 与文件系统的关联
    • 每个独立的挂载操作(如把 A 硬盘挂载到 /mnt/a,把 B 硬盘挂载到 /mnt/b)都会生成独立的 vfsmount。内核通过这些 “登记卡” 知道:当访问 /mnt/a 时,要读取 A 硬盘的文件系统;访问 /mnt/b 时,要读取 B 硬盘的文件系统。
    • 同一个文件系统无论有多少目录,都通过同一个 vfsmount 管理。比如,把 U 盘挂载到 /mnt/usb,那么 /mnt/usb 下所有文件和目录都由这个 U 盘的 vfsmount 关联,内核通过它定位到 U 盘的文件系统。

二、挂载点的 “覆盖” 原理

当把一个文件系统挂载到某个目录(如 /mnt)时:

 
  • 挂载前,/mnt 目录下可能有自己的文件(比如原本的 README.txt)。
  • 挂载后,/mnt 会 “显示” 新挂载文件系统的内容(比如光盘里的 srclibs)。这不是原数据消失,而是被 “遮盖” 了 —— 新文件系统的内容覆盖了挂载点的访问入口。只有卸载新文件系统,原来 /mnt 的内容才会重新可见。
  • vfsmount 就是这个 “遮盖” 过程的 “管理员”,它告诉内核:“现在访问 /mnt,要读我登记的这个新文件系统的内容。”

 具体mount源码如下:(新版本内核vfsmount被mount结构体代替)

        文件系统之间的父子关系有上述两个成员链表表示,mnt_mounts表头是子文件系统链表的起点,而mnt_child字段则是用作该链表的链表元素。

        系统中的每个vfsmount示例,通过两种途径标识,一个命名空间的所有挂载的文件系统都保存在namespace->list链表中。使用vfs的mnt_list成员作为链表元素。

超级块管理:

1. 超级块的核心作用:文件系统的 “灵魂档案”
  • 场景类比
    想象你走进一个图书馆,想借书却发现没有任何标识、目录或管理员。这时你根本不知道书放在哪里、有多少本书、书架怎么分配。
    超级块就相当于图书馆的管理员手册,它告诉内核(图书馆管理员):

    • 文件系统(图书馆)的 “总容量”(总书籍数量)
    • “块大小”(每个书架格子的大小)
    • “inode 数量”(书籍的唯一编号总数)
    • 挂载点(图书馆的入口位置)
    • 其他关键参数(如权限、加密方式等)。
  • 没有超级块会怎样?
    内核无法识别文件系统,就像管理员没有手册,不知道图书馆的结构,文件系统会变成 “无法读取的乱码区”。

2. 超级块的具体内容:文件系统的 “户口本”

超级块存储的核心信息包括:

 
  • 基本属性
    • 文件系统类型(如 EXT4、NTFS)
    • 块大小(如 4KB / 块)
    • 总块数、已用块数、可用块数
  • inode 管理
    • inode 总数、已用 inode 数、可用 inode 数
    • inode 块的位置(类似书籍索引区的位置)
  • 挂载信息
    • 挂载点(如/mnt/data
    • 最后一次挂载时间、最后一次检查时间
  • 错误信息
    • 文件系统是否干净卸载(避免下次启动时检查)
    • 错误计数(如磁盘坏块标记)

类比
这相当于手册里详细记录了图书馆的地址(挂载点)、每个书架的格子大小(块大小)、书籍编号范围(inode 数量)、哪些书已借出(已用块)等。

3. 超级块的存储位置与备份机制
  • 物理位置
    超级块通常位于文件系统的起始扇区(类似图书馆入口处的公告栏)。例如,EXT4 文件系统的超级块在第 1 块。
  • 备份机制
    为防止超级块损坏导致文件系统瘫痪,许多文件系统(如 EXT4)会在多个位置存储备份超级块。例如,每隔若干块就会复制一次超级块,类似图书馆在不同楼层存放管理员手册副本。
    恢复场景
    当主超级块损坏时,工具(如e2fsck)可以从备份中恢复,就像管理员丢失手册后,从其他楼层的副本中找回信息。

        在挂载新的文件系统时,vfsmount并不是唯一需要在内存(由于是内存不是持久化,这也很好的说明mount挂载,系统重启后失效)中创建的结构。挂载操作开始于超级块的读取。

         s_op指向一个包含了函数指针的结构体,该结构按熟悉的VFS方式,提供一个一般性的接口,用于处理超级块相关的操作。操作的实现必须由底层文件系统的代码提供。

超级块管理:file_system_type对象当中保存的read_super函数指针返回一个类型 super_block的对象。用于在内存中表示一个超级块,它是借助底层实现产生的。

1. 注册文件系统:商场的 “开业许可登记”

在操作系统里注册一个文件系统,就好比在现实中为一个商场申请开业许可。

商场场景

当你想要开一家商场时,首先要到相关部门进行登记注册,说明你要开的商场是什么类型的,比如是购物中心、批发市场还是奥特莱斯等。在这个过程中,你要提交一些关于商场的基本信息,例如商场的规模、能容纳的店铺数量、经营模式等。

操作系统场景

在操作系统中,当我们注册一个文件系统(如 ext4)时,会创建一个 file_system_type 对象。这个对象就像是商场的 “类型登记信息”,它包含了该文件系统的各种特征和操作函数指针,其中就有 read_super 函数指针。这个注册过程相当于告诉操作系统:“我要使用这种类型的文件系统啦!”

2. 挂载文件系统:商场选定 “开业地点”

挂载文件系统类似于商场选定一个具体的开业地点。

商场场景

商场登记好类型后,需要选择一个合适的地理位置来建造和运营。这个地点就像是文件系统的挂载点,它决定了商场在城市中的具体位置,人们可以通过这个位置找到商场并进入购物。

操作系统场景

在操作系统中,我们使用 mount 命令将文件系统挂载到一个特定的目录(挂载点)上。这一步操作触发了后续填充超级块的流程,就像商场选定地点后,开始着手准备商场内部的各项设施和管理规则。

3. 调用 read_super 函数:商场的 “规划设计”

当挂载文件系统时,会调用 file_system_type 对象中的 read_super 函数。这个过程就像是商场的规划设计阶段。

商场场景

商场选定地点后,需要聘请专业的设计师来进行规划设计。设计师会根据商场的类型(如购物中心)和场地条件,设计出商场的整体布局,包括楼层分布、店铺区域划分、通道设置等。同时,设计师还会制定一些管理规则,比如营业时间、安保措施等。

操作系统场景

read_super 函数就像是这个专业设计师,它会从存储设备(如硬盘)中读取文件系统的相关信息,并在内存中创建一个 super_block 对象。这个 super_block 对象就像是商场的 “规划设计蓝图”,它包含了文件系统的各种关键信息,如块大小、inode 数量、空闲块和 inode 的位图等。

4. 填充超级块:商场的 “设施布置”

创建好 super_block 对象后,就需要填充其中的各项信息,这就如同商场按照规划设计蓝图进行设施布置。

商场场景

商场的设计师完成规划设计后,施工团队会根据蓝图进行实际的建设和设施布置。他们会安装电梯、铺设地板、设置照明设备、划分店铺区域等。同时,还会确定商场的管理团队,制定具体的运营规则和流程。

操作系统场景

read_super 函数会根据存储设备上的文件系统信息,将各种关键数据填充到 super_block 对象中。例如,它会读取文件系统的元数据,确定块的分配情况,将空闲块和已使用块的信息记录在位图中;还会统计 inode 的使用情况,将相关信息存储在 super_block 中。填充完成后,super_block 对象就完整地代表了文件系统的当前状态,操作系统可以根据它来进行文件和目录的管理。

 超级块super_block和挂载结构体mount的区别:

概念super_blockmount
角色文件系统的「元数据仓库」挂载点的「运营管理中心」
存储内容文件系统的核心配置(如块大小、inode 数量、空闲块位图等)挂载点路径、挂载标志、子文件系统链表等
生命周期随文件系统存在(如磁盘分区格式化后即生成随挂载操作创建,卸载时销毁

mount系统调用

        mount系统调用的入口点是sys_mount函数,由sys_mount从用户空间复制到内核空间之后,内核将控制权转移给do_mount.

        do_mount 函数的主要功能是解析用户传入的挂载相关参数,完成必要的权限检查,查找或创建相应的文件系统实例,最后将文件系统挂载到指定的挂载点上,使得用户可以通过该挂载点访问文件系统中的文件和目录。

 

共享子树

        共享子树最核心的特征是允许挂载和卸载事件以一种自动的,可控的方式在不同的amespaces间传递(propagation)。这就意味着,在一个命名空间中挂载光盘的同时也会触发对其他namespace对同一张光盘的挂载。

        在共享子树中,每个挂载点都存在一个名为传递类型(propagation type)的标记,该标记决定了一个namespace中创建或者删除的挂载点是否会传递到其他的namespaces。

共享子树有 4 种传输类型:

  • MS_SHARED:该挂载点及其共享挂载、卸载事件会传递。
  • MS_PRIVATE:与共享挂载相反,标记为 private 的事件不会传递到任何对等组,挂载操作默认使用此标志。
  • MS_SLAVE:传输类型介于 shared 和 private 之间,一个 slave mount 拥有一个 master(共享对等组),且 slave mount 不能将事件传递给 master mount
  • MS_UNBINDABLE:该挂载点不可绑定

在 Linux 内核中,命名空间(Namespace) 是一种强大的资源隔离机制,允许不同的进程组(如容器)看到独立的系统资源视图。结合之前讨论的共享子树(Shared Subtree)场景,我们可以用以下生活化的比喻来理解其作用:

核心作用:资源隔离与共享控制

想象有多个「平行宇宙」(命名空间),每个宇宙都有自己的「地球」(系统资源)。命名空间的作用是:

  1. 隔离性:每个宇宙的地球独立存在,互不干扰。
  2. 可控共享:通过特定规则(如共享子树的传输类型),可以让某个宇宙的「月球」(挂载点)与其他宇宙同步变化。

命名空间在挂载传播中的具体角色

1. 挂载隔离
  • 默认情况下,每个命名空间有独立的挂载树。例如:
    • 宇宙 A 在 /mnt 挂载了硬盘,宇宙 B 看不到这个挂载。
    • 宇宙 B 可以在 /mnt 挂载光驱,与宇宙 A 互不影响。
2. 共享子树的「对讲机」机制
  • 当两个命名空间通过 MS_SHARED 标记建立关联时:
    • 宇宙 A 在 /mnt 挂载新设备,会通过「对讲机」通知宇宙 B 自动同步挂载。
    • 反之,宇宙 B 卸载 /mnt 时,宇宙 A 也会自动卸载。
3. 控制传播方向
  • MS_SLAVE:主宇宙(Master)的挂载变化会传递给从宇宙(Slave),但从宇宙的操作不会反向传播。
    • 比喻:主宇宙的对讲机是「发送端」,从宇宙的对讲机是「接收端」。

命名空间的典型应用场景

1. 容器化(如 Docker)
  • 每个容器运行在独立的挂载命名空间中,默认使用 MS_PRIVATE 隔离。
  • 通过 --mount-propagation=shared 可以让容器间共享挂载点。
2. 系统服务隔离
  • 不同服务(如 Web 服务器、数据库)运行在独立命名空间,避免文件系统操作相互干扰。
3. 特权分离
  • 普通用户进程无法访问系统命名空间的挂载点,提升安全性。

二、 标准函数

        VFS层提供的有用资源是用于读写数据的标准函数。这些操作对所有文件系统来说,在一定程度上都是相同的。如果数据所在的块是已知的,则首先查询页缓存。如果数据并未保存在其中,则向对应的块设备发出读请求。如果每个文件系统都需要实现这些操作,则会导致代码大量复制,我们应该防止这种情况的发生。

        常用VFS与read/write系统调用,如vfs_read和vfs_write。

         VFS(虚拟文件系统)是物理文件系统与服务之间的接口层,向下对文件系统提供标准接口,方便其他文件系统移植,向上对应用层提供标准文件操作接口,使open()、read()、write()等系统调用可以跨越各种文件系统和不同介质进行。

 

 VFS对象以及数据结构

        超级块对象 super_block,对应已装载的文件系统;索引节点对象 inode,对应介质上的一个文件;目录项对象 dentry,对应一个目录项;文件对象 file,对应进程所打开的文件。所有定义在 linux/fs.h

        超级块对象:用来描述整个文件系统的信息,每个具体的文件系统都有自己的超级块,所有超级块对象以双向循环链表的形式连接,超级块对象在文件系统装载时创建,保存在内存中,在文件系统卸载时它会自动删除。

        索引节点对象:索引节点对象包含内核在操作文件或目录时需要的全部信息。

        目录项对象:目录项对象没有对应的磁盘数据结构(三种状态:被使用、未使用、负状态)。

        文件对象:文件对象表示进程已打开的文件。由 open() 系统调用创建,由 close() 系统调用删除,多个进程同时打开和操作同一对象时,存在多个对应的文件对象。

VFS层调用流程

VFS层读操作调用流程

1. 系统调用入口
  • 当用户程序发起读操作时,系统调用sys_read被触发。
2. VFS层接口调用
  • sys_read会调用到VFS层的_vfs_read接口。
  • _vfs_read根据文件的file_operations结构体中的readread_iter方法进行调用。
3. 具体文件系统操作
  • 如果文件的file_operations结构体中定义了read方法,则直接调用该方法。
  • 如果定义了read_iter方法,则调用new_sync_read函数,该函数会进一步调用read_iter方法。
  • 如果两者都没有定义,则返回EINVAL错误。
4. 通用读操作处理
  • new_sync_read函数会初始化一些必要的数据结构,如ioveckiocbiov_iter
  • 然后调用read_iter方法进行实际的读操作。
  • 读操作完成后,更新文件的读取位置*ppos,并返回读取的字节数。
5. 通用读操作实现
  • generic_file_read_iter是VFS层提供的一个通用读操作实现。
  • 该函数会根据读取的字节数和文件的映射信息,调用do_generic_file_read函数进行实际的数据读取。
  • do_generic_file_read函数会处理数据的缓存和实际读取操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值