jffs2文件系统-------挂载

       按照文件系统的惯例,如果是非rootfs的话,就需要手动挂载。如: mount -t jffs2 /dev/mtdjffs2 jffs2  (这行shell的大体意思就是将mtdjffs2这个mtd块设备,按照jffs2文件系统的格式,挂载到jffs2文件夹下)。支持这行shell的前提有两个,其一内核要支持jffs2文件系统;其二mtdjffs2这个块设备上的数据,要符合jffs2文件系统的要求。内核支持很简单,在config文件中,添加jffs2即可;第二个要求则需要工具,谷歌jffs2的移植。

       本文重要的是看源码,配置、移植之类的,不再累赘。

     挂载代码可以简略成三部分,其中第三部分是最关键,也是代码量最大的,第一第二步很简单,也不再累赘,主要是匹配mtd块设备。

1、jffs2_get_sb
得到jffs2是第几个mtd分区后,进入第二步
2、jffs2_get_sb_mtdnr
从mtd_table得到分区的信息后,进入第三步
3、jffs2_get_sb_mtd
对超级块的设置,系统挂载

       关于第三部分,可看代码(只截取重点部分)

static int jffs2_get_sb_mtd(struct file_system_type *fs_type,
			    int flags, const char *dev_name,
			    void *data, struct mtd_info *mtd,
			    struct vfsmount *mnt)
{
	struct super_block *sb;
	struct jffs2_sb_info *c;
	sb = sget(fs_type, jffs2_sb_compare, jffs2_sb_set, c);
		//jffs2_sb_set  :将sb->s_fs_info指向c,这个在fill_super会用到
       ……
	sb->s_op = &jffs2_super_operations;	//这个也很重要
        ……
	ret = jffs2_do_fill_super(sb, data, flags & MS_SILENT ? 1 : 0);	//最重要的一个函数
      ……
}
       其中sget是去获取文件系统的超级块,而它的s_fs_info(Filesystem private info)指向jffs2的超级块。

      而jffs2_do_fill_super,从字面上看是堆超级块的填充,这个也是挂载部分最终的部分(一般文件系统挂载,无非是搞一个超级块,再搞个dirent,inode)。

int jffs2_do_fill_super(struct super_block *sb, void *data, int silent)
{
	struct jffs2_sb_info *c;
	struct inode *root_i;
	int ret;
	size_t blocks;

	c = JFFS2_SB_INFO(sb);	//sb->s_fs_info
	
	c->cleanmarker_size = sizeof(struct jffs2_unknown_node);	//节点的大小
	
	jffs2_init_xattr_subsystem(c);// is used to initialize semaphore and list_head, and some variables.

	if ((ret = jffs2_do_mount_fs(c)))
		goto out_inohash;

	root_i = iget(sb, 1);	//根节点的inode
	sb->s_root = d_alloc_root(root_i);	//根目录
	if (!(sb->s_flags & MS_RDONLY))
		jffs2_start_garbage_collect_thread(c);	//回收线程开启
	return 0;
 }
最关键的三部:jffs2_do_mount_fs,iget,jffs2_start_garbage_collect_thread。

jffs2_do_mount_fs:扫描整个flash,并对flash数据进行分析挂载

iget:获得第一个inode

jffs2_start_garbage_collect_thread:开启垃圾回收线程

int jffs2_do_mount_fs(struct jffs2_sb_info *c)
{
	ret = jffs2_sum_init(c);

	if (jffs2_build_filesystem(c)) {
		goto out_free;
	}

	jffs2_calc_trigger_levels(c);

	return 0;

 }
