上一篇:https://blog.csdn.net/weixin_42523774/article/details/103739139
· 上文我们解释了文件系统加入Linux需要什么内容,本文就以一个实际使用过的文件系统yaffs为例,解释一下一个文件系统如何融入Linux内核。
· 本文会结合这篇 yaffs2源代码分析(最新版)。解释yaffs实现的部分原理。

· 首先我们先看到 yaffs_vfs_multi.c 文件中的内容,与这个yaffs_vfs_single.c 有所区别,主要是前者支持所有的Linux版本,增加了很多的宏定义,而后者只支持某一个版本,我们可以通过前者的代码对后者代码进行修改来得到自己需要的版本。

1.初始化yaffs模块

· 一个文件系统融入Linux的入口就是初始化函数,这里的初始化函数比较简单,大家看代码中的增加的注释即可。

static int __init init_yaffs_fs(void)
{
	int error = 0;
	struct file_system_to_install *fsinst;

	yaffs_trace(YAFFS_TRACE_ALWAYS,
		"yaffs built " __DATE__ " " __TIME__ " Installing.");

	mutex_init(&yaffs_context_lock);

	/* 创建proc文件系统的条目 */
	my_proc_entry = create_proc_entry("yaffs",
					  S_IRUGO | S_IFREG, NULL);
	/* 成功则初始化,否则退出 */
	if (my_proc_entry) {
		my_proc_entry->write_proc = NULL;
		my_proc_entry->read_proc = yaffs_proc_read;
		my_proc_entry->data = NULL;
	} else {
		return -ENOMEM;
	}


	/* 现在增加文件系统条目 */

	fsinst = fs_to_install;/************  关注此处,下节细讲  ************/
	/* 注册文件系统 */
	while (fsinst->fst && !error) {
		error = register_filesystem(fsinst->fst);
		if (!error)
			fsinst->installed = 1;
		fsinst++;
	}

	/* 有任何错误都会注销这个文件系统  */
	if (error) {
		fsinst = fs_to_install;

		while (fsinst->fst) {
			if (fsinst->installed) {
				unregister_filesystem(fsinst->fst);
				fsinst->installed = 0;
			}
			fsinst++;
		}
	}

	return error;
}

· exit_yaffs_fs 卸载yaffs函数是 init_yaffs_fs 的逆过程,各位同学可以自行分析;

2. yaffs的 super_block 结构

· yaffs文件系统在flash中是没有存放super_block结构的,而是通过一个函数固定读取这个结构,yaffs内部通过 yaffs_internal_read_super函数 获取 super_block, 通过接口yaffs2_internal_read_super_mtd 传递给VFS。
· yaffs_internal_read_super 函数获取super_block结构时,每次都是重新获取。
· 上一节代码中提到了 fs_to_install 这个没有讲,这个就是yaffs系统注册的数组,可以通过如下代码知道,这就是将yaffs和yaffs2 2个文件系统注册到内核中所需的,内核通过这个数组知道存在哪些文件系统的。

/* Stuff to handle installation of file systems */
struct file_system_to_install {
	struct file_system_type *fst;
	int installed;
};

static struct file_system_to_install fs_to_install[] = {
	{&yaffs_fs_type, 0},
	{&yaffs2_fs_type, 0},
	{NULL, 0}
};

· 通过路径 init_yaffs_fs->fs_to_install->yaffs2_fs_type 找到这个yaffs2_fs_type,这里最重要的就是 yaffs2_mount 函数,注册到系统之后,就可以使用 mount 命令 将yaffs2文件系统 挂载到对应目录下使用,mount系统调用最终就是调用yaffs2_mount函数,和我们做的open搜寻类似,大家可以自行搜寻这个过程,本文不细讲。

static struct file_system_type yaffs2_fs_type = {
	.owner = THIS_MODULE,
	.name = "yaffs2",
	.mount = yaffs2_mount,
	.kill_sb = kill_block_super,
	.fs_flags = FS_REQUIRES_DEV,
};
static struct dentry *yaffs2_mount(struct file_system_type *fs,
			     int flags, const char *dev_name, void *data)
{
	return mount_bdev(fs, flags, dev_name, data,
			   yaffs2_internal_read_super_mtd);
}

static int yaffs2_internal_read_super_mtd(struct super_block *sb, void *data,
					  int silent)
{
	return yaffs_internal_read_super(2, sb, data, silent) ? 0 : -EINVAL;
}

