Linux 内核源码分析---处理 VFS 对象及标准函数

处理VFS对象

注册文件系统:在文件系统注册到内核时,文件系统是编译为模块,或者持久编译到内核中。
在这里插入图片描述
fs/super.c 中的register_filesystem用来向内核注册文件系统。我们可以通过/proc/filesystems查看系统所有的文件系统类型。
一个文件系统不能注册两次,否则,将描述新文件系统的对象置于链表末尾,这样就完成向内核的注册。

static struct file_system_type ext4_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext4",
	.mount		= ext4_mount,
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};

int register_filesystem(struct file_system_type * fs)
{
	int res = 0;
	struct file_system_type ** p;

	BUG_ON(strchr(fs->name, '.'));
	if (fs->next)
		return -EBUSY;
	write_lock(&file_systems_lock);
	//在已注册的全局文件系统链表file_systems中查找,看是否已经注册过
	p = find_filesystem(fs->name, strlen(fs->name));
	if (*p)
		//如果查找到就返回错误,同一个文件系统不能注册两次
		res = -EBUSY;
	else
		//没查找到,直接将该文件系统添加链表末尾
		*p = fs;
	write_unlock(&file_systems_lock);
	return res;
}

static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
	struct file_system_type **p;
	//在已注册的全局文件系统链表file_systems中查找
	for (p = &file_systems; *p; p = &(*p)->next)
		if (strncmp((*p)->name, name, len) == 0 &&
		    !(*p)->name[len])
			break;
	return p;
}

在这里插入图片描述

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

在这里插入图片描述

  • vfsmount 结构:采用一种单一的文件系统层次结构,新的文件系统可以集成到其中,使用 mount 可查询目录树中各种文件系统的装载情况如下:
    在这里插入图片描述
    将文件系统装载到一个目录时,装载点的内容被替换为即将装载的文件系统的相对根目录的内容,前一个目录数据消失,直到新文件系统卸载才重新出现。
    vfsmount 结构描述一个独立文件系统的挂载信息,每个不同挂载点对应一个独立的 vfsmount 结构,属于同一文件系统的所有目录和文件隶属于同一个 vfsmount,该 vfsmount 结构对应于该文件系统顶层目录,即挂载目录。
/*fs/mount.h*/
struct mount {
	`struct` hlist_node mnt_hash;
	struct mount *mnt_parent; 				//装载点所在的父文件系统
	struct dentry *mnt_mountpoint;			//装载点在父文件系统中的dentry(目录项)
	struct vfsmount mnt;
	union {
		struct rcu_head mnt_rcu;
		struct llist_node mnt_llist;
	};
#ifdef CONFIG_SMP
	struct mnt_pcp __percpu *mnt_pcp;
#else
	int mnt_count;
	int mnt_writers;
#endif
	//子文件系统链表
	struct list_head mnt_mounts;	/* list of children, anchored here */
	//链表元素,用于父文件系统中的mnt_mount链表
	struct list_head mnt_child;	/* and going through their mnt_child */
	struct list_head mnt_instance;	/* mount instance on sb->s_mounts */
	//设备名称,例如/dev/dsk/hda1
	const char *mnt_devname;	/* Name of device e.g. /dev/dsk/hda1 */
	struct list_head mnt_list;
	//链表元素,用于特定于文件系统的到期链表中
	struct list_head mnt_expire;	/* link in fs-specific expiry list */
	//链表元素,用于共享装载的循环链表
	struct list_head mnt_share;	/* circular list of shared mounts */
	//从属装载的链表
	struct list_head mnt_slave_list;/* list of slave mounts */
	//链表元素,用于从属装载的链表
	struct list_head mnt_slave;	/* slave list entry */
	//指向主装载,从属装载位于master->mnt_slave_list链表上面
	struct mount *mnt_master;	/* slave is on master->mnt_slave_list */
	//所属的命名空间
	struct mnt_namespace *mnt_ns;	/* containing namespace */
	struct mountpoint *mnt_mp;	/* where is it mounted */
	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
	struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
	struct hlist_head mnt_fsnotify_marks;
	__u32 mnt_fsnotify_mask;
#endif
	int mnt_id;			/* mount identifier */
	int mnt_group_id;		/* peer group identifier */
	int mnt_expiry_mark;		/* true if marked for expiry */
	struct hlist_head mnt_pins;
	struct fs_pin mnt_umount;
	struct dentry *mnt_ex_mountpoint;
};

