当前位置: 首页 > 工具软件 > simplefs > 使用案例 >

SimpleFs文件系统初步三(Mount过程分析)

曾英睿
2023-12-01
static int simplefs_init(void)
{
	int ret;

	sfs_inode_cachep = kmem_cache_create("sfs_inode_cache",
	                                     sizeof(struct simplefs_inode),
	                                     0,
	                                     (SLAB_RECLAIM_ACCOUNT| SLAB_MEM_SPREAD),
	                                     NULL);
	if (!sfs_inode_cachep) {
		return -ENOMEM;
	}

	ret = register_filesystem(&simplefs_fs_type);
	if (likely(ret == 0))
		printk(KERN_INFO "Sucessfully registered simplefs\n");
	else
		printk(KERN_ERR "Failed to register simplefs. Error:[%d]", ret);

	return ret;
}

上述代码:

第一步建立了用于vfs的inode cache:

关于inode和dentry的cache可以看下面的blob。
inode和dentry


第二步注册当前的文件系统:

struct file_system_type simplefs_fs_type = {
	.owner = THIS_MODULE,
	.name = "simplefs",
	.mount = simplefs_mount,
	.kill_sb = simplefs_kill_superblock,
	.fs_flags = FS_REQUIRES_DEV,
};

当前文件系统的类型是"simplefs",mount指针指向的simplefs_mount会在文件系统挂载的时候被调用,kill_sb指针则会在umount文件系统的时候被调用。


第三步挂载的时候获取超级块
simplefs_mount->mount_bdev
看看mount_bdev的传入参数:
fs_type:文件系统类型,当前是simplefs
flags:
dev_name:块设备的名称:例如:/dev/sda,当前我们是/dev/loop0
data:传入指针
fill_super:函数指针,当前指向的是simplefs_fill_super,该函数主要完成的是super block的填充。

struct dentry *mount_bdev(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data,
	int (*fill_super)(struct super_block *, void *, int))
{
		...
		// 通过dev_name设备名(如/dev/loop0)得到对应的block_device结构
        // 首先是一个路径查找的过程,调用kern_path()得到struct path
        // 然后以path.dentry->d_inode为参数调用bd_acquire得到block_device结构
        // 对于路径查找和块设备的问题以后再叙述
	    bdev = blkdev_get_by_path(dev_name, mode, fs_type);
	    ...
		// sget现在现存fs_type->fs_supers链表中查找已经存在的对应的超级块实例(因为一个设备可能已经被挂载过了),fs_supers是file_system_type的成员,它指向一个特定文件系统下的所有超级块实例的链表表头。比较的过程就是遍历fs_supers链表,用每一个super_block->s_bdev和sget的bdev参数做比较,比较他们是不是同一个设备,test_bdev_super就是为了比较bdev而传入的函数参数。
        // 如果没能找到已经存在的超级块实例,那就只能创建一个新的了。此时set_bdev_super函数就是用来把bdev参数设置到新创建的super_block的s_bdev域中。然后设置一下s_type和s_id(s_id这里先初始化为文件系统名,之后如果发现是磁盘设备再改为磁盘设备名),并把这个新的sb加入到全局super_blocks链表,以及此file_system_type的fs_supers链表中。
        // 到此就得到了一个已知的或新的super_block实例,后面的工作都是为了填充这个super_block的内容,并把它加入到各种链表中。
        s = sget(fs_type, test_bdev_super, set_bdev_super, flags | MS_NOSEC,bdev);
        // 这个if是判断得到的sb是一个已经存在的还是一个新的sb,已经存在的sb的s_root已经被初始化了,新的sb的s_root还是空
        if (s->s_root) {
                // 处理已经存在的sb
                // 判断此次的挂载flag是否和之前的挂载有读/写冲突,如果有冲突则返回错误
                if ((flags ^ s->s_flags) & MS_RDONLY) {
                        deactivate_locked_super(s);
                        error = -EBUSY;
                        goto error_bdev;
                }
 
                /*
                 * s_umount nests inside bd_mutex during
                 * __invalidate_device().  blkdev_put() acquires
                 * bd_mutex and can't be called under s_umount.  Drop
                 * s_umount temporarily.  This is safe as we're
                 * holding an active reference.
                 */
                up_write(&s->s_umount);
                // 因为已经有了之前存在的sb,也就是block_dev之前也分配过了,所以这个新的bdev就可以释放了。这里对应前面的blkdev_get_by_path
                blkdev_put(bdev, mode);
                down_write(&s->s_umount);
        } else {
                // 处理新的sb
                char b[BDEVNAME_SIZE];
 
                s->s_mode = mode;
                strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));
                sb_set_blocksize(s, block_size(bdev));
                // 设置了sb的mode, id, blocksize后,就到了fill_super的时候了。fill_super是一个函数参数,它由具体文件系统自己实现,如xfs就实现了xfs_fs_fill_super。
                error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
                if (error) {
                        deactivate_locked_super(s);
                        goto error;
                }
 
                s->s_flags |= MS_ACTIVE;
                bdev->bd_super = s;
        }
 
        return dget(s->s_root);
}