· 下面介绍 yaffs2_mount 如何实现,首先调用 mount_bdev函数,mount_bdev是针对块设备挂载时使用的函数,主要实现就是设置其他的参数,然后调用 yaffs2_internal_read_super_mtd 来获取文件系统的superblock,yaffs2_internal_read_super_mtd又直接调用了 yaffs_internal_read_super,下面专门详细讲解这个函数。
· 这个函数就干3件事:
(1)从data中获取yaffs属性选项;
(2)依据属性填充sb中的值,主要是sb->s_fs_info,就是struct yaffs_dev结构。
(3)最后创建根目录节点。
· 这的确就是mount必须的步骤,通过参数设置,你可以理解yaffs文件系统的关键属性是怎么设置的,对于要移植yaffs的同学要仔细看一遍。我增加了很对注释,方便大家理解。

/*
本函数用来获得并初始化yaffs系统的super_block,主要通过对sb赋值,
参数1:yaffs_version 表示yaffs版本号,1表示yaffs1,2表示yaffs2;
参数2:struct super_block *sb是传入的参数,后续会将其赋值之后返回;
参数3:void *data 用于传递属性字符串,多个属性用逗号隔开,通过yaffs_parse_options获取属性之后,会修改相应的参数项;
参数4:int silent 参数暂时没有使用
返回值:返回修改以后的super_block
*/
static struct super_block *yaffs_internal_read_super(int yaffs_version,
						     struct super_block *sb,
						     void *data, int silent)
{
	int n_blocks;
	struct inode *inode = NULL;
	struct dentry *root;
	struct yaffs_dev *dev = 0;
	char devname_buf[BDEVNAME_SIZE + 1];
	struct mtd_info *mtd;//yaffs是mtd设备
	int err;
	char *data_str = (char *)data;//输入的属性字符串
	struct yaffs_linux_context *context = NULL;//yaffs文件内容
	struct yaffs_param *param;//yaffs参数
	int read_only = 0;//是否只读
	struct yaffs_options options;//yaffs属性
	unsigned mount_id;
	int found;
	struct yaffs_linux_context *context_iterator;//
	struct list_head *l;

	if (!sb) {
		printk(KERN_INFO "yaffs: sb is NULL\n");
		return NULL;
	}

	sb->s_magic = YAFFS_MAGIC;//魔术字固定
	sb->s_op = &yaffs_super_ops;/*****yaffs_super_ops是一个全局变量,已经定义完毕,赋值使用。下一节讲解典型例子***/
	sb->s_flags |= MS_NOATIME;

	read_only = ((sb->s_flags & MS_RDONLY) != 0);//是否只读

	sb->s_export_op = &yaffs_export_ops;//yaffs的一些非标准操作,也是定义好的。

	if (!sb->s_dev)//打印设备名
		printk(KERN_INFO "yaffs: sb->s_dev is NULL\n");
	else if (!yaffs_devname(sb, devname_buf))
		printk(KERN_INFO "yaffs: devname is NULL\n");
	else
		printk(KERN_INFO "yaffs: dev is %d name is \"%s\" %s\n",
		       sb->s_dev,
		       yaffs_devname(sb, devname_buf), read_only ? "ro" : "rw");

	if (!data_str)
		data_str = "";

	printk(KERN_INFO "yaffs: passed flags \"%s\"\n", data_str);

	memset(&options, 0, sizeof(options));
	//  解析yaffs选项,将data_str中的字符串解析成对于的flag
	if (yaffs_parse_options(&options, data_str))
		/* Option parsing failed */
		return NULL;

	sb->s_blocksize = PAGE_CACHE_SIZE;//PAGE大小,查找这个宏,你就可以知道yaffs的spare area大小的宏在哪,怎么修改
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;//PAGE大小的二进制位数

	yaffs_trace(YAFFS_TRACE_OS,
		"yaffs_read_super: Using yaffs%d", yaffs_version);
	yaffs_trace(YAFFS_TRACE_OS,
		"yaffs_read_super: block size %d", (int)(sb->s_blocksize));

	yaffs_trace(YAFFS_TRACE_ALWAYS,
		"Attempting MTD mount of %u.%u,\"%s\"",
		MAJOR(sb->s_dev), MINOR(sb->s_dev),
		yaffs_devname(sb, devname_buf));

	/* 检查是否是MTD设备,这是由于yaffs是基于mtd设备的,不是mtd设备则直接退出。*/
	if (MAJOR(sb->s_dev) != MTD_BLOCK_MAJOR)
		return NULL;	/* This isn't an mtd device */

	/* 获取此设备的设备信息 */
	mtd = get_mtd_device(NULL, MINOR(sb->s_dev));
	if (IS_ERR(mtd)) {
		yaffs_trace(YAFFS_TRACE_ALWAYS,
			"MTD device #%u doesn't appear to exist",
			MINOR(sb->s_dev));
		return NULL;
	}
	/* 检查mtd设备是否是MTD_NANDFLASHCheck it's NAND */
	if (mtd->type != MTD_NANDFLASH) {
		yaffs_trace(YAFFS_TRACE_ALWAYS,
			"MTD device is not NAND it's type %d",
			mtd->type);
		return NULL;
	}

	yaffs_trace(YAFFS_TRACE_OS, " erase %p", mtd->erase);
	yaffs_trace(YAFFS_TRACE_OS, " read %p", mtd->read);
	yaffs_trace(YAFFS_TRACE_OS, " write %p", mtd->write);
	yaffs_trace(YAFFS_TRACE_OS, " readoob %p", mtd->read_oob);
	yaffs_trace(YAFFS_TRACE_OS, " writeoob %p", mtd->write_oob);
	yaffs_trace(YAFFS_TRACE_OS, " block_isbad %p", mtd->block_isbad);
	yaffs_trace(YAFFS_TRACE_OS, " block_markbad %p", mtd->block_markbad);
	yaffs_trace(YAFFS_TRACE_OS, " writesize %d", mtd->writesize);
	yaffs_trace(YAFFS_TRACE_OS, " oobsize %d", mtd->oobsize);
	yaffs_trace(YAFFS_TRACE_OS, " erasesize %d", mtd->erasesize);
	yaffs_trace(YAFFS_TRACE_OS, " size %lld", mtd->size);
	//自动选择yaffs版本判断,如果设备writesize >= 2K就可自动转换 版本2
	if (yaffs_auto_select && yaffs_version == 1 && mtd->writesize >= 2048) {
		yaffs_trace(YAFFS_TRACE_ALWAYS, "auto selecting yaffs2");
		yaffs_version = 2;
	}
	//自动选择yaffs版本判断,如果设备writesize == 512就可自动转换 版本1
	if (yaffs_auto_select && yaffs_version == 2 && !options.inband_tags &&
		mtd->writesize == 512) {
		yaffs_trace(YAFFS_TRACE_ALWAYS, "auto selecting yaffs1");
		yaffs_version = 1;
	}

	if (yaffs_version == 2) {
		/* 检查类型函数是否支持Check for version 2 style functions */
		if (!mtd->erase ||
		    !mtd->block_isbad ||
		    !mtd->block_markbad ||
		    !mtd->read ||
		    !mtd->write || !mtd->read_oob || !mtd->write_oob) {
			yaffs_trace(YAFFS_TRACE_ALWAYS,
				"MTD device does not support required functions");
			return NULL;
		}
		//查看page size是否正确
		if ((mtd->writesize < YAFFS_MIN_YAFFS2_CHUNK_SIZE ||
		     mtd->oobsize < YAFFS_MIN_YAFFS2_SPARE_SIZE) &&
		    !options.inband_tags) {
			yaffs_trace(YAFFS_TRACE_ALWAYS,
				"MTD device does not have the right page sizes");
			return NULL;
		}
	} else {
		/* Check for V1 style functions */
		if (!mtd->erase ||
		    !mtd->read ||
		    !mtd->write || !mtd->read_oob || !mtd->write_oob) {
			yaffs_trace(YAFFS_TRACE_ALWAYS,
				"MTD device does not support required functions");
			return NULL;
		}

		if (mtd->writesize < YAFFS_BYTES_PER_CHUNK ||
		    mtd->oobsize != YAFFS_BYTES_PER_SPARE) {
			yaffs_trace(YAFFS_TRACE_ALWAYS,
				"MTD device does not support have the right page sizes");
			return NULL;
		}
	}

	/* OK, so if we got here, we have an MTD that's NAND and looks
	 * like it has the right capabilities
	 * Set the struct yaffs_dev up for mtd
	 */
	//查看是否可读不可写
	if (!read_only && !(mtd->flags & MTD_WRITEABLE)) {
		read_only = 1;
		printk(KERN_INFO
		       "yaffs: mtd is read only, setting superblock read only");
		sb->s_flags |= MS_RDONLY;
	}
	//分配 yaffs_dev 和 yaffs_linux_context的空间, 后续就是操作填充dev信息。
	dev = kmalloc(sizeof(struct yaffs_dev), GFP_KERNEL);
	context = kmalloc(sizeof(struct yaffs_linux_context), GFP_KERNEL);
	//检查空间是否申请成功
	if (!dev || !context) {
		kfree(dev);
		kfree(context);
		dev = NULL;
		context = NULL;

		/* Deep shit could not allocate device structure */
		yaffs_trace(YAFFS_TRACE_ALWAYS,
			"yaffs_read_super failed trying to allocate yaffs_dev");
		return NULL;
	}
	/*  后续大量都是在填充 dev 和其中的 param         和 os_context*/
	memset(dev, 0, sizeof(struct yaffs_dev));
	param = &(dev->param);

	memset(context, 0, sizeof(struct yaffs_linux_context));
	dev->os_context = context;
	INIT_LIST_HEAD(&(context->context_list));
	context->dev = dev;
	context->super = sb;

	dev->read_only = read_only;

	sb->s_fs_info = dev;

	dev->driver_context = mtd;
	param->name = mtd->name;

	/* Set up the memory size parameters.... */
	/*  计算块数  */
	n_blocks =
	    YCALCBLOCKS(mtd->size,
			(YAFFS_CHUNKS_PER_BLOCK * YAFFS_BYTES_PER_CHUNK));

	param->start_block = 0;  ///<  起始块号
	param->end_block = n_blocks - 1; ///<  结束块号
	param->chunks_per_block = YAFFS_CHUNKS_PER_BLOCK; ///< 每块页数
	param->total_bytes_per_chunk = YAFFS_BYTES_PER_CHUNK; ///< 每页字节数
	param->n_reserved_blocks = 5; ///< 保留块数
	param->n_caches = (options.no_cache) ? 0 : 10; ///< 缓存个数
	param->inband_tags = options.inband_tags;

	param->disable_lazy_load = 1;///<  禁止延迟加载
	param->enable_xattr = 1;///<使能扩展属性
	if (options.lazy_loading_overridden)
		param->disable_lazy_load = !options.lazy_loading_enabled;

	param->defered_dir_update = 1;///< 延迟目录更新

	if (options.tags_ecc_overridden)///<覆写ECC标记
		param->no_tags_ecc = !options.tags_ecc_on;

	param->empty_lost_n_found = 1;///<自动将lost+found目录置空

	param->refresh_period = 500;///< 刷新周期为500ms

	if (options.empty_lost_and_found_overridden)
		param->empty_lost_n_found = options.empty_lost_and_found;

	/* 设置操作函数... and the functions. */
	if (yaffs_version == 2) {
		param->write_chunk_tags_fn = nandmtd2_write_chunk_tags;//写chunk函数
		param->read_chunk_tags_fn = nandmtd2_read_chunk_tags;//读chunk函数
		param->bad_block_fn = nandmtd2_mark_block_bad;//标记坏块函数
		param->query_block_fn = nandmtd2_query_block;//查询块状态和序列号
		yaffs_dev_to_lc(dev)->spare_buffer =
				kmalloc(mtd->oobsize, GFP_NOFS);//申请一块内存存放oob区
		param->is_yaffs2 = 1;
		param->total_bytes_per_chunk = mtd->writesize;//页大小
		param->chunks_per_block = mtd->erasesize / mtd->writesize;//块大小/页大小
		n_blocks = YCALCBLOCKS(mtd->size, mtd->erasesize);//总大小/块大小

		param->start_block = 0;//起始大小为0号
		param->end_block = n_blocks - 1;//终止大小为0号
	} else {
		/* use the MTD interface in yaffs_mtdif1.c */
		param->write_chunk_tags_fn = nandmtd1_write_chunk_tags;
		param->read_chunk_tags_fn = nandmtd1_read_chunk_tags;
		param->bad_block_fn = nandmtd1_mark_block_bad;
		param->query_block_fn = nandmtd1_query_block;
		param->is_yaffs2 = 0;
	}
	/* 公共函数... and common functions */
	param->erase_fn = nandmtd_erase_block;//擦除块函数
	param->initialise_flash_fn = nandmtd_initialise;//flash初始化函数

	yaffs_dev_to_lc(dev)->put_super_fn = yaffs_mtd_put_super;

	param->sb_dirty_fn = yaffs_touch_super;//标记superblock已经dirty函数
	param->gc_control_fn = yaffs_gc_control_callback;//控制包收集回调函数

	yaffs_dev_to_lc(dev)->super = sb;//dev与super相互绑定

	param->use_nand_ecc = 1;

	param->skip_checkpt_rd = options.skip_checkpoint_read;//是否忽略读检查点控
	param->skip_checkpt_wr = options.skip_checkpoint_write;//是否忽略写检查点控

	mutex_lock(&yaffs_context_lock);
	/* 在yaffs_context_list中搜寻挂载ID,找到最小的mount_id. Get a mount id */
	for (mount_id = 0, found = 0; !found; mount_id++) {
		found = 1;
		list_for_each(l, &yaffs_context_list) {
			context_iterator =
				list_entry(l, struct yaffs_linux_context,
					context_list);
			if (context_iterator->mount_id == mount_id)
				found = 0;
		}
	}
	context->mount_id = mount_id;

	list_add_tail(&(yaffs_dev_to_lc(dev)->context_list),
		      &yaffs_context_list);//放入链表
	mutex_unlock(&yaffs_context_lock);

	/* Directory search handling... */
	INIT_LIST_HEAD(&(yaffs_dev_to_lc(dev)->search_contexts));
	param->remove_obj_fn = yaffs_remove_obj_callback;//删除对象回调函数,如果有内容就不删除

	mutex_init(&(yaffs_dev_to_lc(dev)->gross_lock));

	yaffs_gross_lock(dev);

	err = yaffs_guts_initialise(dev);//dev参数初始化

	yaffs_trace(YAFFS_TRACE_OS,
		"yaffs_read_super: guts initialised %s",
		(err == YAFFS_OK) ? "OK" : "FAILED");

	if (err == YAFFS_OK)
		yaffs_bg_start(dev);

	if (!context->bg_thread)
		param->defered_dir_update = 0;

	sb->s_maxbytes = yaffs_max_file_size(dev);

	/* Release lock before yaffs_get_inode() */
	yaffs_gross_unlock(dev);

	/* 调用yaffs_get_inode创建根节点inode,并初始化设置,Create root inode */
	if (err == YAFFS_OK)
		inode = yaffs_get_inode(sb, S_IFDIR | 0755, 0, yaffs_root(dev));

	if (!inode)
		return NULL;
	//设置目录的操作函数
	inode->i_op = &yaffs_dir_inode_operations;
	inode->i_fop = &yaffs_dir_operations;

	yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: got root inode");

	root = d_alloc_root(inode);//给inode分配一个根目录

	yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: d_alloc_root done");

	if (!root) {
		iput(inode);
		return NULL;
	}
	sb->s_root = root;//配置根目录
	sb->s_dirt = !dev->is_checkpointed;
	yaffs_trace(YAFFS_TRACE_ALWAYS,
		"yaffs_read_super: is_checkpointed %d",
		dev->is_checkpointed);

	yaffs_trace(YAFFS_TRACE_OS, "yaffs_read_super: done");
	return sb;
}

