IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> VFS之普通文件系统安装 -> 正文阅读

[数据结构与算法]VFS之普通文件系统安装

1. VFS层安装普通文件系统分析

之所以称之为普通安装是因为接下来讨论的是将一个文件系统安装在一个已安装文件系统之上的情形。使用 mount() 系统调用来安装一个普通文件系统,将会调用它的服务例程 sys_mount() 函数。

1.1 sys_mount()

/**
 * @dev_name:	文件系统所在的设备文件的路径名
 * @dir_name:	安装点路径名
 * @type:		文件系统类型
 * @flags:		安装标志
 * @data:		指向一个与文件系统相关的数据结构的指针(可能为NULL)
 */	
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
			  char __user * type, unsigned long flags, void __user * data)
			  
		/* 把参数值拷贝到内核缓冲区 */	
		copy_mount_options(type, &type_page);
		getname(dir_name);
		copy_mount_options(dev_name, &dev_page);
		copy_mount_options(data, &data_page);
		
		/* 调用domount()处理安装操作 */
		do_mount((char *)dev_page, dir_page, (char *)type_page, flags, (void *)data_page);
				struct nameidata nd;
				int mnt_flags = 0;
				/* 根据系统调用传入的标志设置安装标志mnt_flags */

				/* 1. 查找安装点 */
				path_lookup(dir_name, LOOKUP_FOLLOW, &nd);

				/* 2. 检查安装标识,决定做什么 */
				if (flags & MS_REMOUNT)
					/* 省略其他操作 */
				else		
					do_new_mount(&nd, type_page, flags, mnt_flags, dev_name, data_page);
							struct vfsmount *mnt;
		
							/* 安装操作的核心函数,返回一个已安装文件系统描述符,参考1.2 */
							mnt = do_kern_mount(type, flags, name, data);
	
							/* 把已安装文件描述符对象插入到相应链表中,参考1.3 */
							do_add_mount(mnt, nd, mnt_flags, NULL);
				path_release(&nd);
  1. 调用path_lookup()函数搜索安装点路径,将最后一个分量的信息保存在 nameidata 类型的局部变量 nd 中,nd 中包括安装点的目录项和已安装文件描述符等重要信息。
  2. 根据安装标志可以进行不同的操作,例如更改已安装文件系统的安装点等。我们讨论的是用户要安装一个特殊文件系统或存放在磁盘分区中的普通文件系统的情况所以调用 do_new_mount() 函数。

1.2 do_kern_mount()

/**
 * @fstype:	文件系统类型
 * @flags:	安装点路径名
 * @name:	存放文件系统的块设备路径名或特殊问及那系统类型名
 * @data:	
 */	
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
		/* 1. 根据文件系统类型搜索文件系统类型链表 */
		struct file_system_type *type = get_fs_type(fstype);
		struct vfsmount *mnt;
		
		mnt = vfs_kern_mount(type, flags, name, data);
				struct vfsmount *mnt;
				/* 分配一个新的已安装文件系统描述符 */
				mnt = alloc_vfsmnt(name);
						struct vfsmount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
						mnt->mnt_devname = newname;
				
				/* 读取并填充超级块对象 */
				type->get_sb(type, flags, name, data, mnt);
				
				/* 2. 后面这两个值会重新指向正确的对象 */
				mnt->mnt_mountpoint = mnt->mnt_root;
				mnt->mnt_parent = mnt;

  1. 在注册的文件系统类型链表中搜索将要挂载的文件系统的名字,结果保存在局部变量中。后续需要调用文件系统对象中的 get_sb() 方法获取并填充超级块对象。
  2. 这里进行了初始化,后面在 mnt_set_mountpoint() 中会重新指向正确的对象。

1.3 do_add_mount()

/**
 * @newmnt:		已安装文件系统描述符
 * @nd:			执行搜索安装点的结果
 * @mnt_flags:	安装标志
 * @fslist:		
 */	
