在上一节,我们把jbd2的初始化代码讲完了,初始代码的结果是创建了四个cache,
jbd2_revoke_record_cache ----- struct jbd2_revoke_record_s
jbd2_revoke_table_cache ------ struct jbd2_revoke_table_s
jbd2_journal_head_cache ------ struct journal_head
jbd2_handle_cache ------ handle_t (struct handle_s)
还有一个目录
/proc/jbd2/
让我们来看看
ls /proc/fs/jbd2/
sda10:8 sda7:8 sda9:8
在jbd2下有三个目录,因为我的机器上有三个ext4分区,分别是sda7/9/10,所以它会在jbd2里创建三个目录,分别代表三个分区,再让我们看看里面买的什么药,
ls /proc/fs/jbd2/sda10\:8/
history info
是两个文件,遇到文件,我们就cat一下。
cat /proc/fs/jbd2/sda10\:8/info
48 transaction, each upto 8192 blocks
average:
0ms waiting for transaction
2396ms running transaction
0ms transaction was being locked
0ms flushing data (in ordered mode)
60ms logging transaction
36369us average transaction commit time
23 handles per transaction
8 blocks per transaction
9 logged blocks per transaction
如果你不是英文差到连牛A与牛C之间是什么都不知道的话,你应该能看懂上面的洋文,就是一些关于这个分区日志的基本信息,如果我们也cat一下history,你就会看一些历史记录
cat /proc/fs/jbd2/sda10\:8/history
R/C tid wait run lock flush log hndls block inlog ctime write drop close
R 8568 0 544 0 0 32 19 9 10
R 8569 0 5004 0 0 280 212 17 18
R 8570 0 1588 0 0 76 60 12 13
………
就是记录一些历史操作的结果。
这些东西是怎么出来的,就让我们来撬开它的大门,因为它在每个分区都有一个,所以在第个分区初始化的时候就把这些东西弄好了。
为了让大家容易理解,我把建立这个功能的函数的调用关系作了一张图
在fs/ext4/super.c(2631)有这么一句
if (ext4_load_journal(sb, es, journal_devnum)) goto failed_mount3;
|
这句话是在ext4_fill_super里调用,这个函数是ext4文件系统为每个分区初始化的时候构造 super_block调用的。关于ext4文件系统,我也准备写一个详细的分析,所以在这里不多说了。让我们来看看ext4_load_journal这个函数,在fs/ext4/super.c(3030)
static int ext4_load_journal(struct super_block *sb, struct ext4_super_block *es, unsigned long journal_devnum) { journal_t *journal; unsigned int journal_inum = le32_to_cpu(es->s_journal_inum); dev_t journal_dev; int err = 0; int really_read_only;
BUG_ON(!EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_HAS_JOURNAL));
if (journal_devnum && journal_devnum != le32_to_cpu(es->s_journal_dev)) { printk(KERN_INFO "EXT4-fs: external journal device major/minor " "numbers have changed\n"); journal_dev = new_decode_dev(journal_devnum); } else journal_dev = new_decode_dev(le32_to_cpu(es->s_journal_dev));
really_read_only = bdev_read_only(sb->s_bdev);
/* * Are we loading a blank journal or performing recovery after a * crash? For recovery, we need to check in advance whether we * can get read-write access to the device. */
if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER)) { if (sb->s_flags & MS_RDONLY) { printk(KERN_INFO "EXT4-fs: INFO: recovery " "required on readonly filesystem.\n"); if (really_read_only) { printk(KERN_ERR "EXT4-fs: write access " "unavailable, cannot proceed.\n"); return -EROFS; } printk(KERN_INFO "EXT4-fs: write access will " "be enabled during recovery.\n"); } }
if (journal_inum && journal_dev) { printk(KERN_ERR "EXT4-fs: filesystem has both journal " "and inode journals!\n"); return -EINVAL; }
if (journal_inum) { if (!(journal = ext4_get_journal(sb, journal_inum))) return -EINVAL; } else { if (!(journal = ext4_get_dev_journal(sb, journal_dev))) return -EINVAL; }
if (journal->j_flags & JBD2_BARRIER) printk(KERN_INFO "EXT4-fs: barriers enabled\n"); else printk(KERN_INFO "EXT4-fs: barriers disabled\n");
if (!really_read_only && test_opt(sb, UPDATE_JOURNAL)) { err = jbd2_journal_update_format(journal); if (err) { printk(KERN_ERR "EXT4-fs: error updating journal.\n"); jbd2_journal_destroy(journal); return err; } }
if (!EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RECOVER)) err = jbd2_journal_wipe(journal, !really_read_only); if (!err) err = jbd2_journal_load(journal);
if (err) { printk(KERN_ERR "EXT4-fs: error loading journal.\n"); jbd2_journal_destroy(journal); return err; }
EXT4_SB(sb)->s_journal = journal; ext4_clear_journal_err(sb, es);
if (journal_devnum && journal_devnum != le32_to_cpu(es->s_journal_dev)) { es->s_journal_dev = cpu_to_le32(journal_devnum); sb->s_dirt = 1;
/* Make sure we flush the recovery flag to disk. */ ext4_commit_super(sb, es, 1); }
return 0; }
|
这个函数是我们目前见到最长的函数,且听我慢慢说来。第6行unsigned int journal_inum = le32_to_cpu(es->s_journal_inum);这行的意思是把一个小端(little endian)数转成CPU数,关于大小端,有点计算机道德的人都知道,在计算机处理器中,有大小端之分,简而言之就是假如一个short数0xABCD,如果在计算机内存中AB在放在大的地址CD小的地址,则是大端,比如以前因为Apple闻名的powerPC处理器就是大端的,反过来而是小端,我们所用的Intel的就是小端。因为es是ext4_super_block,是ext4文件系统里的物理结构读出来的是小端,我们为了保证在各种CPU上都能运行,所以就定义了这么一个与CPU相关的宏来转换。这样的函数在以后会经常看到,不重复。这个函数就是读出日志所在的inode节点的编号。
11行就是如果这个分区有兼容的日志系统如是从ext3转过来的,那么就会报一个bug.
13-19行就是根据分区号创建一个日志设备,如果if成立,表示要挂载的分区号和在分区里在放的分区号不一样,当然以现在的分区号为准,如果走else那么就以硬盘存的为准。实际上日志设备并不是一个真正的设备,在通常情况下,它只是硬盘分区里的一个目录。
21-41行就是检查分区是不是只读分区,如果是只读分区,那就没有必要加载日志系统,你不能写,要日志来做什么?
43-55行比较有意思,就是在通常情况下,我们的日志只是一个目录,但是也有些用一个独立的分区来记录日志,所以如果一个分区同时有两种,那么就出错了。如果只有一种,那就根据存在的这种生成一个journal对象,其中ext4_get_journal和ext4_get_dev_journal在下一节会做详细的讲解,因为jbd2的肉体是由他们来构建的。
57-60行就是测试一下这个硬盘是不是开启了硬盘屏障,IDE Barrier是指有些硬盘有很大的容量,但是有些主板不支持那么大的,我记得三年前很多同志的硬盘只能识别137G,就是这个IDE Barrier.
62-69行就是如果分区不是只读,并且需要更新日志,那么就更新,有两种情况,一是日志一到性更新,还有就是把日志的版本更新。
71-72行检查日志如果有不兼容的情况,就要把日志清扫,当然如果分区只读,么就打印一个信息"JBD: Ignoring recovery information on journal",然后就什么也不做,如果是读写分区,那么就会清扫日志,把日志内容清空。
73-74行,把日志信息从分区的super_block中读出来。这个jbd2_journal_load也不是简洁的东西,代码和调用很多,但是没有什么难的东西,最难的东西是它的这用调用关系jbd2_journal_load -> load_superblock -> journal_get_superblock 最后这个函数里第9-15行那点从硬盘读数据的代码, 那个可以先不管,知道是从硬盘读取相应的数据就行了。
85-92行就是根据刚才的情况更新super_block,并把它写到硬盘上。这样这个函数就完成使命了,这篇日志也同样完成使命了。下节开始讲ext4_get_journal和ext4_get_dev_journal。
阅读(1760) | 评论(0) | 转发(0) |