Linux 文件系统(二) --- vfs简单分析

linux,vfs · 浏览次数 : 2

小编点评

**ext4 文件系统类型定义** ```c struct super_block { struct list_head s_list; // ...其他成员变量 ... } ``` **ext4_mount 函数** ```c struct mount *ext4_mount(struct dentry * dentry, const char * mount_point, int flags); ``` **ext4_file_operations 结构** ```c struct file_operations { // ...文件操作成员函数 ... } ``` **ext4_file_inode_operations 结构** ```c struct inode_operations { // ...文件操作成员函数 ... } ``` **ext4_sops 结构** ```c struct super_operations { // ...所有fs操作成员函数 ... } ``` **主要成员函数** * `ext4_mount`: 将特定文件系统绑定到vfs。 * `ext4_file_operations`: 提供对文件操作的封装。 * `ext4_file_inode_operations`: 提供对文件信息操作的封装。 * `ext4_sops`: 提供对fs操作的封装。

正文

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

  无

前言


  VFS(Virtual File System)是一种软件抽象,主要还是为了连接用户态、内核态和实际文件系统本身。例如:我们可以write一个字符串到磁盘ext4 fs上的某个文件。

  在linux里面,有各种各样的文件系统,它们实际存放的位置可能是硬盘(ext4fs等)、RAM(sysfs, procfs, devtmpfs等)、网络(nfs等)等等存储介质。





VFS的基础知识


  这里主要介绍VFS的基础知识,以及我们常见的文件操作怎么对应到VFS里面,主要以ext4 fs与vfs的关联为例子.



Directory Entry Cache (dcache)

  在vfs里面提供了一种缓存机制,缓存struct dentry项,这个dcache在内核里面表示了一个文件系统的整个文件目录信息(从根目录开始的一个目录信息),但是作为一种cache数据结构,一般会存在cache miss然后创建对应struct dentry。这样就可以很快的查找到我们传入的一个路径的文件对于的struct dentry项。

  vfs通过我们常见的open接口操作文件时,一般是用路径来标识一个文件名的,那么怎么将我们传入的名字转换成对应的目录/文件信息呢?答案就是上面提到的dcache数据结构,通过查询dcache得到目录/文件信息,这个部分的内容也是open系统调用常常做的事情。

  下面是dcache项的基本定义:

struct dentry {
	/* RCU lookup touched fields */
	unsigned int d_flags;		/* protected by d_lock */
	seqcount_spinlock_t d_seq;	/* per dentry seqlock */
	struct hlist_bl_node d_hash;	/* lookup hash list */
	struct dentry *d_parent;	/* parent directory */
	struct qstr d_name;
	struct inode *d_inode;		/* Where the name belongs to - NULL is
					 * negative */
	unsigned char d_iname[DNAME_INLINE_LEN];	/* small names */
	... ...
	struct super_block *d_sb;	/* The root of the dentry tree */
	... ...
};

  还记得我们在《Linux 文件系统(一) --- ext4文件系统简介》( https://www.cnblogs.com/Iflyinsky/p/18162137 )中提到,目录也是一种文件嘛?文件又是用inode来表示的,那么struct dentry就可以得到对应的struct inode信息了。



struct inode 对象

  大家应该都听过一句话,在unix里面,一切皆文件。那么对于vfs来说,一个独立的文件就是struct inode对象.

/*
 * Keep mostly read-only and often accessed (especially for
 * the RCU path lookup and 'stat' data) fields at the beginning
 * of the 'struct inode'
 */
struct inode {

	... ...
	const struct inode_operations	*i_op;
	struct super_block	*i_sb;
	struct address_space	*i_mapping;	
	... ...
}

  其实这个struct inode和struct ext4_inode有许多相似的属性,他们也有些许关联。

  对于struct inode来说,很重要的就是struct inode_operations,这个代表着我们可以对这个inode进行的操作,其结构大概如下:

struct inode_operations {
	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);

	... ...

	int (*create) (struct mnt_idmap *, struct inode *,struct dentry *,
		       umode_t, bool);
	int (*link) (struct dentry *,struct inode *,struct dentry *);
	int (*unlink) (struct inode *,struct dentry *);

	... ...
}

  根据父目录的对应的lookup函数,我们可以查找对应的文件inode信息。此外,还有创建删除inode节点的方法,这些方法在文件系统装载的时候实现。



struct file 对象

  我们上面讲的struct dentry和struct inode是和对应的文件系统存储的数据是息息相关的。但是实际我们操作文件的第一步是打开文件,对于一个打开的文件,在内核里面使用struct file来标识,其结构如下:

struct path {
	struct vfsmount *mnt;
	struct dentry *dentry;
};

/*
 * f_{lock,count,pos_lock} members can be highly contended and share
 * the same cacheline. f_{lock,mode} are very frequently used together
 * and so share the same cacheline as well. The read-mostly
 * f_{path,inode,op} are kept on a separate cacheline.
 */