mount_bdev函数的主要逻辑就是这样的

blkdev_get_by_path根据设备名得到block_device结构

sget得到已经存在或者新分配的super_block结构

如果是已经存在的sb,就释放第一步得到的bdev结构

如果是新的sb,就调用文件系统个别实现的fill_super函数继续处理新的sb,并创建根inode, dentry

返回得到的s_root

可以看到fill_super函数将完成mount接下来重要的工作,我们来看一下simplefs是如何做fill_super处理的。


第四步填充超级块

int simplefs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct inode *root_inode;
	struct buffer_head *bh;
	struct simplefs_super_block *sb_disk;
	int ret = -EPERM;

	bh = sb_bread(sb, SIMPLEFS_SUPERBLOCK_BLOCK_NUMBER);
	BUG_ON(!bh);
	//获取磁盘中存放的super block的真实内容
	sb_disk = (struct simplefs_super_block *)bh->b_data;

	printk(KERN_INFO "The magic number obtained in disk is: [%llu]\n",
	       sb_disk->magic);

	if (unlikely(sb_disk->magic != SIMPLEFS_MAGIC)) {
		printk(KERN_ERR
		       "The filesystem that you try to mount is not of type simplefs. Magicnumber mismatch.");
		goto release;
	}

	if (unlikely(sb_disk->block_size != SIMPLEFS_DEFAULT_BLOCK_SIZE)) {
		printk(KERN_ERR
		       "simplefs seem to be formatted using a non-standard block size.");
		goto release;
	}

	printk(KERN_INFO
	       "simplefs filesystem of version [%llu] formatted with a block size of [%llu] detected in the device.\n",
	       sb_disk->version, sb_disk->block_size);

	/* A magic number that uniquely identifies our filesystem type */
	//sb中的魔数和磁盘中的一致
	sb->s_magic = SIMPLEFS_MAGIC;

	/* For all practical purposes, we will be using this s_fs_info as the super block */
	//指向文件系统信息的数据结构,
	sb->s_fs_info = sb_disk;
	//表面当前文件系统最大数据块为4K
	sb->s_maxbytes = SIMPLEFS_DEFAULT_BLOCK_SIZE;
	//实现超级块的指针,不是必须实现的
	sb->s_op = &simplefs_sops;

	//为我们的根节点分配一个Inode
	root_inode = new_inode(sb);
	//设置根节点的Inode编号
	root_inode->i_ino = SIMPLEFS_ROOTDIR_INODE_NUMBER;
	//声明这个Inode是一个目录,因为是根dentry所以它的所在目录为NULL
	inode_init_owner(root_inode, NULL, S_IFDIR);
	//指向所在文件系统的超级块
	root_inode->i_sb = sb;
	//因为需要在根节点下进行操作,因此需要实现节点的操作指针
	/*
	 * 	1.创建一个普通文件,调用create指针;
	 * 	2.创建一个普通文件之前,还需要先调用lookup指针;
	 * 	3.创建一个目录,调用mkdir;
	 */
	root_inode->i_op = &simplefs_inode_ops;
	//Provide Io Operation For UserSpace,eg:readdir
	//是否需要支持文件的操作,比如读写,mmap等
	root_inode->i_fop = &simplefs_dir_operations;
	//Inode的创建时间戳
	root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime =
	    CURRENT_TIME;
	//既然是根节点,自然是需要获取根节点的内容,这里的i_private就是指向根节点信息的
	/*
		信息如下:
		1.根节点号
		2.根节点中数据内容存放的位置-> DATA_BLOCK_BUMBER
		3.根节点下面的子节点个数
	*/
	root_inode->i_private =
	    simplefs_get_inode(sb, SIMPLEFS_ROOTDIR_INODE_NUMBER);

	/* TODO: move such stuff into separate header. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)
	//Super Block需要告知当前文件系统的根dentry,而这个dentry本质上也是来自于一个inode
	sb->s_root = d_make_root(root_inode);
#else
	sb->s_root = d_alloc_root(root_inode);
	if (!sb->s_root)
		iput(root_inode);
#endif

	if (!sb->s_root) {
		ret = -ENOMEM;
		goto release;
	}

	ret = 0;
release:
	brelse(bh);

	return ret;
}
 类似资料: