一文读懂|Linux 虚拟文件系统(VFS)

news2024/9/28 9:25:37

前言

虚拟文件系统是一个很庞大的架构,如果要分析的面面俱到,会显得特别复杂而笨拙,让人看着看着,就不知所云了(当然主要还是笔者太菜),所以这篇博客,以 open() 函数为切入点,来试着分析分析VFS文件系统的运转机理,本文的代码来源于 linux3.4.2。

基础知识

首先我们来看一张图:

(图1)

从这张图中,我们可以看出,系统调用函数并不是直接操作真正的文件系统,而是通过一层中间层,也就是我们说的虚拟文件系统,为什么要有虚拟文件系统?

linux中常见的文件系统有三类:基于磁盘的文件系统;基于内存的文件系统;网络文件系统,(这三类文件系统是共存于文件系统层,为不同类型的数据提供存储服务,这三类文件系统格式是不一样的,也就是说如果不通过虚拟文件系统,直接对真正的文件系统进行读取,有种类型的文件系统,你就得写几种相对应的读取函数),所以说虚拟文件的出现(VFS)就是为了通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系统格式。

VFS的数据结构

VFS依靠四个主要的数据结构和一些辅助的数据结构来描述其结构信息,这些数据结构表现得就像是对象;每个主要对象中都包含由操作函数表构成的操作对象,这些操作对象描述了内核针对这几个主要的对象可以进行的操作。

1、超级块对象

存储一个已安装的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时, 内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个安装实例和一个超级块对象一一对应。超级块通过其结构中的一个域s_type记录它所属的文件系统类型。

struct super_block { //超级块数据结构
        struct list_head s_list;                /*指向超级块链表的指针*/
        ……
        struct file_system_type  *s_type;       /*文件系统类型*/
       struct super_operations  *s_op;         /*超级块方法*/
        ……
        struct list_head         s_instances;   /*该类型文件系统*/
        ……
};

struct super_operations { //超级块方法
        ……
        //该函数在给定的超级块下创建并初始化一个新的索引节点对象
        struct inode *(*alloc_inode)(struct super_block *sb);
       ……
        //该函数从磁盘上读取索引节点,并动态填充内存中对应的索引节点对象的剩余部分
        void (*read_inode) (struct inode *);
       ……
};

2、索引节点对象

索引节点对象存储了文件的相关信息,代表了存储设备上的一个实际的物理文件。当一个 文件首次被访问时,内核会在内存中组装相应的索引节点对象,以便向内核提供对一个文件进行操 作时所必需的全部信息;这些信息一部分存储在磁盘特定位置,另外一部分是在加载时动态填充的。

struct inode {//索引节点结构
      ……
      struct inode_operations  *i_op;     /*索引节点操作表*/
     struct file_operations   *i_fop;  /*该索引节点对应文件的文件操作集*/
     struct super_block       *i_sb;     /*相关的超级块*/
     ……
};

struct inode_operations { //索引节点方法
     ……
     //该函数为dentry对象所对应的文件创建一个新的索引节点,主要是由open()系统调用来调用
     int (*create) (struct inode *,struct dentry *,int, struct nameidata *);

     //在特定目录中寻找dentry对象所对应的索引节点
     struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
     ……
};

3、目录项对象

引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分,不管是目录还是 普通的文件,都是一个目录项对象。如:在路径 /home/source/test.c 中,目录 /homesource 和文件 test.c都对应一个目录项对象。不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS 在遍历路径名的过程中现场将它们逐个地解析成目录项对象。

struct dentry {//目录项结构
     ……
     struct inode *d_inode;           /*相关的索引节点*/
    struct dentry *d_parent;         /*父目录的目录项对象*/
    struct qstr d_name;              /*目录项的名字*/
    ……
     struct list_head d_subdirs;      /*子目录*/
    ……
     struct dentry_operations *d_op;  /*目录项操作表*/
    struct super_block *d_sb;        /*文件超级块*/
    ……
};

struct dentry_operations {
    //判断目录项是否有效;
    int (*d_revalidate)(struct dentry *, struct nameidata *);
    //为目录项生成散列值;
    int (*d_hash) (struct dentry *, struct qstr *);
    ……
};

4、文件对象

文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系。它由 sys_open() 现场创建,由 sys_close() 销毁。文件对象和物理文件的关系有点像进程和程序的关系一样。