· 通过以上的理解,我们很清楚的能知道,Linux的inode,在yaffs中是以obj的方式来存储的,相互之间转化用的是 yaffs_inode_to_obj,这个函数告诉我们,obj 放在inode的i_private处存放,每个obj有一个obj_id作为它的唯一标识,通过yaffs2源代码分析(最新版)我们可以知道,这个并不是对应flash上的位置,只是一个文件编号。每一个文件系统对象都被赋予一个唯一的编号,作为对象标识,也用于将该对象挂入一个散列表,加快对象的搜索速度。该文件头在 flash 上的 chunk 序号才是对应的物理位置。
· 我们是如何使用yaffs文件系统的呢?首先是yaffs_dev的cache申请一段空间来存放对应obj的缓存,修改之后会继续使用,但是会标记成dirty,当后续多个dirty之后会一起写进flash。

2.1 yaffs 的 super_block 结构中的操作函数

· super_block 结构的操作函数集合是将yaffs文件系统加入的关键操作,这里能我们重点讲解。

static const struct super_operations yaffs_super_ops = {
	.statfs = yaffs_statfs,

#ifndef YAFFS_USE_OWN_IGET
	.read_inode = yaffs_read_inode,
#endif
#ifdef YAFFS_HAS_PUT_INODE
	.put_inode = yaffs_put_inode,
#endif
	.put_super = yaffs_put_super,
#ifdef YAFFS_HAS_EVICT_INODE
	.evict_inode = yaffs_evict_inode,
#else
	.delete_inode = yaffs_delete_inode,
	.clear_inode = yaffs_clear_inode,
#endif
	.sync_fs = yaffs_sync_fs,
#ifdef YAFFS_HAS_WRITE_SUPER
	.write_super = yaffs_write_super,
#endif
	.remount_fs = yaffs_remount_fs,
};