重点是jffs2_build_filesystem
/* Scan plan:
 - Scan physical nodes. Build map of inodes/dirents. Allocate inocaches as we go
 - Scan directory tree from top down, setting nlink in inocaches
 - Scan inocaches for inodes with nlink==0
*/
static int jffs2_build_filesystem(struct jffs2_sb_info *c)
{
	/* First, scan the medium and build all the inode caches with
	   lists of physical nodes */
	ret = jffs2_scan_medium(c); 	//正如上面注释上所说扫描介质,
								//并且建立所有的节点缓存通过list of physical nodes
								//节点信息挂载在c->inocachelist中

	
	/* Now scan the directory tree, increasing nlink according to every dirent found. */
	for_each_inode(i, c, ic) {//c->inocache_list中的元素一个个扫描过去
		if (ic->scan_dents) {	//如果inocache中有目录项元素
			jffs2_build_inode_pass1(c, ic);//给有目录项指向的数据节点的nlink++
			cond_resched();
		}
	}

	/* Next, scan for inodes with nlink == 0 and remove them. If
	   they were directories, then decrement the nlink of their
	   children too, and repeat the scan. As that's going to be
	   a fairly uncommon occurrence, it's not so evil to do it this
	   way. Recursion bad. */
	   //nlink==0的那种属于没有目录项来指向的
	for_each_inode(i, c, ic) {
		if (ic->nlink)
			continue;
		jffs2_build_remove_unlinked_inode(c, ic, &dead_fds);
	}

	while (dead_fds) {
		fd = dead_fds;
		dead_fds = fd->next;

		ic = jffs2_get_ino_cache(c, fd->ino);

		if (ic)
			jffs2_build_remove_unlinked_inode(c, ic, &dead_fds);
		jffs2_free_full_dirent(fd);
	}

	jffs2_build_xattr_subsystem(c);
}
按照注释所言,这个函数,首先是要扫描物理节点,建立dirent、inode的map,再设置dirent的nlink,最后扫面nlink==o的inocache

扫描物理节点,建立dirent、inode的map