struct file {
	
	... ...

	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;
	
	... ...
}

  对于一个file对象来说,除了上面提到的struct dentry和struct inode关联外,还有一个重要的结构是:struct file_operations,对于这个结构来说,大家应该非常的熟悉,包含了open/close/read/write等等接口:

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 *);
	
	... ...

	int (*open) (struct inode *, struct file *);

	... ...

  到这里,其实我们vfs我们常见用到的基本就讲完了,串起来是说就是open一个文件,创建一个struct file对象,关联一个struct dentry和struct inode,这时就可以对文件进行操作了.





VFS的深入知识


  



FileSystem 的注册和取消注册

  我们前面提到了,vfs最终会对接到实际文件系统本身,那么VFS支持哪些FS呢?在linux的/proc/filesystems文件中,存放了当前注册到vfs的所有支持的FS。

  下面我们介绍文件系统的注册和取消注册:

/*
 * Filesystem context for holding the parameters used in the creation or
 * reconfiguration of a superblock.
 *
 * Superblock creation fills in ->root whereas reconfiguration begins with this
 * already set.
 *
 * See Documentation/filesystems/mount_api.rst
 */
struct fs_context {
	const struct fs_context_operations *ops;
	struct mutex		uapi_mutex;	/* Userspace access mutex */
	struct file_system_type	*fs_type;
	void			*fs_private;	/* The filesystem's context */
	void			*sget_key;
	struct dentry		*root;		/* The root and superblock */

	... ...
}

struct file_system_type {
        const char *name; //文件系统名字,例如ext4
        int fs_flags;
        int (*init_fs_context)(struct fs_context *);
        const struct fs_parameter_spec *parameters;
        struct dentry *(*mount) (struct file_system_type *, int,
                const char *, void *);
        void (*kill_sb) (struct super_block *);
        struct module *owner;
        struct file_system_type * next;
        struct hlist_head fs_supers;
		
		... ...
};


#include <linux/fs.h>
extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);

  在register_filesystem/unregister_filesystem函数里面,主要是对内核里面的file_system_type变量进行链表操作,注册就是增加链表节点,取消注册就是删除链表节点。



FileSystem的挂载和卸载

  这里我们先举个例子,对于linux来说,内核启动后第一个挂载的文件系统,也就是挂载在/根目录的文件系统。如果大家观察过一些linux的启动相关信息,有个常见的问题就是:

Error: root fs cannot be detected。

  这个问题就是内核没有找到适合的根文件系统来加载,一般来说失败后会自动进入一个叫做initramfs的文件系统,方便进行诊断。

  对于用户挂载和卸载文件系统来说,一般我们是使用mount/umount命令,其和内核启动后挂载第一个文件系统的操作类似,其实我们执行mount命令的时候,对调用对应的file_system_type的mount函数(看上文中的file_system_type有一个mount函数)来完成挂载的操作。

  首先我们来看看mount命令是怎么到file_system_type中的mount函数的。我们来看看调用序列:

  1. mount命令调用userspace 中的mount函数
  2. 接着调用sys_mount
  3. 接着调用do_mount
  4. 接着调用path_mount
  5. 接着调用do_new_mount,在这里我们会根据参数,获取或者创建struct file_system_type对象。(linux kernel v4.20.17)
  6. 接着调用vfs_kern_mount。(linux kernel v4.20.17)
  7. 接着调用mount_fs,在这里面通过struct file_system_type对象调用ext4_mount函数。(linux kernel v4.20.17)

  上面的操作完毕, 我们得到一个struct vfsmount 和 struct mount 对象,这个对象代表了一个文件系统的挂载基本信息。struct mount 中的mnt_instance指向的是ext4 fs 的root dentry中的super_block存放的链表。意思就是,创建好的一个文件系统,其挂载点信息可以在root dentry中找到。

struct vfsmount {
	struct dentry *mnt_root;	/* root of the mounted tree */
	struct super_block *mnt_sb;	/* pointer to superblock */
	int mnt_flags;
	struct mnt_idmap *mnt_idmap;
} __randomize_layout;

struct mount {
	struct hlist_node mnt_hash;
	struct mount *mnt_parent;
	struct dentry *mnt_mountpoint;
	struct vfsmount mnt;
	... ...
	struct list_head mnt_instance;	/* mount instance on sb->s_mounts */
	... ...
}

  下面我们来讲讲ext4的file_system_type定义中,ext4_mount的调用。

  注意,file_system_type的mount接口在新的内核版本中被废弃了,因为有新的mount api实现,所以在5.17-rc1中,ext4_mount这个函数无了。下面是linux kernel中这个提交的commit内容:

ext4: switch to the new mount api
Add the necessary functions for the fs_context_operations. Convert and
rename ext4_remount() and ext4_fill_super() to ext4_get_tree() and
ext4_reconfigure() respectively and switch the ext4 to use the new api.

One user facing change is the fact that we no longer have access to the
entire string of mount options provided by mount(2) since the mount api
does not store it anywhere. As a result we can't print the options to
the log as we did in the past after the successful mount.

Signed-off-by: Lukas Czerner <lczerner@redhat.com>
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
Link: https://lore.kernel.org/r/20211027141857.33657-13-lczerner@redhat.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>

commit-id:cebe85d570cf84804e848332d6721bc9e5300e07

  下面,我们仍用ext4_mount的函数内容来讲解。对于挂载来说,一般会做如下操作(对于磁盘来说):

  • 根据mount命令的参数,ext4_mount调用mount_bdev函数。
  • 根据mount_bdev函数的dev_name获取struct block_device对象。
  • 通过struct block_device对象,初始化并创建struct super_block对象,将对象放到super_blocks链表中。
  • 返回此文件系统对于的根dentry的引用(例如ext4fs: 根据block group0中的inode table[2]获取根节点,并创建dentry),这个时候我们就可以解析整个文件系统的所有文件目录了。

  从上面的说明我们可以知道,一个struct super_block对象,代表一个挂载的文件系统。其定义如下:

struct super_block {
	struct list_head	s_list;		/* Keep this first */
	dev_t			s_dev;		/* search index; _not_ kdev_t */
	unsigned char		s_blocksize_bits;
	unsigned long		s_blocksize;
	loff_t			s_maxbytes;	/* Max file size */
	struct file_system_type	*s_type;
	const struct super_operations	*s_op;

	... ...
}

  从上面的结构来看,最重要的就是s_op了,这个代表对一个文件系统的一些基本操作方法。此外,对于s_list来说,很明显的表达了struct super_block会被存储到一个链表里面,在linux里面,是存放在 static LIST_HEAD(super_blocks) 变量中的。

struct super_operations {
   	struct inode *(*alloc_inode)(struct super_block *sb);
	void (*destroy_inode)(struct inode *);
	void (*free_inode)(struct inode *);

   	void (*dirty_inode) (struct inode *, int flags);
	int (*write_inode) (struct inode *, struct writeback_control *wbc);
	int (*drop_inode) (struct inode *);
	void (*evict_inode) (struct inode *);

	... ...
}


ext4的各种操作实现

  上面我们提到了struct super_operations、struct inode_operations、struct file_operations这三个重要的操作,对于挂载的ext4fs来说,其实现在ext4中实现,并对应赋值给对应的指针。他们定义分别如下:

const struct file_operations ext4_file_operations = {
	.llseek		= ext4_llseek,
	.read_iter	= ext4_file_read_iter,
	.write_iter	= ext4_file_write_iter,
	.iopoll		= iocb_bio_iopoll,
	.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext4_compat_ioctl,
#endif
	.mmap		= ext4_file_mmap,
	.mmap_supported_flags = MAP_SYNC,
	.open		= ext4_file_open,
	.release	= ext4_release_file,
	.fsync		= ext4_sync_file,
	.get_unmapped_area = thp_get_unmapped_area,
	.splice_read	= ext4_file_splice_read,
	.splice_write	= iter_file_splice_write,
	.fallocate	= ext4_fallocate,
};

const struct inode_operations ext4_file_inode_operations = {
	.setattr	= ext4_setattr,
	.getattr	= ext4_file_getattr,
	.listxattr	= ext4_listxattr,
	.get_inode_acl	= ext4_get_acl,
	.set_acl	= ext4_set_acl,
	.fiemap		= ext4_fiemap,
	.fileattr_get	= ext4_fileattr_get,
	.fileattr_set	= ext4_fileattr_set,
};


static const struct super_operations ext4_sops = {
	.alloc_inode	= ext4_alloc_inode,
	.free_inode	= ext4_free_in_core_inode,
	.destroy_inode	= ext4_destroy_inode,
	.write_inode	= ext4_write_inode,
	.dirty_inode	= ext4_dirty_inode,
	.drop_inode	= ext4_drop_inode,
	.evict_inode	= ext4_evict_inode,
	.put_super	= ext4_put_super,
	.sync_fs	= ext4_sync_fs,
	.freeze_fs	= ext4_freeze,
	.unfreeze_fs	= ext4_unfreeze,
	.statfs		= ext4_statfs,
	.show_options	= ext4_show_options,
	.shutdown	= ext4_shutdown,
#ifdef CONFIG_QUOTA
	.quota_read	= ext4_quota_read,
	.quota_write	= ext4_quota_write,
	.get_dquots	= ext4_get_dquots,
#endif
};

  从上面来,还没有挂载的时候,对于一个ext4fs的各种操作就已经实现了,挂载只是将这些操作实现对应赋值而已。

  这里多说一句,其他的fs也会有对应operations的实现。例如:我们常见的驱动开发的时候,file_operations的填充可以说是基操。