· 其中 yaffs_put_super 和 yaffs_write_super 就是读写super_block,yaffs_sync_fs 就是同步文件系统。
· 我们取其中的一个函数来讲解一下内部基本实现,yaffs_sync_fs 和 yaffs_write_super 的核心都是 yaffs_do_sync_fs。yaffs_do_sync_fs 的核心就是 调用 yaffs_flush_super 函数。下面简单讲一下这个函数:

static void yaffs_flush_super(struct super_block *sb, int do_checkpoint)
{
	struct yaffs_dev *dev = yaffs_super_to_dev(sb);
	if (!dev)
		return;

	yaffs_flush_inodes(sb);/*将缓存中的inode一个一个更新到flash中*/
	yaffs_update_dirty_dirs(dev);/*检查目录是否dirty并更新*/
	yaffs_flush_whole_cache(dev, 1);/*由于更新了inode,需要刷新缓存中的inode*/
	if (do_checkpoint)
		yaffs_checkpoint_save(dev);/*检查是否inode层次结构是否变化*/
}

static void yaffs_flush_inodes(struct super_block *sb)
{
	struct inode *iptr;
	struct yaffs_obj *obj;

	list_for_each_entry(iptr, &sb->s_inodes, i_sb_list) {
		obj = yaffs_inode_to_obj(iptr);
		if (obj) {
			yaffs_trace(YAFFS_TRACE_OS,
				"flushing obj %d",
				obj->obj_id);
			yaffs_flush_file(obj, 1, 0, 0);/*将缓存中的file更新到flash中*/
		}
	}
}

