文件系统(二、ramfs文件系统解析--上)

这个文件系统的介绍,已经尝试写过了几篇,总感觉不知道如何入门,如何讲起,如果从inode,超级块将其感觉有讲的不透彻,这样还是按照书的标准来,上一节介绍文件系统的几个重要的数据结构,我也不觉得看了上一节就会明白文件系统是什么了,所以这一章开始,尝试着分析一个最简单的文件系统的源码ramfs.

2.1 ramfs简介

ramfs是Linux下一种基于RAM做存储的文件系统。在使用过程中你就可以把ramfs理解为在普通的HDD上建立了一个文件系统,而现在HDD被替换成了RAM,因为是RAM做存储所以会有很高的存储效率。由于ramfs的实现就相当于把RAM作为最后一层的存储,所以在ramfs中不会使用swap。你什么时候听过会把HDD上的文件swap到哪里去吗?平常说的swap都是针对内存来说的,而ramfs底层的存储是RAM,虽然不是HDD,但是在Linux看来它就跟HDD一样。但是ramfs有一个很大的缺陷就是它会吃光系统所有的内存,即使你mount的时候指定了大小,同时它也只能被root用户访问。

概念这东西我也是百度,这里贴一下我百度的网址:Linux: 内存文件系统 ramfs

2.2 ramfs源码介绍

简介看完了,那剩下就直接上源码了,作为工程师都比较喜欢直接上源码的可能。

我们打开ramfs目录文件夹:(默认大家都是知道怎么下载,编译内核了)
在这里插入图片描述
看到这么少的文件系统就开心,因为可以自己分析了。

我们知道内核是一个高度模块化的内核,搞过驱动的也知道,我们先找到init函数,看看在那里注册了文件系统,就3个文件,很快就找到了,就在inode.c文件中

在这里插入图片描述
做过驱动的是不是很亲切。

2.3 ramfs源码解析

做了前面的铺垫工作之后,接下来就要到高潮部分了,源码分析,源码分析会分为多个二级标题,尽量保存注册的方式来讲解。

2.3.1 文件系统注册

首先我们来到我们初始化函数:

int __init init_ramfs_fs(void)
{
	static unsigned long once;

	if (test_and_set_bit(0, &once))			//是保证一个文件系统的,
		return 0;
	return register_filesystem(&ramfs_fs_type);		//这个就是往内核中注册文件系统
}
fs_initcall(init_ramfs_fs);

我们往内核方向继续追,看看是怎么登记的:

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);
	p = find_filesystem(fs->name, strlen(fs->name));
	if (*p)
		res = -EBUSY;
	else
		*p = fs;
	write_unlock(&file_systems_lock);
	return res;
}

EXPORT_SYMBOL(register_filesystem);

函数register_filesystem的参数是一个文件类型的指针,比较重要,之后会说,现在先跳过,这个文件系统注册比较重要的是find_filesystem函数,要先在内核维护的文件系统中寻找是否有相同名字的文件系统,如果不存在相同名字的文件系统,就把ramfs加入到系统的文件系统链表。如果存在,则返回忙。

我们接下来看看find_filesystem函数:

static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
	struct file_system_type **p;
	for (p=&file_systems; *p; p=&(*p)->next)
		if (strlen((*p)->name) == len &&
		    strncmp((*p)->name, name, len) == 0)
			break;
	return p;
}

file_systems是内核维护的一个全局变量,用来保存所有登记的文件系统,static struct file_system_type *file_systems。

find_filesystem这个函数我们就看到在遍历查找内核中的文件系统。内核的文件系统的链表是通过next连起来的。

2.3.2 file_system_type数据

static struct file_system_type ramfs_fs_type = {
	.name		= "ramfs",
	.mount		= ramfs_mount,
	.kill_sb	= ramfs_kill_sb,
	.fs_flags	= FS_USERNS_MOUNT,
};

也没什么内容,一个name标注名字,一个mount,表示着使用mount命令会调用这个函数,怎么调用的过程,这篇文章不讲,以后再讲,kill_sb,umount调用的。