/*
扫描一个个物理扇区,并将它们的信息挂载到超级块(c)上
总体而言更新了c->inocache_list,c->wasted_size等信息,最主要的是inocache_list
还有c->free_list之类的链表中保存扇区信息
个人觉着这个函数的核心是jffs2_scan_eraseblock
*/
int jffs2_scan_medium(struct jffs2_sb_info *c)
{

	for (i=0; inr_blocks; i++) {	//一个扇区一个扇区扫描
		ret = jffs2_scan_eraseblock(c, jeb, buf_size?flashbuf:(flashbuf+jeb->offset),
						buf_size, s);	//扫描一个扇区,在超级块c的ionde_chache中挂载各种节
	//下面代码中有个switch,是对这个区挂载到sb的链表中进行一个分析,其实挺重要的,but代码太多,暂时省略
						

}
jffs2_scan_eraseblock的入口参数:超级块,擦出块,数据缓存,数据缓存长度,摘要信息。
/* Called with 'buf_size == 0' if buf is in fact a pointer _directly_ into
   the flash, XIP-style 
   c是超级块,jeb是要扫描的扇区,buf是数据缓存,buf_size是数据最大的大小
   s是概要,一般在最后面
   
   	这个函数的大体作用就是扫描一个扇区,然后将目录项,数据节点之类的
	都挂载到超级块的链表里面(inocache_list),可以通过ino来查询,如果是dirent的话
	可以用pino先找到inode_cache,再用name找到dirent,而它的ino可以相对应的inode。
	inode_cache中有flash的信息
   */
static int jffs2_scan_eraseblock (struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb,
				  unsigned char *buf, uint32_t buf_size, struct jffs2_summary *s) {
	if (jffs2_sum_active()) {
		……//如果有摘要的话,执行这部分代码
	}
	/* Scan only 4KiB of 0xFF before declaring it's empty */
	while(ofs < EMPTY_SCAN_SIZE(c->sector_size) && *(uint32_t *)(&buf[ofs]) == 0xFFFFFFFF)
		ofs += 4;		//如果分区头4k字节全为空,这时候ofs =1024
	//如果前面有空,后面还有一堆代码对这种情况进行分析,篇幅有限,省去代码跟分析

scan_more:
	while(ofs < jeb->offset + c->sector_size) {//开始扫描这个扇区里的数据,一个数据段一个数据段扫描
					//最糟糕的情况是扫描整页

		/* Make sure there are node refs available for use */
		err = jffs2_prealloc_raw_node_refs(c, jeb, 2);	//jeb->last_node生成,最后一个非空,参数c无视掉
							//其实就是生成一个jffs2_raw_node_ref然后jeb->last_node指向它


		node = (struct jffs2_unknown_node *)&buf[ofs-buf_ofs];	//头部结构,一开始要假装这个头是什么都不知道的

		if (*(uint32_t *)(&buf[ofs-buf_ofs]) == 0xffffffff) {	//如果是空
			……//一堆代码,扫面后面的是不是也都是空的
		}

		switch(je16_to_cpu(node->nodetype)) {//根据节点类型,省略篇幅,截取最常见的三个
		case JFFS2_NODETYPE_INODE:
			err = jffs2_scan_inode_node(c, jeb, (void *)node, ofs, s);//生成一个inode,挂载在c上面,可以通过info查询的到
						//暂时这么理解,将inode挂到sb(超级块)中
			break;

		case JFFS2_NODETYPE_DIRENT:	//这是个目录项
			err = jffs2_scan_dirent_node(c, jeb, (void *)node, ofs, s); 	//扫描目录节点
																//在c中挂载inode,在inode中挂载fd
			break;

		case JFFS2_NODETYPE_CLEANMARKER:
				jffs2_link_node_ref(c, jeb, ofs | REF_NORMAL, c->cleanmarker_size, NULL);
				//jeb->first_node,c->free_size,jeb->free_size这些值在上面这个过程中有了变化
				//jeb->last_node->flash_offset也变成了REF_NORMAL
				ofs += PAD(c->cleanmarker_size);
				//4字节对齐,还有就是c->cleanmarker_size就是节点头占用的大小
			break;
		}
	}

	D1(printk(KERN_DEBUG "Block at 0x%08x: free 0x%08x, dirty 0x%08x, unchecked 0x%08x, used 0x%08x, wasted 0x%08x\n",
		  jeb->offset,jeb->free_size, jeb->dirty_size, jeb->unchecked_size, jeb->used_size, jeb->wasted_size));
	//总结这一块里的信息,总结这一个扇区里的信息,多少字节空闲,多少被用,多少脏,多少浪费
	//clearmask跟没过时的目录项是used ,inode数据是unchecked,过时的目录项是waste
	
	return jffs2_scan_classify_jeb(c, jeb);  //返回这个擦出块的使用状况
}
上面代码中有个重要的函数,没有截取出来jffs2_fill_scan_buf这个函数的作用是从物理介质中去取得相应的数据。

上面的注释已经说明了这个函数的大体作用,扫描物理介质,一个个node的读过来。一开始假装是一个unknow的node,然后根据nodetype来分析这个node的类型。

如果是inode的话:生成一个inocache,挂载到超级块的inocache_list中;nlink=0

如果是dirent的话:生成一个inocache(如果找不到相应的pino的ic的话,就要生成),挂载到超级块的inocache_list中,如果是根目录下的dirent,nlink=1。生成一个临时变量fd,这个fd生成在这里,but释放在其他地方,且fd中存放dirent的各种信息,然后将这个fd挂载到ic->scan_dents链表中

如果是cleanmask:一般情况下,就是个头,这种节点,直接偏移,对齐,over。


程序将返回到jffs2_build_filesystem,执行jffs2_build_inode_pass1,这个函数的作用是将dirent的节点所指向的inode节点的nlink++。因为dirent会指向一个inode,在jffs2中dirent的ino(如果ino=0,则表示这个dirent已经是被删除了的)指向inode在sb->inocache_list中的ino,此函数就是将inode的ino_cache->nlink++。

jffs2_build_remove_unlinked_inode这个函数的作用,是将没有nlink(没有nlink是个没人要的怨妇,浪费空间)的ic给标记过时,修改freesize,dirtysize之类的信息。


程序返回到jffs2_do_fill_super,接下来执行iget。这个函数的作用是获得第一个inode,也就是根目录的inode。抽丝剥茧,这个函数最后调用的是jffs2_read_inode(因为sb->s_op->read_inode(inode)  === jffs2_read_inode)。

void jffs2_read_inode (struct inode *inode)		//这个函数里有inode初始化
{
	
	f = JFFS2_INODE_INFO(inode);	//这个inode是系统刚刚给分配的inode
	c = JFFS2_SB_INFO(inode->i_sb);	//根据sb->s_fs_info来得到jffs2的超级块
	
	jffs2_init_inode_info(f);	//红黑树是NULL,metadata,dents都是NULL
	ret = jffs2_do_read_inode(c, f, inode->i_ino, &latest_node);

	switch (inode->i_mode & S_IFMT) {  //各种节点类型
		……//一大堆代码,主要是对inode节点操作的赋值
	}

}
重点也是难点是jffs2_do_read_inode,这个函数我也看得迷迷糊糊,应该有很大的漏洞。入口参数:jffs2的超级块,jffs2的inode,inode的ino(挂载的时候,这个是1),回调参数。
/* Scan the list of all nodes present for this ino, build map of versions, etc. */
int jffs2_do_read_inode(struct jffs2_sb_info *c, struct jffs2_inode_info *f,
			uint32_t ino, struct jffs2_raw_inode *latest_node)
{
 retry_inocache:
	f->inocache = jffs2_get_ino_cache(c, ino);

	if (f->inocache) {
	/* Check its state. We may need to wait before we can use it */
	//一堆代码来检测这个inocache的状态,可能需要等待
	}
	
	if (!f->inocache && ino == 1) {	//如果inocache不在,且ino=1,初始化的时候可能会进入,因为根节点必须存在inode,没有也得搞一个出来
	……
	}

	return jffs2_do_read_inode_internal(c, f, latest_node);
}
最蛋疼的jffs2_do_read_inode_internal。入口参数:jffs2的超级块,jffs2的inode,回调参数。由于不太懂,所以这个函数只大致说一下作用,将有数据的node挂到f的红黑树上面。没有数据,即使version再高也没用。入口参数中的f->fragtree这颗树上挂有数据,按照ofs+size来挂载的
/*
将有数据的node插到f->freg这颗红黑树上,latest_node是version最高的那个,不管有没有数据
*/
static int jffs2_do_read_inode_internal(struct jffs2_sb_info *c,
					struct jffs2_inode_info *f,
					struct jffs2_raw_inode *latest_node)
{
	/* Grab all nodes relevant to this ino */
	ret = jffs2_get_inode_nodes(c, f, &tn_list, &fd_list, &f->highest_version, &latest_mctime, &mctime_ver);
		//所有的node,然后返回最高版本,以及一颗红黑树tn_list,这颗红黑树是按照version插入的

	f->dents = fd_list;

	rb = rb_first(&tn_list);

	while (rb) {	//查找第一个有有效数据的fn,rb这颗红黑树是根据version来排列的
		if (fn->size) {	//有数据
			ret = jffs2_add_older_frag_to_fragtree(c, f, tn);	//红黑树f->frag,将tn->fn插进去,按照fn->ofs跟size来插
														//可以理解成数据最后的结束位置,来插入红黑树
		}
		//下面一堆红黑树的操作,删除rb的操作,且如果节点过期就删除掉它
		rb = rb_next(rb);	//rb指向下一个
		/* Remove the spent tn from the tree; don't bother rebalancing
		 * but put our right-hand child in our own place. */
	}
	jffs2_dbg_fragtree_paranoia_check_nolock(f);


	ret = jffs2_flash_read(c, ref_offset(fn->raw), sizeof(*latest_node), &retlen, (void *)latest_node);
	//去读这个节点的信息,这个lastnode还是version最高的那个
	switch(jemode_to_cpu(latest_node->mode) & S_IFMT) {
	//暂时指向分析一个
	case S_IFREG:
		/* If it was a regular file, truncate it to the latest node's isize */
		//isize是实际长度
		jffs2_truncate_fragtree(c, &f->fragtree, je32_to_cpu(latest_node->isize));//在f->frag红黑树上删除那些长度+偏移>isize的节点。
		break;
	}
}


程序返回到jffs2_do_fill_super,开启垃圾回收线程。

挂载到此结束。




你可能感兴趣的:(linux内核)