《Linux 内核设计与实现》13. 虚拟文件系统
通用文件接口
VFS 使得可以直接使用 open()、read()、write() 这样的系统调用而无需考虑具体文件系统和实际物理介质。
好处:新的文件系统和新类型的存储介质需要挂载时,程序无需重写,甚至无需重新编译。
VFS 将各种不同的文件系统抽象后采用统一的方式进行操作。
文件系统抽象层
可以使用这种通用接口对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层。
为了支持多文件系统,VFS 提供了一个通用文件系统模型,该模型囊括了任何文件系统的常用功能集和行为。
VFS 抽象层之所以能衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的、基本的、概念上的接口和数据结构。
文件系统需要和 VFS 所定义的概念保持一致(其实就是规则、约束)。文件系统提供 VFS 所期望的抽象接口和数据结构,这样内核就可以和任何文件系统协同工作。
在内核中,在文件系统之外的其它部分,并不需要了解其文件系统内部细节:
ret = write(fd, buf, len); // 系统调用
// 其具体实现:sys_write
系统调用是通用 VFS 接口,提供给用户使用。系统调用的具体实现由文件系统中的具体文件实现,实现的是 VFS 所需要的 sys_write() 方法。
Unix 文件系统
- 目录是特殊的文件,即目录文件。
- 根目录:
/
- 文件相关的信息,乘作为文件的元数据,这些元数据被存储到 inode 结构体中。
- 文件系统的相关信息,即文件系统的元数据,被存储在超级块中。
VFS 对象及其数据结构
VFS 采用面向对象思想来设计的。其对象使用结构体表示。
VFS 四个主要的对象类型:
- 超级块对象:代表一个具体的已安装文件系统。
- 索引节点对象:代表一个具体文件。
- 目录项对象:代表一个目录项,是路径的一个组成部分。
- 文件对象:代表由进程打开的文件。
每个对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:
- super_operations 对象:包含内核针对特定文件系统所能调用的方法,比如 write_inode() 和 sync_fs() 等。
- inode_operations 对象:包含内核针对特定文件所能调用的方法,比如 create() 和 link() 等。
- dentry_operations 对象:包含内核针对特定目录所能调用的方法,比如 d_compare() 和 d_delete() 等。
- file_operations 对象:包含进程针对已打开文件所能调用的方法,比如 read() 和 write() 等。
并不止这些对象…
超级块对象
各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。其它并非基于磁盘的文件系统(如基于内存的文件系统 sysfs),它们会在使用现场创建超级块并将其保存到内存中。
超级块的结构体为 super_block,位于 include/linux/fs.h:
对超级块的操作,位于:fs/super.c
超级块对象通过 alloc_super() 函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。
超级块操作
超级块对象 super_block 中的 s_op 域指向超级块的操作函数表。
超级块的操作函数表用 super_operations 结构体表示,位于 include/linux/fs.h
struct super_operations {struct inode *(*alloc_inode)(struct super_block *sb);void (*destroy_inode)(struct inode *);void (*dirty_inode) (struct inode *);int (*write_inode) (struct inode *, struct writeback_control *wbc);void (*drop_inode) (struct inode *);void (*delete_inode) (struct inode *);void (*put_super) (struct super_block *);void (*write_super) (struct super_block *);int (*sync_fs)(struct super_block *sb, int wait);int (*freeze_fs) (struct super_block *);int (*unfreeze_fs) (struct super_block *);int (*statfs) (struct dentry *, struct kstatfs *);int (*remount_fs) (struct super_block *, int *, char *);void (*clear_inode) (struct inode *);void (*umount_begin) (struct super_block *);int (*show_options)(struct seq_file *, struct vfsmount *);int (*show_stats)(struct seq_file *, struct vfsmount *);
#ifdef CONFIG_QUOTAssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
#endifint (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
};
Tips:
sb->s_op->write_super(sb);
// 因为这不是对象,只是将其看成对象
// 结构体无 this 指向
// 因此只能手动传入 sb 对象,间接实现 this 指向 sb
索引节点对象
索引节点对象包含了内核在操作文件或目录时需要的全部信息。
一个索引节点代表文件系统中的一个文件,它也可以是设备或管道这样的特殊文件。但索引节点只有当文件被访问时,才能在内存中创建。
索引节点操作
struct inode_operations {int (*create) (struct inode *,struct dentry *,int, struct nameidata *);struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);int (*link) (struct dentry *,struct inode *,struct dentry *);int (*unlink) (struct inode *,struct dentry *);int (*symlink) (struct inode *,struct dentry *,const char *);int (*mkdir) (struct inode *,struct dentry *,int);int (*rmdir) (struct inode *,struct dentry *);int (*mknod) (struct inode *,struct dentry *,int,dev_t);int (*rename) (struct inode *, struct dentry *,struct inode *, struct dentry *);int (*readlink) (struct dentry *, char __user *,int);void * (*follow_link) (struct dentry *, struct nameidata *);void (*put_link) (struct dentry *, struct nameidata *, void *);void (*truncate) (struct inode *);int (*permission) (struct inode *, int);int (*check_acl)(struct inode *, int);int (*setattr) (struct dentry *, struct iattr *);int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);ssize_t (*listxattr) (struct dentry *, char *, size_t);int (*removexattr) (struct dentry *, const char *);void (*truncate_range)(struct inode *, loff_t, loff_t);long (*fallocate)(struct inode *inode, int mode, loff_t offset,loff_t len);int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,u64 len);
};
目录项对象
目录项对象没有对应的磁盘数据结构,VFS 根据字符串形式的路径在内存中创建它,因此目录项对象并非真正保存在磁盘上。可以看到该结构体也没有磁盘相关的域。
目录项状态
状态有三:被使用、未被使用、负状态。
**被使用:**一个被使用的目录项对应一个有效的索引节点(即 d_inode 指向相应的索引节点)并且表面该对象存在一个或多个使用者(d_count 为正)。一个目录项处于被使用状态,意味着它正被 VFS 使用并且指向有效的数据,因此不能被丢弃。
未被使用: 一个未被使用的目录项对应一个有效的索引节点(d_inode 指向一个索引节点),但是应指明 VFS 当前并未使用它(d_count 为 0)。该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时再使用它。以后若再需要它,就不必重新创建。若要回收内存,也可以撤销未使用的目录项。
负状态: 其实就是无效目录项,一个负状态的目录项没有对应的有效索引节点(d_inode 为 NULL),因为索引节点已被删除,或路径不在正确,但目录项仍然可以保留,以便快速解析以后的路径查询。
目录项对象释放后也可以保存到 slab 对象缓存中。此时,任何 VFS 或文件系统代码都没有指向该目录项对象的有效引用。
目录项缓存
内核将目录项对象缓存在目录项缓存中。
目录项缓存包括三个主要部分:
目录项操作
include\linux\dcache.h
struct dentry_operations {int (*d_revalidate)(struct dentry *, struct nameidata *);int (*d_hash) (struct dentry *, struct qstr *);int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);int (*d_delete)(struct dentry *);void (*d_release)(struct dentry *);void (*d_iput)(struct dentry *, struct inode *);char *(*d_dname)(struct dentry *, char *, int);
};
文件对象
VFS 的文件对象表示进程已打开的文件。文件对象是已打开的文件在内存中的表示。该对象(切记,不是具体的文件,即不是物理的,而只是存在于内存中的,因此 VFS 的文件对象没有对应的磁盘数据)由相应的 open() 系统调用创建,由 close() 系统调用撤销。
文件操作
/** NOTE:* read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl* can be called without the big kernel lock held in all filesystems.*/
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, struct dentry *, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);
};
和文件系统相关的数据结构
内核定义了用来管理文件系统的数据结构。
file_system_type 用来描述各种特定文件系统类型:
缺图
每种文件系统,不管有多少个实例安装到系统中,还是根本就没有安装到系统中,都只有一个 file_system_type 结构。
当文件系统被安装时,将有一个 vfsmount 结构体在安装点被创建。该结构体用来代表文件系统的实例,即代表一个安装点。
缺图
vfsmount 结构体中维护的各种链表,用于理清文件系统和所有其它安装点间的关系。
vfsmount 结构保存了在安装时指定的标志信息,该信息存储在 mnt_flags 域中。
缺图
和进程相关的数据结构
系统中每个进程都有自己的一组打开的文件。file_struct、fs_strruct、namespace 结构体将 VFS和系统的进程紧密联系在一起。
file_struct 结构体,该结构体由进程描述符中的 files 目录项指向。所有与单个进程相关的信息都保护在其中:
include\linux\fdtable.h
缺图
fs_struct 结构体,该结构由进程描述符的 fs 域指向。它包含了文件系统和进程相关的信息。
include\linux\fs_struct.h
缺图
namespace 结构体,由进场描述符中的 mmt_namespace 域指向。它使得每个进程在系统中都看到唯一的安装文件系统,不仅是唯一的根目录,而且是唯一的文件系统层次结构。
缺图
相关文章:

《Linux 内核设计与实现》13. 虚拟文件系统
通用文件接口 VFS 使得可以直接使用 open()、read()、write() 这样的系统调用而无需考虑具体文件系统和实际物理介质。 好处:新的文件系统和新类型的存储介质需要挂载时,程序无需重写,甚至无需重新编译。 VFS 将各种不同的文件系统抽象后采…...

2021-06-09 51单片机:两个独立按键控制一个led,k1按下松开led闪烁三次,k2按下LED闪烁五次
缘由51单片机:两个独立按键控制一个led,k1按下松开led闪烁三次,k2按下LED闪烁五次_嵌入式-CSDN问答 #include "REG52.h" sbit K1 P1^0; sbit K2 P1^1; sbit LEDP0^0; void main() {unsigned char Xd0,ss0;unsigned int wei0;while(1){if(K10&&Xd0){ss3*2;…...
C/C++ 经典面试算法题
1.打印杨辉三角 1 #include <stdio.h>2 #include <string.h>3 4 int main()5 {6 int x;7 int a[100][100];8 printf("输入行数\n");9 scanf("%d",&x); 10 for(int i 0;i<x;i) 11 { 12 for(int j 0;…...
2023年下学期《C语言》作业0x02-分支 XTU OJ 1068 1069 1070 1071 1072
第一题 #include<stdio.h>int main() {int a;scanf("%d",&a);if(a>90&&a<100) printf("A");else printf("B");return 0; } 没有换行,不然会格式错误 第二题 #include<stdio.h>int main() {int a;s…...

JMeter学习第一、二、三天
首先,我们来了解一下到底什么是接口测试与性能测试: 接口测试 定义 接口测试主要关注系统组件之间的交互,确保各个接口按预期工作。这包括验证传递的数据、数据格式、调用的频率和其他与接口调用相关的任何限制。 目的 确保系统的各个组件可…...

常用的分布式ID解决方案原理解析
目录 前言 一:分布式ID的使用场景 二:分布式ID设计的技术指标 三:常见的分布式ID生成策略 3.1 UUID 3.2 数据库生成 3.3 数据库的多主模式 3.4 号段模式 3.5 雪花算法 前言 分布式ID的生成是分布式系统中非常核心的基础性模块&#…...
echarts3D地图打点
1、echarts地图打点加鼠标移上去显示文字 2、1-3和前面的一样echart3D地图 if (res.code 0) {const resData res.data || [];if (resData.length > 0) {for (var i 0; i < resData.length; i) {let arr new Array(2);arr[0] resData[i].longitude || ""…...
分布式主键算法
目录 一、引言二、常见算法介绍雪花算法(Snowflake Algorithm)特性详解优势劣势 UUID(Universally Unique Identifier)特性详解优势劣势 数据库自增主键特性详解优势劣势 分布式数据库的序列(Sequence)特性…...

暴力破解及验证码安全
1.暴力破解注意事项 1、破解前一定要有一个有郊的字典(Top100 TOP2000 csdn QQ 163等密码) https://www.bugku.com/mima/ 密码生成器 2、判断用户是否设置了复杂的密码 在注册页面注册一个,用简单密码看是否可以注册成功 3、网站是…...

程序无法启动,提示“找不到msvcp140.dll”或“msvcp140.dll缺失报错”解决方法
大家好!今天我来给大家分享一下msvcp140.dll丢失的解决方法。我们都知道,在运行一些软件或游戏时,经常会遇到“找不到msvcp140.dll”的错误提示,这会让我们非常苦恼。那么,这个问题该怎么解决呢?下面我将为…...

【Python查找算法】二分查找、线性查找、哈希查找
目录 1 二分查找算法 2 线性查找算法 3 哈希查找算法 1 二分查找算法 二分查找(Binary Search)是一种用于在有序数据集合中查找特定元素的高效算法。它的工作原理基于将数据集合分成两半,然后逐步缩小搜索范围,直到找到目标元素…...

【MySQL实战45讲-基础篇】
基础篇 基础架构 MySQL的基本架构示意图:MySQL可以分为Server层和存储引擎层两部分。 Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函…...
asp.net core中间件预防防止xss攻击
using System; using System.Text.Json; using System.Text.Json.Serialization;namespace CommonUtils {/// <summary>/// newtonsoft的转化器/// 防止xss攻击/// </summary>public class AntiXssNewtonsoftConverter : Newtonsoft.Json.JsonConverter<string&…...

jvm概述
1、JVM体系结构 2、JVM运行时数据区 3、JVM内存模型 JVM运行时内存 共享内存区 线程内存区 3.1、共享内存区 共享内存区 持久带(方法区 其他) 堆(Old Space Young Space(den S0 S1)) 持久代: JVM用持久带(Permanent Space)实现方法…...

C++简单上手helloworld 以及 vscode找不到文件的可能性原因
helloworld #include <iostream>int main() {std::cout << "hello world!" << std::endl;return 0; }输入输出小功能 #include <iostream> using namespace std; /* *主函数 *输出一条语句 */int main() {// 输出一条语句cout << &q…...

掌动智能:性能压力测试的重要性
采用性能压力测试可以帮助企业预估系统容量、提升用户体验以及降低风险和成本。在软件开发过程中,将性能压力测试纳入测试策略的重要一环,将为企业的成功和用户满意度打下坚实的基础。 性能压力测试的重要性: 一、发现性能瓶颈 性能压力测试能…...

kafka日志文件详解及生产常见问题总结
一、kafka的log日志梳理 日志文件是kafka根目录下的config/server.properties文件,配置log.dirs/usr/local/kafka/kafka-logs,kafka一部分数据包含当前Broker节点的消息数据(在Kafka中称为Log日志),称为无状态数据,另外一部分存在…...
Linux-Centos中配置docker
1.安装yum工具 yum install -y yum-utils 2.配置yam源头 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 3.安装docker yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 4. 查看d…...

IDEA-2023-jdk8 HelloWorld的实现
目录 1 新建Project - Class 2 编写代码 3 运行 1 新建Project - Class 选择"New Project": 指名工程名、使用的JDK版本等信息。如下所示: 接着创建Java类: 2 编写代码 public class HelloWorld {public static void main(S…...

【1++的Linux】之进程(五)
👍作者主页:进击的1 🤩 专栏链接:【1的Linux】 文章目录 一,什么是进程替换二,替换函数三,实现我们自己的shell 一,什么是进程替换 我们创建出来进程是要其做事情的,它可…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...