int do_add_mount(struct vfsmount *newmnt, struct nameidata *nd,
		 int mnt_flags, struct list_head *fslist)
		/* 1. 当前进程有可能休眠,另一个进程可能在相同的安装点上安装文件系统 */
		while (d_mountpoint(nd->dentry) && follow_down(&nd->mnt, &nd->dentry));
		/* 最近安装的文件系统是否指向当前的命名空间,如果不是,返回错误 */
		if (!check_mnt(nd->mnt));
				return mnt->mnt_ns == current->nsproxy->mnt_ns;
		/* 2. 要安装的文件系统已被安装到此安装点上,返回错误 */	
		if (nd->mnt->mnt_sb == newmnt->mnt_sb &&
			nd->mnt->mnt_root == nd->dentry);
		/* 该安装点是一个符号链接返回错误 */
		if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode));
		
		newmnt->mnt_flags = mnt_flags;
		graft_tree(newmnt, nd);
				attach_recursive_mnt(mnt, nd, NULL);
						/* 设置父安装描述符和安装点 */
						mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
								child_mnt->mnt_parent = mntget(mnt);
								child_mnt->mnt_mountpoint = dget(dentry);
								dentry->d_mounted++;
						/* 将新安装的文件系统描述符添加到相应队列 */
						commit_tree(source_mnt);
								struct vfsmount *parent = mnt->mnt_parent;
								struct vfsmount *m;
								LIST_HEAD(head);
								struct mnt_namespace *n = parent->mnt_ns;
							
								/* 设置已安装文件系统描述符命名空间,并添加到命名空间链表 */
								list_add_tail(&head, &mnt->mnt_list);
								list_for_each_entry(m, &head, mnt_list)
									m->mnt_ns = n;
								list_splice(&head, n->list.prev);
								
								/* 将文件系统描述符添加到哈希表等链表中 */
								list_add_tail(&mnt->mnt_hash, mount_hashtable +
											hash(parent, mnt->mnt_mountpoint));
								list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);		 
  1. 进程可能会发生休眠,这期间另一个进程可能在相同的安装点上安装文件系统设置改变了根文件系统。这样前面获取到的nd的信息有可能已经变化了。

    /* 这里判断安装点的目录项是否已经安装了文件系统 */
    d_mountpoint(nd->dentry)
    		return dentry->d_mounted;
    
    follow_down(&nd->mnt, &nd->dentry)
    		struct vfsmount *mounted;
    	
    		mounted = lookup_mnt(*mnt, *dentry);
    				/* 在哈希表中搜索在此安装点下安装的文件系统描述符 */
    				__lookup_mnt(mnt, dentry, 1)
    						struct list_head *head = mount_hashtable + hash(mnt, dentry);
    						for (;;) {
    							p = list_entry(tmp, struct vfsmount, mnt_hash);
    							if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
    								found = p;
    								break;
    							}
    						}
    		/* 如果该安装点安装过文件系统,需要改变一下nd中的目录项和已安装文件系统描述符 */
    		if (mounted) {
    			*mnt = mounted;
    			*dentry = dget(mounted->mnt_root);
    			return 1;
    		}
    		return 0;
    		
    
  2. 同一个文件系统安装到同一个安装点没有意义。

2. 超级块的分配

在安装过程中调用 do_kern_mount() 函数时,通过函数指针调用了 get_sb() 方法,该方法用于分配和填充超级块对象,并将创建好的超级块添加到相应链表中。接下来已Ext2文件系统为例进行说明。

2.1 文件系统类型注册

static struct file_system_type ext2_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext2",
	.get_sb		= ext2_get_sb,		/* 获取并填充超级块的方法 */
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};

static int __init init_ext2_fs(void)
	/* 注册ext2类型文件系统 */
	register_filesystem(&ext2_fs_type);

/**
 * @fs_type:		文件系统类型
 * @flags:			安装标志
 * @dev_name:		存放文件系统的块设备路径名
 * @data:	
 * @mnt:			已安装文件系统描述符
 */	
static int ext2_get_sb(struct file_system_type *fs_type, int flags, 
					   const char *dev_name, void *data, struct vfsmount *mnt)
		/* 分配并初始化一个超级块对象 */
		get_sb_bdev(fs_type, flags, dev_name, data, ext2_fill_super, mnt);

2.2 get_sb_bdev()

该函数分配并初始化一个新的适合于磁盘文件系统的超级块。

/**
 * @fs_type:		文件系统类型
 * @flags:			安装标志
 * @dev_name:		存放文件系统的块设备路径名
 * @data:	
 * @fill_super:		从Ext2磁盘分区读取磁盘超级块
 * @mnt:			已安装文件系统描述符
 */	