当我们站在用户空间来看待 VFS,我们像是只需与文件对象打交道,而无须关心超级块,索引节点或目录项。因为多个进程可以同时打开和操作 同一个文件,所以同一个文件也可能存在多个对应的文件对象。

文件对象仅仅在进程观点上代表已经打开的文件,它 反过来指向目录项对象(反过来指向索引节点)。一个文件对应的文件对象可能不是惟一的,但是其对应的索引节点和 目录项对象无疑是惟一的。

struct file {
    ……
     struct list_head        f_list;        /*文件对象链表*/
    struct dentry          *f_dentry;       /*相关目录项对象*/
    struct vfsmount        *f_vfsmnt;       /*相关的安装文件系统*/
    struct file_operations *f_op;           /*文件操作表*/
    ……
};

struct file_operations {
    ……
    //文件读操作
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ……
    //文件写操作
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ……
    int (*readdir) (struct file *, void *, filldir_t);
    ……
    //文件打开操作
    int (*open) (struct inode *, struct file *);
    ……
};

 

 资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

正篇

经过基础知识点的介绍后,我们开始来探究,当我们通 open() 尝试去打开一个文件的时候,Linux 内部是如何找到对应的存储在硬件上的该文件的数据。

(图2)

(图3)

首先我们来看看上面这两张图,files_struct 主要就是一个 file 指针数组,我们通常说的文件描述符是一个整数,而这个整数正好可以作为下标,从而从 files_struct 中获得 file 结构。

task_struct 为进程描述符,代表的是打开文件的这么一个动作,这里我想表达的知识点:当文件第一次被打开时(打开成功),会建立起如上图所示的联系,返回 fd 文件描述符就这样和底层的存储结构联系在了一起,fd 作为文件描述符,文件作为数据的载体,我们可以将它们理解为密码和保险柜之间的关系,第一打开文件就是相当初始化时设置密码(建立起了密码和保险柜的联系),当我们以后再需要拿取保险柜中的东西时,只需要通过第一次设置的密码就可以对保险柜进程操作。

内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。文件描述表中每一项都是一个指针,指向一个用于描述打开的文件的数据块 ——— file 对象,file 对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的 file 对象。

需要注意的是,file 对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的 file 对象,从而共享这个打开的文件。 file 对象有引用计数,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁 file 对象,因此某个进程关闭文件,不影响与之共享同 一个 file 对象的进程.

下面我们来分析具体的代码。

应用层:

应用程序在操作任何一个文件之前,必须先调用 open() 来打开该文件,即通知内核新建一个代表该文件的结构,并且返回该文件的描述符(一个整数),该描述符在进程内唯一。所用到函数为 open()

int open(const char * pathname,int oflag, mode_t mode )
    /*pathname:代表需要打开的文件的文件名;

       oflag:表示打开的标识 (只读打开/只写打开/读写打开 ...........)

      mode: 当新创建一个文件时,需要指定mode参数(设置权限)
     */

内核层:

当 open() 系统调用进入内核时候,最终调用的函数为:

SYSCALL_DEFINE3(open, const char __user , filename, int, flags, int,mode)

该函数位于 fs/open.c 中,下面将会分析其具体的实现过程。

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
 long ret;
 //判断系统是否支持大文件,即判断long的位数,如果64则表示支持大文件; 
 if (force_o_largefile())
  flags |= O_LARGEFILE;
 
 //完成主要的open工作,AT_FDCWD表示从当前目录开始查找
 ret = do_sys_open(AT_FDCWD, filename, flags, mode);
 /* avoid REGPARM breakage on x86: */
 asmlinkage_protect(3, ret, filename, flags, mode);
 return ret;
}

该函数主要调用 do_sys_open() 来完成打开工作,do_sys_open() 的代码分析如下。

long do_sys_open(int dfd, const char__user *filename, int flags, int mode)
{
 //将欲打开的文件名拷贝到内核中,该函数的分析见下文;
 char *tmp = getname(filename);
 int fd = PTR_ERR(tmp);

 if (!IS_ERR(tmp)) {
  //从进程的文件表中找到一个空闲的文件表指针,如果出错,则返回,见下文说明;
  fd = fd = get_unused_fd();
  if (fd >= 0) {
   //执行打开操作,见下文说明,dfd=AT_FDCWD;
   struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
   if (IS_ERR(f)) {
    put_unused_fd(fd);
    fd = PTR_ERR(f);
   } else {
    fsnotify_open(f);//作用是将 filp 的监控点打开,并将其添加到监控系统中
    //添加打开的文件表f到当前进程的文件表数组中,见下文说明;
    fd_install(fd, f);
   }
  }
  putname(tmp);
 }
 return fd;
}