2.3.3 ramfs_mount函数

我们都知道,要使用一个文件系统,就要先mount,然后才能使用,现在就看看这个mount究竟做了什么?

struct dentry *ramfs_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
{
	return mount_nodev(fs_type, flags, data, ramfs_fill_super);
}

我们在上一节也讲到,mount一个文件系统,就是要申请一个超级块,然后再往超级块中填数据。调用的mount_nodev就是在super.c中的

struct dentry *mount_nodev(struct file_system_type *fs_type,
	int flags, void *data,
	int (*fill_super)(struct super_block *, void *, int))
{
	int error;
	struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);

	if (IS_ERR(s))
		return ERR_CAST(s);

	error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
	if (error) {
		deactivate_locked_super(s);
		return ERR_PTR(error);
	}
	s->s_flags |= MS_ACTIVE;
	return dget(s->s_root);
}
EXPORT_SYMBOL(mount_nodev);

这个函数一看,确实没发现申请了内存,其实是放到了sget函数里了,我们来看看

/**
 *	sget	-	find or create a superblock
 *	@type:	filesystem type superblock should belong to
 *	@test:	comparison callback
 *	@set:	setup callback
 *	@flags:	mount flags
 *	@data:	argument to each of them
 */
struct super_block *sget(struct file_system_type *type,
			int (*test)(struct super_block *,void *),
			int (*set)(struct super_block *,void *),
			int flags,
			void *data)
{
	struct super_block *s = NULL;
	struct super_block *old;
	int err;

retry:
	spin_lock(&sb_lock);
	if (test) {	    //传参NULL,不进入
		hlist_for_each_entry(old, &type->fs_supers, s_instances) {
			if (!test(old, data))
				continue;
			if (!grab_super(old))
				goto retry;
			if (s) {
				up_write(&s->s_umount);
				destroy_super(s);
				s = NULL;
			}
			return old;
		}
	}
	if (!s) {		//如果超级快为空,就申请超级快
		spin_unlock(&sb_lock);
		s = alloc_super(type, flags);
		if (!s)
			return ERR_PTR(-ENOMEM);
		goto retry;
	}
		
	err = set(s, data);
	if (err) {
		spin_unlock(&sb_lock);
		up_write(&s->s_umount);
		destroy_super(s);
		return ERR_PTR(err);
	}
	s->s_type = type;
	strlcpy(s->s_id, type->name, sizeof(s->s_id));
	list_add_tail(&s->s_list, &super_blocks);
	hlist_add_head(&s->s_instances, &type->fs_supers);
	spin_unlock(&sb_lock);
	get_filesystem(type);
	register_shrinker(&s->s_shrink);
	return s;
}

EXPORT_SYMBOL(sget);

从函数中可以看到,申请一个超级块的内存空间。
函数中的set函数是一个函数指针,先不管,不影响大局。

我们看看后面填充的超级块的几个变量
s_type:指的是file_system_type
s_list:是超级块维护的一个链表

我们往回走,申请了超级块内存之后,接着调用fill_super,这个也是函数指针,是我们ramfs文件系统的:

static const struct super_operations ramfs_ops = {
	.statfs		= simple_statfs,
	.drop_inode	= generic_delete_inode,
	.show_options	= generic_show_options,
};

int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct ramfs_fs_info *fsi;
	struct inode *inode;
	int err;

	save_mount_options(sb, data);

	fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL);
	sb->s_fs_info = fsi;
	if (!fsi)
		return -ENOMEM;

	err = ramfs_parse_options(data, &fsi->mount_opts);
	if (err)
		return err;

	sb->s_maxbytes		= MAX_LFS_FILESIZE;
	sb->s_blocksize		= PAGE_CACHE_SIZE;
	sb->s_blocksize_bits	= PAGE_CACHE_SHIFT;
	sb->s_magic		= RAMFS_MAGIC;
	sb->s_op		= &ramfs_ops;
	sb->s_time_gran		= 1;

	inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
	sb->s_root = d_make_root(inode);
	if (!sb->s_root)
		return -ENOMEM;

	return 0;
}