int get_sb_bdev(struct file_system_type *fs_type,
				int flags, const char *dev_name, void *data,
				int (*fill_super)(struct super_block *, void *, int),
				struct vfsmount *mnt)
		struct block_device *bdev;
		struct super_block *s;
		
		/* 打开设备名为dev_name的块设备 */
		bdev = open_bdev_excl(dev_name, flags, fs_type);

		/* 查找或者创建超级块,参考2.2.1 */
		s = sget(fs_type, test_bdev_super, set_bdev_super, bdev);	

		/* 1. 判断超级块对象是否是新分配的 */
		if (s->s_root) {
			/* 如果不是新超级块 */
		} else {
			/* 如果是新超级块对象 */
			s->s_flags = flags;
			strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));
			/* 设置超级块的块大小字段 */
			b_set_blocksize(s, block_size(bdev));
			/* 从磁盘分区读取磁盘超级块,参考2.2.2 */
			fill_super(s, data, flags & MS_SILENT ? 1 : 0);
		}	
		simple_set_mnt(mnt, s);
				/* 设置已安装文件系统描述符的超级块对象 */
				mnt->mnt_sb = sb;
				/* 设置已安装文件系统描述符的根目录项 */
				mnt->mnt_root = dget(sb->s_root);					   
  1. 如果是一个新分配的超级块对象,此时还没有分配对应的根目录项,所以 s_root 还没有指向。如果是从文件系统对象链表中搜索到的超级块,则添加链表前 s_root 已经有所指向。通过此方法可以判断 sget() 函数调用获取的超级块对象是已有的还是新分配的。

2.2.1 sget()

搜索文件系统链表 fs_supers,如果找到一个与块设备相关的超级块,则返回它的地址。否则,分配并初始化一个新的超级块对象,把它插入到文件系统链表和超级块全局链表中,并返回地址。

/**
 *	@type:	文件系统类型
 *	@test:	指向一个用于比较的函数
 *	@set:	指向一个用于设置的函数
 *	@data:	
 */
struct super_block *sget(struct file_system_type *type,
						int (*test)(struct super_block *,void *),
						int (*set)(struct super_block *,void *),
						void *data)
		struct super_block *s = NULL;
		struct list_head *p;
retry:		
		/* 先遍历文件系统下的超级块对象链表,看已有的超级块对象是否符合 */
		if (test) list_for_each(p, &type->fs_supers) {
			struct super_block *old;
			old = list_entry(p, struct super_block, s_instances);
			/* 1. 比较函数 */
			if (!test(old, data))
				continue;
			/* 2. 判断该超级块是否活跃有效 */
			if (!grab_super(old))
				goto retry;
			if (s)
				destroy_super(s);
			return old;
		}	
		/* 超级块链表中没有搜索到符合的超级块对象,则重新分配 */	
		if (!s) {
			s = alloc_super(type);
					struct super_block *s = kzalloc(sizeof(struct super_block),  GFP_USER);
					s->s_count = S_BIAS;
					atomic_set(&s->s_active, 1);
			/* 回到上面重新遍历一次链表 */
			goto retry;
		}
		/* 3. 根据传入的data设置超级块对象 */
		set(s, data);
		s->s_type = type;
		strlcpy(s->s_id, type->name, sizeof(s->s_id));
		/* 新分配的超级块对象添加到链表中 */
		list_add_tail(&s->s_list, &super_blocks);
		list_add(&s->s_instances, &type->fs_supers);
  1. test 实际指向 test_bdev_super() 函数,比较前面调用 open_bdev_excl() 函数获取的块设备驱动与链表中超级块的 s_bdev 对象是否相同。
    /* 此时data指向的是前面获取到的块设备驱动 */
    static int test_bdev_super(struct super_block *s, void *data)
    		return (void *)s->s_bdev == data;
    
  2. 当从超级块链表中获取到满足上面条件的超级块需要进一步判断该超级块是否活跃有效,调用 grab_super() 函数。
    static int grab_super(struct super_block *s)
    		s->s_count++;
    		if (s->s_root) {
    			/* 这里注意调用alloc_super分配初始化超级块时,s_count初始值设置被为S_BIAS */
    			if (s->s_count > S_BIAS) {
    				atomic_inc(&s->s_active);
    				s->s_count--;
    				return 1;
    			}
    		}
    		/* 如果返回0意味着该超级块已经dead */
    		put_super(s);
    		return 0;
    
  3. set() 实际指向 set_bdev_super() 函数,对超级块对象进行设置。将前面调用 open_bdev_excl() 函数获取的块设备驱动,以及设备标志符设置到 s_bdev 和 s_dev 字段中。
    /* 此时data指向的是前面获取到的块设备驱动 */
    static int set_bdev_super(struct super_block *s, void *data)
    		s->s_bdev = data;
    		s->s_dev = s->s_bdev->bd_dev;
    

2.2.2 fill_super填充磁盘超级块信息

因为超级块对象有实际的磁盘超级块与其对应,所以需要读取磁盘的信息来填充超级块的一些字段,fill_super 实际指向 ext2_fill_super() 函数。

static int ext2_fill_super(struct super_block *sb, void *data, int silent)
		struct ext2_sb_info * sbi;
		struct inode *root;

		sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
		/* s_fs_info指向实际文件系统的超级块信息 */
		sb->s_fs_info = sbi;
		/* 魔数设置,该字段可用于区分文件系统 */
		sb->s_magic = le16_to_cpu(es->s_magic);
		/* 设置超级块操作 */
		sb->s_op = &ext2_sops;
		sb->s_export_op = &ext2_export_ops;
		sb->s_xattr = ext2_xattr_handlers;

		/* 获取根索引节点,参考2.2.3 */
		root = iget(sb, EXT2_ROOT_INO);
		
		/* 分配根目录项,参考2.2.4 */
		sb->s_root = d_alloc_root(root);

2.2.3 根索引节点

根索引节点号通常为固定值,EXT2文件系统根目录的索引节点号是2(EXT2_ROOT_INO值为2)。

static inline struct inode *iget(struct super_block *sb, unsigned long ino)
		struct inode *inode = iget_locked(sb, ino);
				struct hlist_head *head = inode_hashtable + hash(sb, ino);
				struct inode *inode;
				
				/* 从icache中搜索 */
				inode = ifind_fast(sb, head, ino);
						struct inode *inode;
						inode = find_inode_fast(sb, head, ino);
								hlist_for_each (node, head) {
									inode = hlist_entry(node, struct inode, i_hash);
									if (inode->i_ino != ino)
										continue;
									if (inode->i_sb != sb)
										continue;
								}
				if (inode)
					return inode;
					
				/* 如果icache中没有搜索到该索引节点 */
				get_new_inode_fast(sb, head, ino);
						struct inode * inode;
						/* 重新分配索引节点 */
						inode = alloc_inode(sb);
								inode->i_sb = sb;
						inode->i_ino = ino;
						/* 索引节点对象添加到相应的链表 */
						list_add(&inode->i_list, &inode_in_use);
						list_add(&inode->i_sb_list, &sb->s_inodes);
						hlist_add_head(&inode->i_hash, head);
						inode->i_state = I_LOCK|I_NEW;
		if (inode && (inode->i_state & I_NEW))
				/* 1. 调用超级块方法读磁盘索引节点 */
				sb->s_op->read_inode(inode);
  1. inode对象和磁盘的索引节点相对应,调用超级块的 read_inode() 方法读取磁盘索引节点,该方法对应 ext2_read_inode() 函数。
    void ext2_read_inode (struct inode * inode)
    		struct ext2_inode * raw_inode = ext2_get_inode(inode->i_sb, ino, &bh);
    		inode->i_mode = le16_to_cpu(raw_inode->i_mode);
    		
    		/* 根据文件类型赋予不同的操作集合 */
    		if (S_ISREG(inode->i_mode)) {
    			/* 如果是普通文件 */
    		} else if (S_ISDIR(inode->i_mode)) {
    			/* 如果是目录文件 */
    			inode->i_op = &ext2_dir_inode_operations;
    			inode->i_fop = &ext2_dir_operations;
    		} else if (S_ISLNK(inode->i_mode)) {
    			/* 如果是链接文件 */
    		} else {
    		}
    

2.2.4 根目录项

struct dentry * d_alloc_root(struct inode * root_inode)
		struct dentry *res = NULL;
		/* 1. */
		static const struct qstr name = { .name = "/", .len = 1 };
		/* 分配目录项对象 */
		res = d_alloc(NULL, &name);
				struct dentry *dentry;
				dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
				dentry->d_name.name = dname;
				memcpy(dname, name->name, name->len);
				dentry->d_flags = DCACHE_UNHASHED;
		res->d_sb = root_inode->i_sb;
		/* 根目录项的父目录项指向自己 */
		res->d_parent = res;
		d_instantiate(res, root_inode);
				list_add(&entry->d_alias, &inode->i_dentry);
				/* 将目录项与索引节点关联 */
				entry->d_inode = inode;
  1. 根目录项的名字也是“/”,这个名字不重要,只有路径查找中使用绝对路径时的首个分量会显示的使用“/”,访问其他目录挂载文件系统的情况要使用安装点的名字而不是“/”。

3. 总结:

  • 如何判断一个目录项是否为安装点:
    • 判断该目录项对象的 d_mounted 字段是否大于0。
    • 搜索已安装文件系统描述符哈希表,遍历链表中的元素,判断元素的 mnt_parent 字段是否等于该目录项对象对应的已安装文件系统描述符;并且 mnt_mountpoint 是否等于该目录项。(参考 follow_down()函数的实现)。简单表示为:
      p->mnt_parent == nd->mnt && p->mnt_mountpoint == nd->dentry
  • 如何获取文件系统类型对象:
    调用 get_fs_type() 函数传入文件系统类型的名字,该函数会遍历 file_systems 链表,找到名字相同的对象。
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:55:27  更:2021-07-25 11:56:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 15:14:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码