3. yaffs的 inode 结构

· yaffs文件系统的内部是obj为单位的,这对应着 VFS 的 inode,这才有 yaffs_fill_inode_from_obj 函数 和 yaffs_inode_to_obj 将 object 和 inode 的转化。
上一节我们用到了yaffs_super_ops里面刚好存放了一些inode操作函数,
我们下面简单介绍几个inode操作函数:

/*读inode函数,为了防止死锁,需要加个锁*/
static void yaffs_read_inode(struct inode *inode)
{

	struct yaffs_obj *obj;
	struct yaffs_dev *dev = yaffs_super_to_dev(inode->i_sb);

	yaffs_trace(YAFFS_TRACE_OS,
		"yaffs_read_inode for %d", (int)inode->i_ino);
	/* 依据当前状态是不是在读目录,是就要锁上 */
	if (current != yaffs_dev_to_lc(dev)->readdir_process)
		yaffs_gross_lock(dev);
	/* 依据当前的inode号,查找对于的obj对象 */
	obj = yaffs_find_by_number(dev, inode->i_ino);
	/* 依据找到的obj对象,填充信息到inode对象中 */
	yaffs_fill_inode_from_obj(inode, obj);

	if (current != yaffs_dev_to_lc(dev)->readdir_process)
		yaffs_gross_unlock(dev);
}