申请了一个私有变量,但是data是NULL,这里也先不管,剩下的就是填充超级块的数据了,s_magic对每一个文件系统也说都是唯一的。

static const struct inode_operations ramfs_dir_inode_operations = {
	.create		= ramfs_create,
	.lookup		= simple_lookup,
	.link		= simple_link,
	.unlink		= simple_unlink,
	.symlink	= ramfs_symlink,
	.mkdir		= ramfs_mkdir,
	.rmdir		= simple_rmdir,
	.mknod		= ramfs_mknod,
	.rename		= simple_rename,
};

struct inode *ramfs_get_inode(struct super_block *sb,
				const struct inode *dir, umode_t mode, dev_t dev)
{
	struct inode * inode = new_inode(sb);

	if (inode) {
		inode->i_ino = get_next_ino();
		inode_init_owner(inode, dir, mode);
		inode->i_mapping->a_ops = &ramfs_aops;
		mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
		mapping_set_unevictable(inode->i_mapping);
		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
		switch (mode & S_IFMT) {
		default:
			init_special_inode(inode, mode, dev);
			break;
		case S_IFREG:
			inode->i_op = &ramfs_file_inode_operations;
			inode->i_fop = &ramfs_file_operations;
			break;
		case S_IFDIR:
			inode->i_op = &ramfs_dir_inode_operations;
			inode->i_fop = &simple_dir_operations;

			/* directory inodes start off with i_nlink == 2 (for "." entry) */
			inc_nlink(inode);
			break;
		case S_IFLNK:
			inode->i_op = &page_symlink_inode_operations;
			break;
		}
	}
	return inode;
}

紧接着,继续申请一个inode节点,这个inode是文件系统的根inode,所以传参的时候S_IFDIR指定了是一个目录。

先看看new_inode函数,里面藏着东西:

struct inode *new_inode(struct super_block *sb)
{
	struct inode *inode;

	spin_lock_prefetch(&sb->s_inode_list_lock);

	inode = new_inode_pseudo(sb);
	if (inode)
		inode_sb_list_add(inode);
	return inode;
}

void inode_sb_list_add(struct inode *inode)
{
	spin_lock(&inode->i_sb->s_inode_list_lock);
	list_add(&inode->i_sb_list, &inode->i_sb->s_inodes);  //把inode节点添加到内核的s_inodes链表
	spin_unlock(&inode->i_sb->s_inode_list_lock);
}

下面分析一个inode的变量:
inode_init_owner这个函数,填充自己的uid和gid。

inode->i_mapping这个比较重要,好像是缓冲文件的,具体以后再分析

inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; 这个时间都一致

对操作函数赋值
inode->i_op = &ramfs_dir_inode_operations; //inode操作函数
inode->i_fop = &simple_dir_operations; //文件的操作函数

unsigned int __i_nlink; //硬件连接计数,创建的inode节点了之后,硬件的链接技术增1

i_sb_list:挂载到超级块中的inode节点

接下来要创建一个dentry的对象:

struct dentry *d_make_root(struct inode *root_inode)
{
	struct dentry *res = NULL;

	if (root_inode) {
		static const struct qstr name = QSTR_INIT("/", 1);

		res = __d_alloc(root_inode->i_sb, &name);
		if (res)
			d_instantiate(res, root_inode);
		else
			iput(root_inode);
	}
	return res;
}
EXPORT_SYMBOL(d_make_root);

__d_alloc申请一个dentry的内存,并填充一些变量:
因为我们指定的根文件目录名:“/”,所以我们从根目录开始查找文件使用的都是斜杠符作为开始的原因
static const struct qstr name = QSTR_INIT("/", 1);