文件系统之间的父子关系由上述两个成员实现链表表示,mnt_mounts表头是子文件系统链表的起点,而mnt_child字段则用作该链表的链表元素。
系统当中的每个vfsmount实例,通过两种途径标识一个命名空间的所有装载的文件系统都保存在namespace->list链表中。使用vfsmountmnt_list成员作为链表元素。

  • 超级块管理:在装载新的文件系统时, vfsmount 并不是唯一需要在内存中创建结构。装载操作开始于超级块的读取。
/*fs.h*/
struct super_block {
	struct list_head	s_list;	//通过该变量链接到超级块全局链表super_blocks上
	dev_t			s_dev;		//该文件系统对应的块设备标识符
	unsigned char		s_blocksize_bits;
	unsigned long		s_blocksize; //该文件系统的block size
	loff_t			s_maxbytes;	//文件系统支持的最大文件
	struct file_system_type	*s_type; //文件系统类型,比如ext3、ext4
	const struct super_operations	*s_op; //超级块的操作函数
	const struct dquot_operations	*dq_op; //文件系统限额相关操作
	const struct quotactl_ops	*s_qcop; //磁盘限额
	const struct export_operations *s_export_op;
	unsigned long		s_flags; //文件系统的mount标记
	unsigned long		s_iflags;	/* internal SB_I_* flags */
	unsigned long		s_magic;  //该文件系统类型的魔术字
	struct dentry		*s_root; //全局根目录的dentry项
	...
	struct block_device	*s_bdev;  //对应的块设备
	struct backing_dev_info *s_bdi; //超级块对应的BDI设备
	struct mtd_info		*s_mtd;
	//通过该变量,链接到file_system_type中的fs_supers链表
	struct hlist_node	s_instances;
	...
	char			s_id[32];	/* Informational name */
	uuid_t			s_uuid;		/* UUID tune2fs -l可以查看*/

	void 			*s_fs_info;	//指向具体文件系统超级块结构,如ext4_sb_info
	...
	const struct dentry_operations *s_d_op; //该超级块默认的目录项操作函数
	...
	struct shrinker s_shrink;	//每个超级块注册的shrink函数,用于内存回收
	...
	/* AIO completions deferred from interrupt context */
	struct workqueue_struct *s_dio_done_wq;
	...
	//该超级块对应的未在使用dentry列表
	struct list_lru		s_dentry_lru ____cacheline_aligned_in_smp;
	//该超级块对应的未在使用inode列表
	struct list_lru		s_inode_lru ____cacheline_aligned_in_smp;
	...
	/* s_inode_list_lock protects s_inodes */
	spinlock_t		s_inode_list_lock ____cacheline_aligned_in_smp;
	struct list_head	s_inodes;	//该超级块包含的所有inode

	spinlock_t		s_inode_wblist_lock;
	struct list_head	s_inodes_wb;	//该超级块正在回写的inode
};

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

struct super_operations {
   	struct inode *(*alloc_inode)(struct super_block *sb);
   	//将inode从内存和底层的存储介质删除
	void (*destroy_inode)(struct inode *);
	//将传递的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 *);
	void (*put_super) (struct super_block *);
	int (*sync_fs)(struct super_block *sb, int wait);
	int (*freeze_super) (struct super_block *);
	int (*freeze_fs) (struct super_block *);
	int (*thaw_super) (struct super_block *);
	int (*unfreeze_fs) (struct super_block *);
	int (*statfs) (struct dentry *, struct kstatfs *);
	int (*remount_fs) (struct super_block *, int *, char *);
	int (*remount_fs2) (struct vfsmount *, struct super_block *, int *, char *);
	void *(*clone_mnt_data) (void *);
	void (*copy_mnt_data) (void *, void *);
	void (*umount_begin) (struct super_block *);

	int (*show_options)(struct seq_file *, struct dentry *);
	int (*show_options2)(struct vfsmount *,struct seq_file *, struct dentry *);
	int (*show_devname)(struct seq_file *, struct dentry *);
	int (*show_path)(struct seq_file *, struct dentry *);
	int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
	ssize_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);
	struct dquot **(*get_dquots)(struct inode *);
