上篇博文已经把挂载阶段讲完了,那挂载操作是不是就结束了呢?
答案是否定的,还留下了一些操作给gc线程去完成,这么做的目的是为了让挂载阶段所使用的时间减少,提升用户体验(说白了就是让用户以为已经挂载完成了,实际上还有点无伤大雅的小问题需要gc线程处理一下)。
那么纠结gc线程完成了什么挂载遗留下来的任务呢?
这里省略gc线程的前面部分内容,直接到关于完成挂载工作最后部分的代码处:jffs2_garbage_collect_thread-> jffs2_garbage_collect_pass
gc线程起来之后,首先要做的是对挂载过程中未校验的结点完成crc校验工作。直到c->unchecked_size为0
/* jffs2_garbage_collect_pass
* Make a single attempt to progress GC. Move one node, and possibly
* start erasing one eraseblock.
*/
int jffs2_garbage_collect_pass(struct jffs2_sb_info *c)
{
struct jffs2_inode_info *f;
struct jffs2_inode_cache *ic;
struct jffs2_eraseblock *jeb;
struct jffs2_raw_node_ref *raw;
uint32_t gcblock_dirty;
int ret = 0, inum, nlink;
int xattr = 0;
if (mutex_lock_interruptible(&c->alloc_sem))
return -EINTR;
for (;;) { //进入死循环
spin_lock(&c->erase_completion_lock);
if (!c->unchecked_size) //如果unchecked_size为0,则退出循环
break;
/* We can't start doing GC yet. We haven't finished checking
the node CRCs etc. Do it now. */
/* checked_ino is protected by the alloc_sem */
if (c->checked_ino > c->highest_ino && xattr) {
pr_crit("Checked all inodes but still 0x%x bytes of unchecked space?\n",
c->unchecked_size);
jffs2_dbg_dump_block_lists_nolock(c);
spin_unlock(&c->erase_completion_lock);
mutex_unlock(&c->alloc_sem);
return -ENOSPC;
}
spin_unlock(&c->erase_completion_lock);
if (!xattr)
xattr = jffs2_verify_xattr(c);
spin_lock(&c->inocache_lock);
ic = jffs2_get_ino_cache(c, c->checked_ino++);//从头开始一个个ino号进行检查,首先获取jffs2_inode_cache实例
if (!ic) { //如果获取不到,可能是因为flash上没有该inode号的结点,跳过,继续检查下一个ino号
spin_unlock(&c->inocache_lock);
continue;
}
if (!ic->pino_nlink) { //文件硬链接为0或目录被删除,无需检查
jffs2_dbg(1, "Skipping check of ino #%d with nlink/pino zero\n",
ic->ino);
spin_unlock(&c->inocache_lock);
jffs2_xattr_delete_inode(c, ic);
continue;
}
switch(ic->state) {
case INO_STATE_CHECKEDABSENT:
case INO_STATE_PRESENT:
jffs2_dbg(1, "Skipping ino #%u already checked\n",
ic->ino);
spin_unlock(&c->inocache_lock);
continue;
case INO_STATE_GC:
case INO_STATE_CHECKING:
pr_warn("Inode #%u is in state %d during CRC check phase!\n",
ic->ino, ic->state);
spin_unlock(&c->inocache_lock);
BUG();
case INO_STATE_READING:
/* We need to wait for it to finish, lest we move on
and trigger the BUG() above while we haven't yet
finished checking all its nodes */
jffs2_dbg(1, "Waiting for ino #%u to finish reading\n",
ic->ino);
/* We need to come back again for the _same_ inode. We've
made no progress in this case, but that should be OK */
c->checked_ino--;
mutex_unlock(&c->alloc_sem);
sleep_on_spinunlock(&c->inocache_wq, &c->inocache_lock);
return 0;
default:
BUG();
case INO_STATE_UNCHECKED: //一般inode结点是这个状态
;
}
ic->state = INO_STATE_CHECKING;
spin_unlock(&c->inocache_lock);
jffs2_dbg(1, "%s(): triggering inode scan of ino#%u\n",
__func__, ic->ino);
ret = jffs2_do_crccheck_inode(c, ic); //主要的检查工作交给jffs2_do_crccheck_inode
if (ret)
pr_warn("Returned error for crccheck of ino #%u. Expect badness...\n",
ic->ino);
jffs2_set_inocache_state(c, ic, INO_STATE_CHECKEDABSENT); //检查完成后,将状态改为INO_STATE_CHECKEDABSENT
mutex_unlock(&c->alloc_sem);
return ret;
}
...
}
下面接着来看下jffs2_do_crccheck_inode函数。首先为每个jffs2_inode_cache分配一个jffs2_inode_info结构体,然后将检查工作交给jffs2_do_read_inode_internal函数
int jffs2_do_crccheck_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic)
{
struct jffs2_raw_inode n;
struct jffs2_inode_info *f = kzalloc(sizeof(*f), GFP_KERNEL); //分配jffs2_inode_info结构体
int ret;
if (!f)
return -ENOMEM;
mutex_init(&f->sem);
mutex_lock(&f->sem);
f->inocache = ic;
ret = jffs2_do_read_inode_internal(c, f, &n); //主要的检查函数是这个
mutex_unlock(&f->sem);
jffs2_do_clear_inode(c, f);
jffs2_xattr_do_crccheck_inode(c, ic);
kfree (f);
return ret;
}
jffs2_do_read_inode_internal读取文件进而组建tn树,并加入jffs2_inode_info的fragtree中
static int jffs2_do_read_inode_internal(struct jffs2_sb_info *c,
struct jffs2_inode_info *f,
struct jffs2_raw_inode *latest_node)
{
struct jffs2_readinode_info rii; //这是一个临时结构体,用于承载inode的相关数据信息
uint32_t crc, new_size;
size_t retlen;
int ret;
dbg_readinode("ino #%u pino/nlink is %d\n", f->inocache->ino,
f->inocache->pino_nlink);
memset(&rii, 0, sizeof(rii));//初始化
/* Grab all nodes relevant to this ino */
ret = jffs2_get_inode_nodes(c, f, &rii); //这个函数会填充rii,其遍历其ino下所有节点,对不同的节点相应的将jffs2_full_dirent和jffs2_full_dnode加入jffs2_readinode_info中
if (ret) {
JFFS2_ERROR("cannot read nodes for ino %u, returned error is %d\n", f->inocache->ino, ret);
if (f->inocache->state == INO_STATE_READING)
jffs2_set_inocache_state(c, f->inocache, INO_STATE_CHECKEDABSENT);
return ret;
}
ret = jffs2_build_inode_fragtree(c, f, &rii); //根据rii数据,构建数据的红黑树便于查找
if (ret) {
JFFS2_ERROR("Failed to build final fragtree for inode #%u: error %d\n",
f->inocache->ino, ret);
if (f->inocache->state == INO_STATE_READING)
jffs2_set_inocache_state(c, f->inocache, INO_STATE_CHECKEDABSENT);
jffs2_free_tmp_dnode_info_list(&rii.tn_root);
/* FIXME: We could at least crc-check them all */
if (rii.mdata_tn) {
jffs2_free_full_dnode(rii.mdata_tn->fn);
jffs2_free_tmp_dnode_info(rii.mdata_tn);
rii.mdata_tn = NULL;
}
return ret;
}
if (rii.mdata_tn) { //这种情况存在于该ino号指向的是一个目录、设备文件
if (rii.mdata_tn->fn->raw == rii.latest_ref) {
f->metadata = rii.mdata_tn->fn;
jffs2_free_tmp_dnode_info(rii.mdata_tn);
} else {
jffs2_kill_tn(c, rii.mdata_tn);
}
rii.mdata_tn = NULL;
}
f->dents = rii.fds;//把full_dirent赋给jffs2_inode_info
jffs2_dbg_fragtree_paranoia_check_nolock(f);
if (unlikely(!rii.latest_ref)) { //若这个ino没有数据节点
/* No data nodes for this inode. */
if (f->inocache->ino != 1) {
JFFS2_WARNING("no data nodes found for ino #%u\n", f->inocache->ino);
if (!rii.fds) {//如果没有子目录,这里可能是文件节点或者没有子目录的目录
if (f->inocache->state == INO_STATE_READING)
jffs2_set_inocache_state(c, f->inocache, INO_STATE_CHECKEDABSENT);
return -EIO;
}
//否则,该目录的inode节点不存在,为了避免他的目录项丢失,做一个假的inode节点
JFFS2_NOTICE("but it has children so we fake some modes for it\n");
}
//下面建立假的inode节点
latest_node->mode = cpu_to_jemode(S_IFDIR|S_IRUGO|S_IWUSR|S_IXUGO);
latest_node->version = cpu_to_je32(0);
latest_node->atime = latest_node->ctime = latest_node->mtime = cpu_to_je32(0);
latest_node->isize = cpu_to_je32(0);
latest_node->gid = cpu_to_je16(0);
latest_node->uid = cpu_to_je16(0);
if (f->inocache->state == INO_STATE_READING)
jffs2_set_inocache_state(c, f->inocache, INO_STATE_PRESENT);
return 0;
}
ret = jffs2_flash_read(c, ref_offset(rii.latest_ref), sizeof(*latest_node), &retlen, (void *)latest_node);//读取latest_ref节点
if (ret || retlen != sizeof(*latest_node)) {
JFFS2_ERROR("failed to read from flash: error %d, %zd of %zd bytes read\n",
ret, retlen, sizeof(*latest_node));
/* FIXME: If this fails, there seems to be a memory leak. Find it. */
return ret ? ret : -EIO;
}
crc = crc32(0, latest_node, sizeof(*latest_node)-8);//crc校验
if (crc != je32_to_cpu(latest_node->node_crc)) {
JFFS2_ERROR("CRC failed for read_inode of inode %u at physical location 0x%x\n",
f->inocache->ino, ref_offset(rii.latest_ref));
return -EIO;
}
switch(jemode_to_cpu(latest_node->mode) & S_IFMT) {//根据不同类别的节点不同处理
case S_IFDIR:
if (rii.mctime_ver > je32_to_cpu(latest_node->version)) {
/* The times in the latest_node are actually older than
mctime in the latest dirent. Cheat. */
latest_node->ctime = latest_node->mtime = cpu_to_je32(rii.latest_mctime);
}
break;
case S_IFREG:
/* If it was a regular file, truncate it to the latest node's isize */
new_size = jffs2_truncate_fragtree(c, &f->fragtree, je32_to_cpu(latest_node->isize));
if (new_size != je32_to_cpu(latest_node->isize)) {
JFFS2_WARNING("Truncating ino #%u to %d bytes failed because it only had %d bytes to start with!\n",
f->inocache->ino, je32_to_cpu(latest_node->isize), new_size);
latest_node->isize = cpu_to_je32(new_size);
}
break;
case S_IFLNK:
/* Hack to work around broken isize in old symlink code.
Remove this when dwmw2 comes to his senses and stops
symlinks from being an entirely gratuitous special
case. */
if (!je32_to_cpu(latest_node->isize))
latest_node->isize = latest_node->dsize;
if (f->inocache->state != INO_STATE_CHECKING) {
/* Symlink's inode data is the target path. Read it and
* keep in RAM to facilitate quick follow symlink
* operation. */
uint32_t csize = je32_to_cpu(latest_node->csize);
if (csize > JFFS2_MAX_NAME_LEN)
return -ENAMETOOLONG;
f->target = kmalloc(csize + 1, GFP_KERNEL);
if (!f->target) {
JFFS2_ERROR("can't allocate %u bytes of memory for the symlink target path cache\n", csize);
return -ENOMEM;
}
ret = jffs2_flash_read(c, ref_offset(rii.latest_ref) + sizeof(*latest_node),
csize, &retlen, (char *)f->target);//软连接节点还需读出软连接指向的位置
if (ret || retlen != csize) {
if (retlen != csize)
ret = -EIO;
kfree(f->target);
f->target = NULL;
return ret;
}
f->target[csize] = '\0';
dbg_readinode("symlink's target '%s' cached\n", f->target);
}
/* fall through... */
case S_IFBLK:
case S_IFCHR:
/* Certain inode types should have only one data node, and it's
kept as the metadata node */
if (f->metadata) {
JFFS2_ERROR("Argh. Special inode #%u with mode 0%o had metadata node\n",
f->inocache->ino, jemode_to_cpu(latest_node->mode));
return -EIO;
}
if (!frag_first(&f->fragtree)) {
JFFS2_ERROR("Argh. Special inode #%u with mode 0%o has no fragments\n",
f->inocache->ino, jemode_to_cpu(latest_node->mode));
return -EIO;
}
/* ASSERT: f->fraglist != NULL */
if (frag_next(frag_first(&f->fragtree))) {
JFFS2_ERROR("Argh. Special inode #%u with mode 0x%x had more than one node\n",
f->inocache->ino, jemode_to_cpu(latest_node->mode));
/* FIXME: Deal with it - check crc32, check for duplicate node, check times and discard the older one */
return -EIO;
}
/* OK. We're happy */
f->metadata = frag_first(&f->fragtree)->node;
jffs2_free_node_frag(frag_first(&f->fragtree));
f->fragtree = RB_ROOT;
break;
}
if (f->inocache->state == INO_STATE_READING)
jffs2_set_inocache_state(c, f->inocache, INO_STATE_PRESENT);
return 0;
}
经理了这么多的分析,mount的过程终于分析完了。
能看到这里的小伙伴,一定是非常有毅力的。
值得一提的是,mount的整个流程中,对于数据节点,所做的CRC校验只验了节点头部的CRC值,后面gc的检查会检查数据部分的CRC值。
另外,mount过程前期一次读取一大片flash的数据,而后期gc线程起来做检查,一次只读一小片flash的数据。