dentry->d_name.name = dname;

	dentry->d_lockref.count = 1;
	dentry->d_flags = 0;
	spin_lock_init(&dentry->d_lock);
	seqcount_init(&dentry->d_seq);
	dentry->d_inode = NULL;
	dentry->d_parent = dentry;   //dentry的父节点是自己
	dentry->d_sb = sb;			//超级快
	dentry->d_op = NULL;
	dentry->d_fsdata = NULL;
	INIT_HLIST_BL_NODE(&dentry->d_hash);
	INIT_LIST_HEAD(&dentry->d_lru);
	INIT_LIST_HEAD(&dentry->d_subdirs);
	INIT_HLIST_NODE(&dentry->d_u.d_alias);
	INIT_LIST_HEAD(&dentry->d_child);
	d_set_d_op(dentry, dentry->d_sb->s_d_op);

这些到之后还会提到

这里还漏了解释一个函数了:
dentry对象申请完成之后,会执行d_instantiate(res, root_inode);这个函数,dentry的初始化:

void d_instantiate(struct dentry *entry, struct inode * inode)
{
	BUG_ON(!hlist_unhashed(&entry->d_u.d_alias));
	if (inode)
		spin_lock(&inode->i_lock);
	__d_instantiate(entry, inode);
	if (inode)
		spin_unlock(&inode->i_lock);
	security_d_instantiate(entry, inode);
}

static void __d_instantiate(struct dentry *dentry, struct inode *inode)
{
	unsigned add_flags = d_flags_for_inode(inode);

	spin_lock(&dentry->d_lock);
	if (inode)
		hlist_add_head(&dentry->d_u.d_alias, &inode->i_dentry);
	raw_write_seqcount_begin(&dentry->d_seq);
	__d_set_inode_and_type(dentry, inode, add_flags);
	raw_write_seqcount_end(&dentry->d_seq);
	spin_unlock(&dentry->d_lock);
	fsnotify_d_instantiate(dentry, inode);
}

static inline void __d_set_inode_and_type(struct dentry *dentry,
					  struct inode *inode,
					  unsigned type_flags)
{
	unsigned flags;

	dentry->d_inode = inode;
	flags = READ_ONCE(dentry->d_flags);
	flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU);
	flags |= type_flags;
	WRITE_ONCE(dentry->d_flags, flags);
}

dentry->d_inode 指向了inode节点。

这样子就完成了mount命令了,通过代码分析,到目前为止,创建了一个超级块对象,创建了一个dentry和一个inode,后面创建的文件和目录都应该连接到这个根dentry上,根dentry的d_inode节点指向inode,并且还有超级块中有两个指针,根dentry是sb->s_root,根inode是sb->s_inodes的链表中,是不是之后也是怎么搜索的,我们下一节再看。

2.3.4 ramfs_kill_sb函数

有申请就有释放,这篇最后我们在描述一个umount调用的函数:

static void ramfs_kill_sb(struct super_block *sb)
{
	kfree(sb->s_fs_info);		//释放内存
	kill_litter_super(sb);		//释放相关的,这里就不详述了
}

