按照文件系统的惯例,如果是非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,开启垃圾回收线程。
挂载到此结束。