/*  清除inode被调用,是通知文件系统去释放预缓存的inode数据,对象obj可能一直存在于flash中,而且只能把它从缓存中删除,要不然这个对象obj可能已经被删除了,通过如下调用做到这些 yaffs_delete_inode() -> clear_inode()->yaffs_clear_inode() */
static void yaffs_clear_inode(struct inode *inode)
{
	struct yaffs_obj *obj;
	struct yaffs_dev *dev;
	/*  找到对应的obj对象  */
	obj = yaffs_inode_to_obj(inode);

	yaffs_trace(YAFFS_TRACE_OS,
		"yaffs_clear_inode: ino %d, count %d %s",
		(int)inode->i_ino, atomic_read(&inode->i_count),
		obj ? "object exists" : "null object");
	/* obj如果存在,就可以清除它  */
	if (obj) {
		dev = obj->my_dev;
		yaffs_gross_lock(dev);
		/*真正的清除对象函数,见下面详解*/
		yaffs_unstitch_obj(inode, obj);
		yaffs_gross_unlock(dev);
	}

}
/*  拆除obj函数  */
static void yaffs_unstitch_obj(struct inode *inode, struct yaffs_obj *obj)
{
	/* 首先让obj和inode互不关联 */
	obj->my_inode = NULL;
	yaffs_inode_to_obj_lv(inode) = NULL;

	/* 如果之前对象obj释放被延迟,那么真正的释放现在就开始。这将解决inode不一致的问题。*/
	yaffs_handle_defered_free(obj);
}

void yaffs_handle_defered_free(struct yaffs_obj *obj)
{
	if (obj->defered_free)
		yaffs_free_obj(obj);
}

/*  释放一个对象并将其放回空闲列表中 */
static void yaffs_free_obj(struct yaffs_obj *obj)
{
	struct yaffs_dev *dev;

	if (!obj) {
		BUG();
		return;
	}
	dev = obj->my_dev;
	yaffs_trace(YAFFS_TRACE_OS, "FreeObject %p inode %p",
		obj, obj->my_inode);
	if (obj->parent)
		BUG();
	if (!list_empty(&obj->siblings))
		BUG();

	if (obj->my_inode) {
		/* 如果obj仍然连接到一个缓存的inode。不要现在删除,但标记以后删除 */
		obj->defered_free = 1;
		return;
	}
	/*  将此对象obj从hash表中删除  */
	yaffs_unhash_obj(obj);
	/*  将此对象obj添加到空闲链表中 */
	yaffs_free_raw_obj(dev, obj);
	dev->n_obj--;//设备使用对象数--
	dev->checkpoint_blocks_required = 0;	/* force recalculation */
}

· 通过 以上的函数调用
yaffs_clear_inode() -> yaffs_unstitch_obj() -> yaffs_handle_defered_free() -> yaffs_free_obj(),我们看到最终他只是把内容在链表中释放掉了,并没有真实的从flash中删除。那么什么时候真实删除obj呢,就是垃圾回收的时候,具体可以见 yaffs源代码解析。

· 上文中 yaffs_fill_inode_from_obj 函数是通过obj创建一个inode对象,这里也将对inode的操作函数也赋给了inode,我们可以看看inode操作如何实现:

static const struct inode_operations yaffs_dir_inode_operations = {
	.create = yaffs_create,
	.lookup = yaffs_lookup,
	.link = yaffs_link,
	.unlink = yaffs_unlink,
	.symlink = yaffs_symlink,
	.mkdir = yaffs_mkdir,
	.rmdir = yaffs_unlink,
	.mknod = yaffs_mknod,
	.rename = yaffs_rename,
	.setattr = yaffs_setattr,
	.listxattr = yaffs_listxattr,
#ifdef YAFFS_USE_XATTR
	.setxattr = yaffs_setxattr,
	.getxattr = yaffs_getxattr,
	.removexattr = yaffs_removexattr,
#endif
};

这里,我们通过几个例子理解一下inode操作函数:

3.1 lookup函数

我们之前将open函数所看到过lookup,最终搜寻可能调用到这里来。

static struct dentry *yaffs_lookup(struct inode *dir, struct dentry *dentry,
				   struct nameidata *n)
{
	struct yaffs_obj *obj;
	struct inode *inode = NULL;	/* NCB 2.5/2.6 needs NULL here */

	struct yaffs_dev *dev = yaffs_inode_to_obj(dir)->my_dev;

	if (current != yaffs_dev_to_lc(dev)->readdir_process)
		yaffs_gross_lock(dev);

	yaffs_trace(YAFFS_TRACE_OS, "yaffs_lookup for %d:%s",
		yaffs_inode_to_obj(dir)->obj_id, dentry->d_name.name);
		
	/* 在此目录dir的子文件下找到对应名字的obj对象。 */
	obj = yaffs_find_by_name(yaffs_inode_to_obj(dir), dentry->d_name.name);
	
	/* 如果是硬链接文件,就需要拷贝原obj的内容拷贝过来。 */
	obj = yaffs_get_equivalent_obj(obj);

	/* 调用 yaffs_get_inode() 的时候,不能加锁 */
	if (current != yaffs_dev_to_lc(dev)->readdir_process)
		yaffs_gross_unlock(dev);

	if (obj) {
		yaffs_trace(YAFFS_TRACE_OS,
			"yaffs_lookup found %d", obj->obj_id);
		/*  在obj缓存中,获取对于obj_id的obj  */
		inode = yaffs_get_inode(dir->i_sb, obj->yst_mode, 0, obj);
	} else {
		yaffs_trace(YAFFS_TRACE_OS, "yaffs_lookup not found");

	}

	/* 如果 inode 为 NULL, 就要重新创建dentry hash */
	d_add(dentry, inode);

	return NULL;
}

3.2 yaffs_mknod函数

· yaffs_mknod是 yaffs_create, yaffs_mkdir 实现的实质函数,因为都是要创建一个obj对象,我们重点介绍一下。
· yaffs_mknod实现分为2个部分,(1)获取创建对象的内容信息,(2)依据不同的文件类型,创建相应的对象obj,(3)处理obj和inode、dentry之间的关系。
(1)获取创建对象的内容信息, 这里获取了uid,gid,parent 和 dev这几个创建obj必须的量。