总结


  总的来说,vfs提供了对各种fs的操作的封装。mount命令可以将特定文件系统绑定到vfs。当我们mount一个fs时,可以得到这个fs的root dentry,super_block,mount等结构信息。

  我们根据一个fs的root dentry信息,可以解析出其目录下的所有文件目录结构,从而达到访问特定文件系统、特定设备的文件的目的。

参考文献




打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
qrc_img

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

与Linux 文件系统(二) --- vfs简单分析相似的内容:

Linux 文件系统(二) --- vfs简单分析

PS:要转载请注明出处,本人版权所有。 PS: 这个只是基于《我自己》的理解, 如果和你的原则及想法相冲突,请谅解,勿喷。 环境说明 无 前言 VFS(Virtual File System)是一种软件抽象,主要还是为了连接用户态、内核态和实际文件系统本身。例如:我们可以write一个字符串到磁盘e

Linux 文件系统之 --- ext4文件系统简介

PS:要转载请注明出处,本人版权所有。 PS: 这个只是基于《我自己》的理解, 如果和你的原则及想法相冲突,请谅解,勿喷。 环境说明 无 前言 在linux下面,常见的linux fs就是ext系列,linux里面的vfs也和这个ext fs息息相关。本文主要详解一下ext4 fs的实现原理,并且,

[转帖]Linux fuse用户态文件系统及其libfuse

https://www.jianshu.com/p/abc5524ac18c 为什么要有用户态文件系统 VFS文件系统可知文件系统在内核态的,应用程序操作文件,统一调用内核态的VFS层抽象接口。 突然有一天有一个和文件系统有关的需求,要编写一个特定功能的文件系统,不管是代码编写还是调试都不太方便,就

[转帖]Linux设备与内存单位-扇区、块、段、页(sector、block、segment、page)

每个概念是对不同的对象而言的,但它们有一定的联系 这些概念的分析背景是Linux下的内存页和磁盘结构 扇区 是硬盘等存储设备传送单位,大小一般为512B 块 是VFS和文件系统的传送单位(所以相关设备也成为块设备),大小必须是2的幂,不能超过页的大小。 段 是一个内存页或内存页的一部分,它包含磁盘上

保姆教程系列:小白也能看懂的 Linux 挂载磁盘实操

!!!是的没错,胖友们,保姆教程系列又更新了!!! @目录前言简介一、磁盘分区二、文件系统三、实际操作1. 使用lsblk命令查看新加入的磁盘信息2. 使用fdisk或者cfdisk分区新磁盘,并将分区标记为Linux文件系统类型(83)3. 格式化新分区,使用mkfs命令4. 创建挂载目录,使用m

[转帖]Ceph学习之路(二)之Ceph的工作原理及流程

Ceph学习之路(二)之Ceph的工作原理及流程 https://www.cnblogs.com/linuxk/p/9414920.html 一、RADOS的对象寻址 Ceph 存储集群从 Ceph 客户端接收数据——不管是来自 Ceph 块设备、 Ceph 对象存储、 Ceph 文件系统、还是基于

[转帖]Linux修改文件句柄数及vm.max_map_count、stack size的大小

文章目录 一、修改文件句柄数`1.1.查看当前大小``1.2.临时修改``1.3.永久修改` 二、修改max user processes进程数`2.1.临时修改``2.1.永久修改` 三、调整vm.max_map_count的大小`报错“max virtual memory areas vm.ma

[转帖]Linux磁盘I/O(二):使用vm.dirty_ratio和vm.dirty_background_ratio优化磁盘性能

文件缓存是一项重要的性能改进,在大多数情况下,读缓存在绝大多数情况下是有益无害的(程序可以直接从RAM中读取数据)。写缓存比较复杂,Linux内核将磁盘写入缓存,过段时间再异步将它们刷新到磁盘。这对加速磁盘I/O有很好的效果,但是当数据未写入磁盘时,丢失数据的可能性会增加。 当然,也存在缓存被写爆的

[转帖]linux求数组的交集,shell/bash 交集、并集、差集

方法一(直接用文件名):取两个文本文件的并集、交集、差集 并: sort -m 交: sort -m 差 file1 - file2: sort -m 方法二(用变量参数):取两个文本文件的并集、交集、差集 file1=XXXX file2=YYYY # 并: sort -m # 交: sort -

[转帖]Linux命令之tr命令

一、命令介绍 tr 命令用于转换或删除文件中的字符。tr 指令从标准输入设备读取数据,执行转换(或者压缩、删除)处理,将结果输出到标准输出设备。 二、使用示例 0、示例文件 [root@test1 test]# cat 1.txt The string is used to describe the