再次梳理linux文件系统

前言

由于最近在学习文件系统相关知识。为了巩固这部分知识,想办法对其进行实践。本篇文章主要记录一下如何自定义一个文件系统。

主要参考了
《Linux内核探秘》——提供文件系统原理及框架说明
《一个简单地文件系统》——提供较新内核的简单文件系统实现代码。全篇文章以它为例

编译文件系统

自定义的文件系统可以作为一个模块,通过 insmod 的方式注册到内核中。类似编译一个驱动模块,编译一个文件系统模块需要:

  1. 文件系统相关代码
  2. Makefile

文件系统相关代码后续再进行说明,首先关注 Makefile 。

CONFIG_MODULE_SIG=n
ifneq ($(KERNELRELEASE),)
obj-m := myfs.o
else
KDIR:=/lib/modules/$(shell uname -r)/build
all:
	make -C $(KDIR) M=$(PWD) modules
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
endif

install:
	sudo insmod myfs.ko
uninstall:
	sudo rmmod myfs
mount:
	mkdir testmp && sudo mount -t my none ./testmp
umount:
	sudo umount ./testmp && rm -rf ./testmp

其中 KDIR 指定了用于编译模块的内核源码路径。意味着,需要根据目标内核进行 KDIR 的设置,一般路径为 /lib/modules/xxx版本/build。对于像 Ubuntu 发行版而言,其 /lib/modules/ 下会自带对应其自身版本的内核源码。如果希望编译的模块在当前 Ubuntu 下直接运行,则可以通过 uname -r 版本的获取,如 Makefile 所示。如果希望使用指定内核源码,例如 4.9.229,可以先编译该版本源码后,执行 make module_install ,则自动在 /lib/modules/ 下生成 4.9.229 版本的 build。

具体实现

在找到上述开源文件系统代码前,笔者是通过 《Linux内核探秘》 进行学习的。该书中通过自定义一个 aufs 文件系统,来对文件系统的文件管理功能进行说明。但 aufs 文件系统基于内核版本为 2.6.18,笔者尝试编译该版本内核一直失败,又考虑到目前较新版本 vfs 的实现与 2.6 的有不少出入,故作罢。才找到了上述开源的 myfs 文件系统。

结合 aufs 与 myfs 基本能够将文件系统的代码逻辑梳理清楚。两个文件系统都很简单,不同之处在于,aufs 直接在模块初始化时为文件系统创建了文件夹与文件,相当于模拟了 vfs 一些基本实现。而 myfs 则自定义了 vfs 的接口,但无额外功能。可以拿来即用。

首先,根据 aufs 了解一下如何自定义一个文件系统。整个代码的梳理顺序由下往上。

aufs

注册文件系统

insmod 一个模块时,会调用该模块 module_init 所指定的注册函数。对于 aufs 而言为 aufs_init。

  1. 通过 register_filesystem 将 au_fs_type 注册到内核中
  2. 执行 kern_mount,为 aufs 初始化一些数据结构。例如根目录 / 的 dentry,inode 等。在这里复习一下 dentry 与 inode。首先,用户所看到的目录,其实对应内核中的 dentry,用于根据文件名检索相应的 inode。在内核根文件系统中,就有一棵 dentry 组成的树。而其他文件系统,自己也有 dentry 树,所谓挂载 mount 的作用便是将其他文件系统的 dentry 树接到内核根文件系统的 dentry 树中,这样内核就能统一管理所有目录了。因此,在这里 aufs 调用 kern_mount ,会先为 aufs 创建属于自己的根目录 dentry 和 inode 。直到执行 mount -t aufs none /xxx 时,才会将 aufs 挂载到 /xxx 上 ,这些数据结构存在 vfsmount 结构体中,即 aufs_mount。
  3. 建好了 aufs 的根目录,那么便可调用 aufs_create_dir 来创建 aufs 的其他目录。这里的 aufs_create_dir 其实模拟了内核 mkdir 的执行流程。
  4. 进一步地可以通过 aufs_create_file ,基于父目录 pslot 创建文件。

可以看到,aufs 初始化时不单单进行注册,它还会自行创建两个 aufs 目录和几个文件。