(图4)

从代码和流程图的分析中我们知道了,fd 和 file 是如何建立联系 (file 对象中包含一个指针,指向 dentry 对象。dentry 对象代表一个独立的文件路径,如果一个文件路径被打开多次,那么会建立多个 file 对象,但它们都指向同一个 dentry 对象。dentry 对象中又包含一个指向 inode 对象的指针。inode 对象代表一个独立文件。因为存在硬链接与符号链接,因此不同的 dentry 对象可以指向相同的 inode 对象。inode 对象包含了最终对文件进行操作所需的所有信息,如文件系统类型、文件的操作方法、文件的权限、访问日期等)。

那我们反向思考一下,现在我们已经得到 fd,如何找到对应 file, 在当前进程中我们保留着文件描述符,文件描述符中(files_structs),文件描述符中又保留着文件描述表(fatable),通过文件描述符表中 file 类型的指针数组对应的 fd 的项,我们可以找到 file

这篇文章到这里就算结束了,还留有一个工作没有完成,如 do_filp_open(dfd, tmp, flags, mode) 是如何得到 file?,这是一个很复杂的过程,以后有空,笔者会尝试着去分析。

原文作者:Linux内核那些事

 

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

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

相关文章

2020年3月全国计算机等级考试真题(C语言二级)

2020年3月全国计算机等级考试真题(C语言二级) 第1题 有以下程序 void fun1 (char*p) { char*q; qp; while(*q!\0) { (*Q); q; } } main() { char a[]{"Program"},*p; p&a[3]; fun1(p); print…

英语学习 Eudic欧路词典 for Mac

欧路词典是一款功能强大的英语学习工具,其多语种支持、海量词库、强大的翻译功能、听力训练和生词本和笔记等特点,使得用户可以方便地进行英语学习和提高英语水平,适用于各种英语学习人员和文化交流人员等不同人群。 1 、全面支持最新Retina…

Salesforce Winter ‘24即将发布!亮点功能抢先看

随着Winter 24脚步的临近,一波增强功能即将面世,Trailblazers的期望值越来越高。在这片云计算的海洋里,一些亮点功能总能在生态系统中引起巨大轰动。 Winter 24发布日期 Winter 24发布的具体日期取决于您的Salesforce实例,主要日…

vue echarts macd指标 完整代码

1 逻辑 给指定的series两个对象 两个对象有相同的xAxisIndex: 2,yAxisIndex: 2, 不同的data {name: "",type: "line",data: data1,xAxisIndex: 2,yAxisIndex: 2,},{name: "",type: "bar",data: data2,xAxisIndex: 2,yAxisIndex: 2,},…

爱荷华州的一个学区正在使用ChatGPT来决定禁止哪些书籍

为了响应爱荷华州最近颁布的立法,管理员们正在从梅森市学校图书馆移除禁书,官员们正在使用ChatGPT帮助他们挑选书籍,根据公报和大众科学. 由州长金雷诺兹签署的禁令背后的新法律是教育改革浪潮的一部分,共和党立法者认为这是保护…

滑块验证码-接口返回base64数据

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言所需包图片示例使用方法提示前言 滑动验证码在实际爬虫开发过程中会遇到很多,不同网站返回的数据也是千奇百怪。这里分享一种接口返回base64格式的情况以及处理方式 所需包 opencv-python、…

【LangChain】Feature Store 特征库 一种新型存储方式 —— 功能存储

Feature Store 特征库 特征库的主要目的是什么?特征库的五个主要组件组成一:Serving 服务组成二:Storage 存储组成三:Transform 转换组成四:Monitoring 监控组成五:Feature Registry 功能注册表 Feature St…

简单学习MySQL

最近,再看数据库相关的知识,用不着学太深,先学一些基本知识,以后需要的时候有个基础,不至于像个无头苍蝇,我学习的网站如下(MySQL 教程 | 菜鸟教程 (runoob.com)) 对MySQL的介绍 My…

[oneAPI] 手写数字识别-VAE

[oneAPI] 手写数字识别-VAE oneAPIVAE模型实现手写数字识别任务定义使用包定义参数加载数据VAE模型与介绍训练过程结果 比赛:https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517 Intel DevCloud for oneAPI:https://devcloud.intel.com/one…

go 中自定义包以及import

目录结构如下 注意ellis这个文件夹是在工作区的src目录下 testpackage.go package testpackageimport ("fmt" )func Test() {fmt.Println("test") }main.go package mainimport ("ellis/testpackage""fmt" )type Name struct {Fir…

电脑PDF转换成PPT?这篇文章给你答案

随着科技的不断发展和进步,电子文档已经成为我们日常工作和学习中不可或缺的一部分。PDF作为一种跨平台的文件格式,以其可靠性和易读性而备受推崇。然而,在某些情况下,我们可能需要电脑PDF转换成PPT,以便更好地展示和编…

塑料玩具en71检测标准

塑料玩具原材料大部是从一些油类中提炼出来的,熟悉的部分pc料是从石油中提炼出来的,pc料在烧的时候有一股汽油味, abs是从煤炭中提炼出来的, abs在烧完灭掉的时候会呈烟灰状,pom是从天然气提炼出来的, pom在烧完的时候会有一股非常臭的瓦斯味,塑料玩具是…

php+echarts实现数据可视化实例

效果&#xff1a; 代码&#xff1a; php <?php include(includes/session.inc); include(includes/SQL_CommonFunctions.inc); ?> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv&quo…

Azure存储账户

存储账户的概念 Azure存储账户是Azure提供的一种云存储解决方案&#xff0c;用于存储和访问各种类型的数据&#xff0c;包括文件、磁盘、队列、表格和Blob&#xff08;二进制大对象&#xff09;数据。存储账户可以基于访问模式和冗余需求来选择不同的类型&#xff0c;以满足应…

网络通信原理应用层(第五十一课)

1)DNS:域名解析系统,端口号TCP或UDP的53 2)域名注册网站 -新网 www.xinnet.com -万网-阿里云 www.net.cn -中国互联 hulian.top 配置通过域名访问网站(NETBASE第七课)_IHOPEDREAM的博客-CSDN博客 2、FTP 1)FTP概述 -文件传输协议 -控制连接:TCP 21 <