static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode,
		       dev_t rdev)
{
	struct inode *inode;

	struct yaffs_obj *obj = NULL;
	struct yaffs_dev *dev;
	/*(1)获取创建对象的内容信息 */
	struct yaffs_obj *parent = yaffs_inode_to_obj(dir);

	int error = -ENOSPC;
	uid_t uid = YPROC_uid(current);
	gid_t gid =
	    (dir->i_mode & S_ISGID) ? EXTRACT_gid(dir->i_gid) : YPROC_gid(current);

	if ((dir->i_mode & S_ISGID) && S_ISDIR(mode))
		mode |= S_ISGID;

	if (parent) {
		yaffs_trace(YAFFS_TRACE_OS,
			"yaffs_mknod: parent object %d type %d",
			parent->obj_id, parent->variant_type);
	} else {
		yaffs_trace(YAFFS_TRACE_OS,
			"yaffs_mknod: could not get parent object");
		return -EPERM;
	}

	yaffs_trace(YAFFS_TRACE_OS,
		"yaffs_mknod: making oject for %s, mode %x dev %x",
		dentry->d_name.name, mode, rdev);

	dev = parent->my_dev;

(2)依据不同的文件类型,创建相应的对象obj, 这里调用的创建函数有些不同,但是实际都是调用的 yaffs_create_obj() 函数.

	yaffs_gross_lock(dev);

	switch (mode & S_IFMT) {
	default:
		/* 特殊文件(socket、 fifo, device ...) */
		yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making special");
		obj =
		    yaffs_create_special(parent, dentry->d_name.name, mode, uid,
					 gid, old_encode_dev(rdev));
		break;
	case S_IFREG:		/* file 文件类型 */
		yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making file");
		obj = yaffs_create_file(parent, dentry->d_name.name, mode, uid,
					gid);
		break;
	case S_IFDIR:		/* directory 目录类型*/
		yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making directory");
		obj = yaffs_create_dir(parent, dentry->d_name.name, mode,
				       uid, gid);
		break;
	case S_IFLNK:		/* symlink 连接文件类型*/
		yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod: making symlink");
		obj = NULL;	/* Do we ever get here? */
		break;
	}

	/* Can not call yaffs_get_inode() with gross lock held */
	yaffs_gross_unlock(dev);

· yaffs_create_special()等函数都是调用 yaffs_create_obj() 函数,然后 通过如下序列获得内存空间的 yaffs_new_obj() -> yaffs_get_tnode() -> yaffs_alloc_raw_tnode() -> yaffs_create_tnodes() -> kmalloc() 通过这个调用序列查到 kmalloc,我们可以知道,yaffs存储文件内容其实是在内存中,存入flash是在后续。
· 然后 yaffs_create_obj() 中是通过 yaffs_new_obj_id() 获取对应的 obj_id ,obj_id 是通过某种规则制定的,核心是桶排序,提高搜索效率,存在yaffs_dev 下的 obj_bucket。
[yaffs_new_obj => yaffs_new_obj_id ]

static int yaffs_new_obj_id(struct yaffs_dev *dev)
{
	int bucket = yaffs_find_nice_bucket(dev);
	int found = 0;
	struct list_head *i;
	u32 n = (u32) bucket;

	/*
	 * Now find an object value that has not already been taken
	 * by scanning the list, incrementing each time by number of buckets.
	 */
	while (!found) {
		found = 1;
		n += YAFFS_NOBJECT_BUCKETS;
		list_for_each(i, &dev->obj_bucket[bucket].list) {
			/* Check if this value is already taken. */
			if (i && list_entry(i, struct yaffs_obj,
					    hash_link)->obj_id == n)
				found = 0;
		}
	}
	return n;
}

(3)处理obj和inode、dentry之间的关系。先过去对应的inode,然后对其说实话。

	if (obj) {
		inode = yaffs_get_inode(dir->i_sb, mode, rdev, obj);
		d_instantiate(dentry, inode);
		update_dir_time(dir);
		yaffs_trace(YAFFS_TRACE_OS,
			"yaffs_mknod created object %d count = %d",
			obj->obj_id, atomic_read(&inode->i_count));
		error = 0;
		yaffs_fill_inode_from_obj(dir, parent);
	} else {
		yaffs_trace(YAFFS_TRACE_OS, "yaffs_mknod failed making object");
		error = -ENOMEM;
	}

	return error;
}

4. 总结

· 通过以上的分析,我们基本弄清楚了yaffs是如何连接linux的,主要是 superblock 和inode的对应接口如何实现。这里的内容需要和 yaffs源码分析 结合起来才能深入明白这是如何实现。
· 如果你想修改相关参数,superblock的如何产生,你是必须知道的。
然后我们需要知道,yaffs文件系统是建立在mtd设备之上的,而mtd设备又是对flash和rom等设备的抽象。mtd设备的架构我们后续在分析。
· inode和obj之间相互转化也是需要了解的,obj就是yaffs的真实对象,这和inode 有部分的差异,obj_id是obj的唯一标识,使用的是桶排序的索引。
· 到此,yaffs如何和linux连接起来依旧基本明白了,如果对yaffs具体是什么时候将内容写入flash 的,请看yaffs2源代码分析(最新版)

如果觉得我的文章还有点收获,就点个赞吧! d=====( ̄▽ ̄*)b

下一篇:https://blog.csdn.net/weixin_42523774/article/details/106914777

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