这一篇只要还是讲解了mount函数,mount函数的作用是申请了一个超级块并且填充了数据,这里就对应上一篇一个文件系统对应一个超级块,在超级块下申请了一个根dentry,和一个根inode结点,下一节继续ramfswen文件系统。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要了解Linux内核文件系统的基本原理。文件系统是一个重要的组件,它管理着文件和目录的存储和访问,并提供了对文件系统的各种操作,如读写、查找、删除等。 在Linux内核文件系统主要由两个部分组成:VFS(Virtual File System)和具体的文件系统实现。VFS是一个抽象层,它定义了文件系统的通用接口,包括文件操作、目录操作文件权限控制等。而具体的文件系统实现则是针对不同的存储介质和访问方式进行的优化,例如ext4文件系统适用于硬盘存储,而ramfs则适用于内存存储。 接下来,我们将模仿ramfs示范一个简单的Linux内核文件系统ramfs是一个基于内存的文件系统,它在内存创建一个虚拟文件系统,将文件和目录存储在内存,因此读写速度非常快,但是对于大量数据的存储来说,内存显然是不够的。 我们可以通过以下步骤创建一个ramfs文件系统: 1. 在内核注册文件系统类型 static struct file_system_type ramfs_fs_type = { .owner = THIS_MODULE, .name = "ramfs", .mount = ramfs_mount, .kill_sb = kill_litter_super, }; static int __init init_ramfs_fs(void) { return register_filesystem(&ramfs_fs_type); } static void __exit exit_ramfs_fs(void) { unregister_filesystem(&ramfs_fs_type); } module_init(init_ramfs_fs); module_exit(exit_ramfs_fs); 这里我们定义了一个名为ramfs文件系统类型,并实现了mount和kill_sb函数,分别用于挂载和卸载文件系统。 2. 定义超级块 static struct super_operations ramfs_super_ops = { .statfs = simple_statfs, .drop_inode = generic_delete_inode, .show_options = generic_show_options, }; static int ramfs_fill_super(struct super_block *sb, void *data, int silent) { struct inode *inode; sb->s_blocksize = PAGE_SIZE; sb->s_blocksize_bits = PAGE_SHIFT; sb->s_magic = RAMFS_MAGIC; sb->s_op = &ramfs_super_ops; inode = ramfs_get_inode(sb, NULL, S_IFDIR, 0); sb->s_root = d_make_root(inode); if (!sb->s_root) { printk(KERN_ERR "RAMFS: get root inode failed\n"); return -ENOMEM; } return 0; } static struct dentry *ramfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { return mount_nodev(fs_type, flags, data, ramfs_fill_super); } 在这里,我们定义了超级块的操作函数和填充函数,其填充函数将创建一个根目录的inode,并将其挂载到超级块的根节点上。 3. 定义inode static struct inode *ramfs_get_inode(struct super_block *sb, const struct inode *dir, umode_t mode, dev_t dev) { struct inode *inode = new_inode(sb); if (inode) { inode->i_mode = mode; inode->i_uid = current_fsuid(); inode->i_gid = current_fsgid(); inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_ino = get_next_ino(); switch (mode & S_IFMT) { case S_IFDIR: inode->i_op = &ramfs_dir_inode_operations; inode->i_fop = &ramfs_dir_operations; break; case S_IFREG: inode->i_op = &ramfs_file_inode_operations; inode->i_fop = &ramfs_file_operations; break; default: init_special_inode(inode, mode, dev); break; } } return inode; } 这里我们定义了一个ramfs的inode结构体,并实现了ramfs_get_inode函数,用于创建inode并设置inode的属性和操作函数。 4. 定义文件和目录操作函数 static const struct file_operations ramfs_file_operations = { .read = do_sync_read, .write = do_sync_write, .llseek = generic_file_llseek, .mmap = generic_file_mmap, .fsync = noop_fsync, }; static const struct file_operations ramfs_dir_operations = { .iterate = ramfs_dir_iterate, }; static const struct inode_operations ramfs_file_inode_operations = { .setattr = simple_setattr, }; static const struct inode_operations ramfs_dir_inode_operations = { .create = ramfs_create, .lookup = ramfs_lookup, }; 在这里,我们定义了文件和目录的操作函数,用于对文件和目录进行读写、创建、查找等操作。 5. 注册文件系统 static struct file_system_type ramfs_fs_type = { .owner = THIS_MODULE, .name = "ramfs", .mount = ramfs_mount, .kill_sb = kill_litter_super, }; static int __init init_ramfs_fs(void) { return register_filesystem(&ramfs_fs_type); } static void __exit exit_ramfs_fs(void) { unregister_filesystem(&ramfs_fs_type); } module_init(init_ramfs_fs); module_exit(exit_ramfs_fs); 最后,我们将定义好的文件系统类型注册到内核,完成ramfs文件系统的创建。 总结: 通过以上步骤,我们成功地创建了一个简单的ramfs文件系统。在实际开发,我们可以根据自己的需求,实现更加复杂和高效的文件系统,来满足不同的应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值