.NET6 System.Drawing.Common 通用解决办法

最近有不少小伙伴在升级 .NET 6 时遇到了 System.Drawing.Common 的问题&#xff0c;同时很多库的依赖还都是 System.Drawing.Common &#xff0c;而 .NET 6 默认情况下只在 Windows 上支持使用&#xff0c;Linux 上默认不支持这就导致在 Linux 环境上使用会有问题&#xff0c;…

【校招VIP】测试类型之兼容性测试分析

考点介绍&#xff1a; 兼容性是测试工作里面比较复杂的一种情况&#xff0c;也是校招里面考察的一个重点&#xff0c;需要从屏幕功能&#xff0c;数据&#xff0c;操作系统等多个维度进行分析。 『测试类型之兼容性测试分析』相关题目及解析内容可点击文章末尾链接查看&#x…

极光笔记 | 如何为您的业务开发和训练一个AI-BOT

生成式AI&#xff08;Generative AI&#xff09;是当今科技领域的前沿技术之一。随着数据量的不断增加和计算能力的不断提升&#xff0c;AI技术在企业和个人生活中的应用越来越广泛。AI-BOT&#xff08;以下简称BOT&#xff09;是生成式AI技术的其中一种重要的应用形式&#xf…

【PySide】QtWebEngine网页浏览器打开Flash网页

QWebEngineView 加载 flash插件,可成功显示Flash,如图 说明 QtWebEngine与Chromium版本对应关系 Chromium对Flash的支持 QtWebEngine模块 Qt WebEngine取代了Qt WebKit模块,后者基于WebKit项目,但自Qt 5.2以来没有主动与上游WebKit代码同步,并且在Qt 5.5中已被弃用。有…

智能温度采集器的优势与特点

温度是实验室和医院中最重要的参数之一&#xff0c;影响着设备、仪器、药品、血液等的使用寿命。如果温度不合格&#xff0c;这些物品就会变质&#xff0c;甚至危及人身安全。因此&#xff0c;监控实验室和医院的温度是非常重要的。 那么选择温湿度监控设备有哪些方便之处呢&a…