static int __init aufs_init(void)
{
	int retval;
	struct dentry *pslot;

	retval = register_filesystem(&au_fs_type);

	if(!retval){
		aufs_mount = kern_mount(&au_fs_type);
		if(IS_ERR(aufs_mount)){
			printk(KERN_ERR "aufs: could not mount!\n");
			unregister_filesystem(&au_fs_type);
			return retval;
			}
		}

	pslot = aufs_create_dir("woman star",NULL);
	aufs_create_file("lbb",S_IFREG|S_IRUGO,pslot,NULL,NULL);
	aufs_create_file("fbb",S_IFREG|S_IRUGO,pslot,NULL,NULL);	
	aufs_create_file("ljl",S_IFREG|S_IRUGO,pslot,NULL,NULL);

	pslot = aufs_create_dir("man star",NULL);
	aufs_create_file("ldh",S_IFREG|S_IRUGO,pslot,NULL,NULL);
	aufs_create_file("lcw",S_IFREG|S_IRUGO,pslot,NULL,NULL);	
	aufs_create_file("jw",S_IFREG|S_IRUGO,pslot,NULL,NULL);

	return retval;
}

static void __exit aufs_exit(void)
{
	simple_release_fs(&aufs_mount,&aufs_mount_count);
	unregister_filesystem(&au_fs_type);
}

module_init(aufs_init);
module_exit(aufs_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("This is a simple module");
MODULE_VERSION("Ver 0.1");

创建目录

进一步地,需要实现 aufs_create_dir 完成目录项的创建。由于入参只有路径名 pathname,因此需要先根据 pathname 找到目标最近的目录项,才能进行创建

struct dentry *aufs_create_dir(const char *name,struct dentry *parent)
{
	return aufs_create_file(name,S_IFDIR|S_IRWXU|S_IRUGO|S_IXUGO,parent,NULL,NULL);
}
struct dentry *aufs_create_file(const char *name,mode_t mode,struct dentry *parent,void *data,struct file_operations *fops)
{
	struct dentry *dentry = NULL;
	int error;

	printk("aufs: creating file '%s'\n",name);

	error = aufs_create_by_name(name,mode,parent,&dentry);
	if (error){
		dentry = NULL;
		goto exit;
		}
exit:
	return dentry;
}

实际干活的是 aufs_create_by_name。通过 lookup_one_len 找到 name 中离目标最近的目录项 dentry。找到之后,便可以根据 mode 判断是创建目录文件还是普通文件。对应分别调用 aufs_mkdir,aufs_create。到这里我们可以知道,实际内核执行 mkdir 过程中,也是进行了 aufs_create_by_name 类似的步骤找出 dentry,而我们只需要实现 aufs_mkdir 和 aufs_create 接口即可。

static int aufs_create_by_name(const char *name, mode_t mode,struct dentry *parent, struct dentry **dentry)
{
	int error = 0;
	if(!parent){
		if (aufs_mount && aufs_mount->mnt_sb){
			parent = aufs_mount->mnt_sb->s_root;
			}
		}
	if(!parent){
		printk("Ah! can not find a parent!\n");
		return -EFAULT;
		}

	*dentry = NULL;
	mutex_lock(&parent->d_inode->i_mutex);
	*dentry = lookup_one_len(name, parent, strlen(name));
	if(!IS_ERR(dentry)){
		if((mode & S_IFMT)==S_IFDIR)
			error = aufs_mkdir(parent->d_inode,*dentry,mode);
		else
			error = aufs_create(parent->d_inode,*dentry,mode);
		}else
		error = PTR_ERR(dentry);
	mutex_unlock(&parent->d_inode->i_mutex);
	return error;
}

具体地,无论是 aufs_mkdir 还是 aufs_create 都是创建文件,都调用 aufs_mknod。因此 aufs_mknod 也是我们需要自行定义的。在 aufs 中如下,通过 aufs_get_inode 创建一个 inode,并将其绑到目录项 dentry中。在 inode 创建过程中,可以对它进行初始化,因此可以为他设置 file_operation 了,这样在 write 或者 read 时就能调用相应的方法。

static int aufs_mknod(struct inode *dir, struct dentry *dentry,int mode,dev_t dev)
{
	struct inode *inode;
	int error = -EPERM;

	if(dentry->d_inode)
		return -EEXIST;

	inode = aufs_get_inode(dir->i_sb,mode,dev);
	if (inode){
		d_instantiate(dentry, inode);
		dget(dentry);
		error = 0;
		}
	return error;
}
static struct inode *aufs_get_inode(struct super_block *sb, int 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_blksize = PAGE_CACHE_SIZE;
		inode->i_blocks = 0;
		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:
			printk("create a file \n");
			break;
		case S_IFDIR:
			inode->i_op = &simple_dir_inode_operations;
			inode->i_fop = &simple_dir_operations;
			printk("create a dir file \n");

			inode->i_nlink++;
			break;
		}
	}
	return inode;
}

小结