#endif
	int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
	long (*nr_cached_objects)(struct super_block *,
				  struct shrink_control *);
	long (*free_cached_objects)(struct super_block *,
				    struct shrink_control *);
};
  • mount 系统调用:
    mount 系统调用的入口点是 sys_mount 函数,由sys_mount从用户空间复制到内核空间之后,内核将控制转移给do_mount
    在这里插入图片描述
    在这里插入图片描述

  • 共享子树
    共享子树最核心的特征是允许挂载和卸载事件以一种自动的,可控的方式在不同的 namespaces间传递(propagation)。这就意味着,在一个命名空间中挂载光盘的同时也会触发对于其他namespace对同一张光盘的挂载。
    在共享子树中,每个挂载点都存在一个名为传递类型(propagation type)的标记,该标记决定了一个namespace中创建或者删除的挂载点是否会传递到其他的namespaces
    共享子树有四种传递类型:
    MS_SHARED:该挂载点和它的共享挂载和卸载事件。
    MS_PRIVATE:和共享挂载相反,标记为private的事件不会传递到任何的对等组,挂载操作默认使用次标志。
    MS_SLAVE:这个传递类型介于sharedslave之间,一个slave mount拥有一个master(一个共享的对等组),slave mount不能将事件传递给master mount
    MS_UNBINDABLE:该挂载点是不可缩写的。

标准函数

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

常用 VFS与 read/write 系统调用,如vfs_readvfs_write

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

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

在这里插入图片描述

超级块对象 super block:对应已装载的文件系统,用来描述整个文件系统的信息,每个具体的文件系统都有自己的超级块,所有超级块对象以双向链循环链表的形成连接,超级块对象在文件系统装载时创建,保存在内存中,在文件系统超载时它会自动删除。
索引节点对象 inode,对应介质上的一个文件,索引节点对象包含内核在操作文件或目录时需要的全部信息。
目录项对象 dentry:对应一个目录项,目录项对象没有对应的磁盘数据结构(三种状态:被使用、未使用、负状态)。
文件对象file:对应由进程所打开的文件。所有定义在linux/fs.h.文件对象表示进程已打开的文件。由open()系统调用创建,由close()系统调用删除,多个进程同时打开和操作同一对象,存在多个对应的文件对象。

系统调用 _sys_read 会调用到 vfs 层的_vfs_read接口,在 vfs 层接口会调用具体文件系统操作由内核来完成。
从内核文件系统看文件读写过程
sys_write()系统调用:
在这里插入图片描述

open/read 等系统调用

在这里插入图片描述

#include <unistd.h>
size_t write(int flides, const void *buf, size_t nbytes);

write 系统调用,是把缓存区buf中的前nbytes字节写入到与文件描述符flides有关的文件中,返回的是实际写入到文件中的字节数。

#include <unistd.h>
size_t read(int flides, void *buf, size_t nbytes);

read 系统调用,是从与文件描述符flides相关联的文件中读取前nbytes字节的内容,并且写入到数据区buf中,返回的是实际读入的字节数

open 有两种调用方法

int open(const *path, int oflags);

将准备打开的文件或是设备的名字作为参数path传给函数,oflags用来指定文件访问模式,成功返回一个新的文件描述符,失败返回 -1
必需部分:
O_RDONLY:以只读方式打开
O_WRONLY:以只写方式打开
O_RDWR:以读写方式打开
可选部分:
O_CREAT:按照参数mode给出的访问模式创建文件
O_EXCL:与O_CREAT一起使用,确保创建出文件,避免两个程序同时创建同一个文件,如文件存在则open调用失败;
O_APPEND:把写入数据追加在文件的末尾;
O_TRUNC:把文件长度设置为0,丢弃原有内容;

int open(const *path, int oflags, mode_t mode);

在第一种调用方式上,加上了第三个参数 mode,主要是搭配O_CREAT使用,这个参数规定了属主、同组和其他人对文件的文件操作权限。
在这里插入图片描述

int close(int flides);

close系统调用,终止文件描述符 flides 与其对应的文件间的联系,文件描述符被释放,可重新使用。

https://www.cnblogs.com/jimbo17/p/10107318.html
https://blog.csdn.net/qq_39755395/article/details/78516383

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞大圣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值