根据 aufs,我们可以直观地体会到内核如何组织文件的。也可以明白,哪些部分 vfs 已经帮我们做好了,哪些部分需要自定义。总的来说,我们可以知道

  1. kern_mount 其实就是 mount 系统调用,这部分我们不用管也可以
  2. 对应的,aufs_create_dir 就是 mkdir ,aufs_create_file 就是 touch。这些我们也可以不管,因为 vfs 都会处理好的
  3. 但是我们需要实现 aufs_mkdir 和 aufs_create 的接口,事实上对应的就是 super_operation ( inode_operation)
  4. aufs_get_inode 我们也需要自定义,我们需要为自定义文件系统生成的 inode 指定 file_operation

myfs

理解了 aufs 后,我们看 myfs 就很轻松了。需要注意的是 myfs 对应较新的内核版本,一些结构体及其函数指针也发生了改变,如 file_system_type 中的 .get_sb 在较新内核版本中为 .mount,其返回值类型也变了。但这不影响对文件系统本质的理解。

基于上述内容,我们直接看 myfs 自定义了哪些方法

首先,初始化模块时,仅仅只进行文件系统注册 register_filesystem

static int __init init_my_fs(void)
{
	printk("init myfs\n");
  // 模块初始化时将文件系统类型注册到系统中
  return register_filesystem(&my_fs_type);
}

定义了 operation

struct super_operations my_super_ops = { // 自定义 super_block 操作集合
	.statfs         = simple_statfs, // 给出文件系统的统计信息,例如使用和未使用的数据块的数目,或者文件件名的最大长度。
	.drop_inode			= generic_delete_inode, // 当inode的引用计数降为0时,将inode删除。
	.put_super      = myfs_put_super,
};

struct address_space_operations myfs_aops = {
	.readpage       = simple_readpage, // 用于从后备存储器将一页数据读入页框
	.write_begin	  = simple_write_begin, // 根据文件的读写位置pos计算出文件的页偏移值,根据索引查找或者分配一个page结构。将页中数据初始化为“0”。
	.write_end   		= simple_write_end, // 在对page执行写入操作后,执行相关更新操作。
	.set_page_dirty	= __set_page_dirty_no_writeback, // 将某个页标记为“脏”
};

struct file_operations myfs_file_operations = {
	.read_iter			= generic_file_read_iter,
	.write_iter			= generic_file_write_iter,
	.splice_read		= generic_file_splice_read,
	.splice_write		= iter_file_splice_write,
	.fsync          = noop_fsync, // 写回设备,内存文件系统不进行操作
	.llseek         = generic_file_llseek,
};
struct inode_operations myfs_dir_inode_ops = {
	.create         = myfs_create,
	.lookup         = simple_lookup, // 可以在此处重新设置 dentry 的操作函数
	.link						= simple_link, // 硬链接
	.unlink         = simple_unlink, // 删除文件
	.symlink				= myfs_symlink, // 软链接
	.mkdir          = myfs_mkdir,
	.rmdir          = simple_rmdir,
	.mknod          = myfs_mknod,
	.rename         = simple_rename,
};
struct inode_operations myfs_file_inode_ops = {
	.getattr        = simple_getattr,
	.setattr 				= simple_setattr,
};

实现了供 vfs 调用的接口。这些实现,是通过与上述 operation 绑定后,operation 再与 inode 进行绑定,从而 vfs 可以通过 inode 完成调用。

static int
myfs_mknod(struct inode *dir, struct dentry *dchild, umode_t mode, dev_t dev)
{
	struct inode * inode = myfs_get_inode(dir->i_sb, dir, mode, dev);
	int error = -ENOSPC;
	
	printk(KERN_INFO "myfs: mknod\n");
	if (inode) {
		d_instantiate(dchild, inode); // 将新建的 inode 和 dentry 相关联
		dget(dchild); // 增加 dentry 的引用计数
		dir->i_mtime = dir->i_ctime = current_time(dir); // 更新父目录的时间

		error = 0;

		/* real filesystems would normally use i_size_write function */
		dir->i_size += 0x20;  /* bogus small size for each dir entry */
	}
	return error;
}

// 创建目录
static int myfs_mkdir(struct inode * dir, struct dentry * dentry, umode_t mode)
{
	int retval = myfs_mknod(dir, dentry, mode | S_IFDIR, 0);
	if (!retval)
		inc_nlink(dir); // 父目录的硬链接数+1,因为子目录的“..”
	return retval;
}

// 创建普通文件
static int myfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool want_excl)
{
	return myfs_mknod(dir, dentry, mode | S_IFREG, 0);
}

写到这才发现关于 mount 的调用接口没有分析。不过大同小异,对应于 aufs 的为 aufs_get_sb,对应 myfs 为 myfs_get_sb

至此,如何实现一个文件系统也基本了解了,终于要进入正题 erofs

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值