はじめに
Linuxのext4のjournalとして使われているjbd2モジュールが書き出すjournalファイルのデータ構造を調べることで、ext4のjbd2の設計を理解しようという趣旨の雑記です。
なお、Linux-4.6くらいを見ていて、内部ジャーナル(同じFilesystem内のs_journal_inumで指定されたinode番号)の場合だけを考えています。
ディスクレイアウト
kernelのext4のWikiを見るなり、直接ソースコードを見る(include/linux/jbd2.h)なりするのがよい。というか、ろくに内部構造を説明した資料が英語も含めてないので、これしか手段がない。
ブロックの種類
journalはブロックで構成される。各ブロックは下記のいずれかとなる
120 /*
121 * Descriptor block types:
122 */
123
124 #define JBD2_DESCRIPTOR_BLOCK 1
125 #define JBD2_COMMIT_BLOCK 2
126 #define JBD2_SUPERBLOCK_V1 3
127 #define JBD2_SUPERBLOCK_V2 4
128 #define JBD2_REVOKE_BLOCK 5
JBD2_SUPERBLOCK_V1, JBD2_SUPERBLOCK_V2
jbd2のsuperblockとなる。journalの中の先頭ブロックに書かれる。V1かV2かのどちらかとなる。ext4ができた頃にはすでにJBD2_SUPERBLOCK_V2が当たり前になっていたハズ。
JBD2_DESCRIPTOR_BLOCK
jbd2の基本となるブロック。1つ以上のdescriptor(journal_block_tag_t)が含まれる。ディスクリプタの数だけこの後ろにデータブロックが連なる。それらデータブロックがディスクどこのデータブロックのトランザクションかを示している。
JBD2_COMMIT_BLOCK
JBD2_DESCRIPTOR_BLOCKとそれに連なるデータブロックが完全に書き出されたあとに書かれるブロック。先のJBD2_DESCRIPTOR_BLOCKで作られたトランザクションが有効であることを示す。これがあればroll forward、なければ何もしない(まだ実ブロックへの書き出しが行われていなかったことが保障される)。
JBD2_REVOKE_BLOCK
以前書かれたJBD2_DESCRIPTOR_BLOCKのトランザクションを無効にするための指示のブロック。一度は書かれたもののやっぱり取り消したい場合に使われる。
具体的には、data=journalではない場合に、一度はmetadataとして使われjournalに書かれたブロックがその後消されて別のinodeのデータの中身となろうとした場合。data=journalではない場合は、データの中身はjournalに残らないので、このケースで問題となる。data=journalの場合は、データの中身もjournalに残すので問題にならず、JBD2_REVOKE_BLOCKは必要なくなる。ext4の__ext4_forget()の中でjbd2_journal_forget()を呼ぶのかjbd2_journal_revoke()を呼ぶのか、といったあたりに現れている。
journalの上限
journalは中を順繰りに使う。長い間使った後だとどこまでが有効なトランザクションかわからなくなる。それはsuperblockのs_sequence, s_startで管理される。
s_startは、journalの中のブロックの位置を示す。replayするときはここより後ろを確認すればよいという意味になる。
s_sequenceは、トランザクションの番号となる。s_startのブロック位置にs_sequenceの番号のJBD2_DESCRIPTOR_BLOCKが書かれていることが期待値となる。replayするときは、トランザクションの番号をインクリメントしながら期待通りのJBD2_DESCRIPTOR_BLOCKが現れているかを確認し、現れていない箇所・壊れてる(checksum合わない)箇所・JBD2_COMMIT_BLOCKが確認できない箇所、までを読み取ってreplayする。
データがキレイにはき出されたタイミングやumountなどでs_sequenceにゼロが入ることがある。これはreplayする必要がないことを表す。replayする必要がないならmount時間を短くすることができたりとメリットになる。
ここで気になるのが、トランザクションの番号がオーバフローしないのかどうかの点。結論だけ言うと問題はない。
- トランザクション番号の比較はmodulo arithmeticで行われる(int型の比較は引き算して0より大きいかどうかで判断、オーバフローする可能性のあるint値の比較の基本)
- journalのサイズはmodulo arithmeticででもおかしくなる(signed intの上限)ほど大きくはならない程度のブロック数しかない
jbd2のブロックサイズ
jbd2のブロックサイズはext4のブロックサイズと同じとなる。
で肝心のext4のブロックサイズは、オプションで指定された場合、/etc/mke2fs.confに書かれた値、が基本となりつつ、それでも決まらない場合はページサイズとなっている模様。ただし仕様として、1024, 2048, 4096のいずれかしか選べないことになっている(manに記載アリ)。特にオプションを指定しなかった場合(ext4の場合)はmke2fs.confにより4096となる。e2fsprogsのmke2fs.cより、
if (blocksize <= 0) {
use_bsize = get_int_from_profile(fs_types, "blocksize", 4096);
if (use_bsize == -1) {
use_bsize = sys_page_size;
if (is_before_linux_ver(2, 6, 0) && use_bsize > 4096)
use_bsize = 4096;
}
if (lsector_size && use_bsize < lsector_size)
use_bsize = lsector_size;
if ((blocksize < 0) && (use_bsize < (-blocksize)))
use_bsize = -blocksize;
blocksize = use_bsize;
fs_blocks_count /= (blocksize / 1024);
} else {
if (blocksize < lsector_size) { /* Impossible */
com_err(program_name, EINVAL, "%s",
_("while setting blocksize; too small "
"for device\n"));
exit(1);
} else if ((blocksize < psector_size) &&
(psector_size <= sys_page_size)) { /* Suboptimal */
fprintf(stderr, _("Warning: specified blocksize %d is "
"less than device physical sectorsize %d\n"),
blocksize, psector_size);
}
}
lsector_sizeは、e2fsprogsのlib/ext2fs/getsectsize.cのext2fs_get_device_sectsize()のBLKSSZGET(ioctl)で決まる。
/*
* Returns the logical sector size of a device
*/
errcode_t ext2fs_get_device_sectsize(const char *file, int *sectsize)
{
int fd;
fd = ext2fs_open_file(file, O_RDONLY, 0);
if (fd < 0)
return errno;
#ifdef BLKSSZGET
if (ioctl(fd, BLKSSZGET, sectsize) >= 0) {
close(fd);
return 0;
}
#endif
#ifdef DIOCGSECTORSIZE
if (ioctl(fd, DIOCGSECTORSIZE, sectsize) >= 0) {
close(fd);
return 0;
}
#endif
*sectsize = 0;
close(fd);
return 0;
}
BLKSSZGETは、kernel/block/ioctl.cのblkdev_ioctl()より、
556 case BLKBSZGET: /* get block device soft block size (cf. BLKSSZGET) */
557 return put_int(arg, block_size(bdev));
558 case BLKSSZGET: /* get block device logical block size */
559 return put_int(arg, bdev_logical_block_size(bdev));
560 case BLKPBSZGET: /* get block device physical block size */
561 return put_uint(arg, bdev_physical_block_size(bdev));
いろいろ各種ドライバやらとつなぐ汎用層がややこしいもの、最終的には、kernel/drivers/scsi/sd.cのblk_queue_logical_block_size(), blk_queue_physical_block_size()で設定した値に行き着く。これは、SCSIコマンドのread_capacity_16(SERVICE_ACTION_IN_16==0x9e, SAI_READ_CAPACITY_16=0x10)で取得した値。具体的に、下記サンプルプログラムを手元のSSDで実行して見るとこうなった。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
int main(int argc, char *argv[]) {
int ret, fd, bsize;
#define IOCTLWRAPPER(iocname) do { \
ret=ioctl(fd,iocname,&bsize); \
if(ret){perror(#iocname); \
}else{printf(#iocname" is %d\n",bsize);} \
}while(0)
fd = open ("/dev/sda1", O_RDONLY);
if ( fd <= 0) {
perror("open /dev/sda1");
} else {
IOCTLWRAPPER(BLKBSZGET);
IOCTLWRAPPER(BLKSSZGET);
IOCTLWRAPPER(BLKPBSZGET);
close(fd);
}
return 0;
}
[rarul@tina blockdev]$ sudo ./blockdev
[sudo] password for rarul:
BLKBSZGET is 4096
BLKSSZGET is 512
BLKPBSZGET is 512
今時はHDDもSSDもソフトウェア互換性を考慮して**AFT(Advanced Format)**になってるハズなので、市販の民生品はみなこれと同じ値になるんじゃないかと思う。
journalのファイルサイズ
オプションで指定されたファイルサイズを尊重しつつ、指定がない場合はe2fsprogsのmkjournal.c:ext2fs_default_journal_size()で決まる。
/*
* Find a reasonable journal file size (in blocks) given the number of blocks
* in the filesystem. For very small filesystems, it is not reasonable to
* have a journal that fills more than half of the filesystem.
*
* n.b. comments assume 4k blocks
*/
int ext2fs_default_journal_size(__u64 num_blocks)
{
if (num_blocks < 2048)
return -1;
if (num_blocks < 32768) /* 128 MB */
return (1024); /* 4 MB */
if (num_blocks < 256*1024) /* 1 GB */
return (4096); /* 16 MB */
if (num_blocks < 512*1024) /* 2 GB */
return (8192); /* 32 MB */
if (num_blocks < 4096*1024) /* 16 GB */
return (16384); /* 64 MB */
if (num_blocks < 8192*1024) /* 32 GB */
return (32768); /* 128 MB */
if (num_blocks < 16384*1024) /* 64 GB */
return (65536); /* 256 MB */
if (num_blocks < 32768*1024) /* 128 GB */
return (131072); /* 512 MB */
return 262144; /* 1 GB */
}
通常はブロックサイズが4096なので、コメント通り、おおむねパーティションのサイズで決まる。
で結局、最大でもブロックサイズの262144倍ということなので、intのmodulo arithmeticではオーバフローは起こりえないことが確認できた。
jbd2のコードを読む
JBD2_SUPERBLOCK_V1 と JBD2_SUPERBLOCK_V2
jbd2_journal_set_features() -> jbd2_journal_check_used_features() -> journal_get_superblock() で、kernel/fs/jbd2/journal.c:journal_get_superblock()でsuperblockの読み込みが行われる。そこでsuperblockのバージョンチェックがされる。
1522 switch(be32_to_cpu(sb->s_header.h_blocktype)) {
1523 case JBD2_SUPERBLOCK_V1:
1524 journal->j_format_version = 1;
1525 break;
1526 case JBD2_SUPERBLOCK_V2:
1527 journal->j_format_version = 2;
1528 break;
1529 default:
1530 printk(KERN_WARNING "JBD2: unrecognised superblock format ID\n");
1531 goto out;
1532 }
先に書いたとおり、今時のext4ならほぼ間違いなくJBD2_SUPERBLOCK_V2になっているハズ。このj_format_versionは何カ所かで使われるものの、結局は、compat/ro_compat/incompatの各種feature能力そのものを使えるかどうかで用いられる。典型的なのがkernel/include/linux/jbd2.hのこれ。
/* Use the jbd2_{has,set,clear}_feature_* helpers; these will be removed */
#define JBD2_HAS_COMPAT_FEATURE(j,mask) \
((j)->j_format_version >= 2 && \
((j)->j_superblock->s_feature_compat & cpu_to_be32((mask))))
#define JBD2_HAS_RO_COMPAT_FEATURE(j,mask) \
((j)->j_format_version >= 2 && \
((j)->j_superblock->s_feature_ro_compat & cpu_to_be32((mask))))
#define JBD2_HAS_INCOMPAT_FEATURE(j,mask) \
((j)->j_format_version >= 2 && \
((j)->j_superblock->s_feature_incompat & cpu_to_be32((mask))))
metadata_csum
kernel/fs/ext4/super.c:set_journal_csum_feature_set()より、JOURNAL_ASYNC_COMMITまたはJOURNAL_CHECKSUMの場合に、ext4のmetadata_csumが有効になっている時はJBD2_FEATURE_INCOMPAT_CSUM_V3を、無効の時はJBD2_FEATURE_INCOMPAT_CSUM_V1を設定している。
2967 if (ext4_has_metadata_csum(sb)) {
2968 /* journal checksum v3 */
2969 compat = 0;
2970 incompat = JBD2_FEATURE_INCOMPAT_CSUM_V3;
2971 } else {
2972 /* journal checksum v1 */
2973 compat = JBD2_FEATURE_COMPAT_CHECKSUM;
2974 incompat = 0;
2975 }
2976
2977 jbd2_journal_clear_features(sbi->s_journal,
2978 JBD2_FEATURE_COMPAT_CHECKSUM, 0,
2979 JBD2_FEATURE_INCOMPAT_CSUM_V3 |
2980 JBD2_FEATURE_INCOMPAT_CSUM_V2);
2981 if (test_opt(sb, JOURNAL_ASYNC_COMMIT)) {
2982 ret = jbd2_journal_set_features(sbi->s_journal,
2983 compat, 0,
2984 JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT |
2985 incompat);
2986 } else if (test_opt(sb, JOURNAL_CHECKSUM)) {
2987 ret = jbd2_journal_set_features(sbi->s_journal,
2988 compat, 0,
2989 incompat);
2990 jbd2_journal_clear_features(sbi->s_journal, 0, 0,
2991 JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT);
2992 } else {
2993 jbd2_journal_clear_features(sbi->s_journal, 0, 0,
2994 JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT);
2995 }
ちなみに、metadata_csumはEXT4_FEATURE_RO_COMPAT_METADATA_CSUMのこと。つまり、tune2fsなどで事前にext4のsuperblockに書いておく必要があり、またRO_COMPATなのでflagの立ったFilesystemを未対応のkernelでmountしようとしたらreadonlyになる。で現時点ではデフォルトでは無効になっている。とはいえ入ってから結構長いからそろそろデフォルト有効になるのかなぁ。kernelのext4のWikiにページがあるので詳細はそちらを。
# tune2fs -O ^metadata_csum /dev/sda1
mount時にext4_fill_super() -> set_journal_csum_feature_set() -> jbd2_journal_set_features()とやってきて、
kernel/fs/jbd2/journal.c:jbd2_journal_set_features()から呼ぶ**jbd2_journal_check_used_features()**でsuperblockの読み込みが行われる。
1857 if (jbd2_journal_check_used_features(journal, compat, ro, incompat))
1858 return 1;
jbd2_journal_set_features() -> jbd2_journal_check_used_features() -> journal_get_superblock() で、kernel/fs/jbd2/journal.c:journal_get_superblock()で読み込みが行われる。読み込んだ後に各種チェックをして、そしてcrc32cドライバの準備をしている。
1549 if (jbd2_has_feature_csum2(journal) &&
1550 jbd2_has_feature_csum3(journal)) {
1551 /* Can't have checksum v2 and v3 at the same time! */
1552 printk(KERN_ERR "JBD2: Can't enable checksumming v2 and v3 "
1553 "at the same time!\n");
1554 goto out;
1555 }
1556
1557 if (jbd2_journal_has_csum_v2or3_feature(journal) &&
1558 jbd2_has_feature_checksum(journal)) {
1559 /* Can't have checksum v1 and v2 on at the same time! */
1560 printk(KERN_ERR "JBD2: Can't enable checksumming v1 and v2/3 "
1561 "at the same time!\n");
1562 goto out;
1563 }
1564
1565 if (!jbd2_verify_csum_type(journal, sb)) {
1566 printk(KERN_ERR "JBD2: Unknown checksum type\n");
1567 goto out;
1568 }
1569
1570 /* Load the checksum driver */
1571 if (jbd2_journal_has_csum_v2or3_feature(journal)) {
1572 journal->j_chksum_driver = crypto_alloc_shash("crc32c", 0, 0);
1573 if (IS_ERR(journal->j_chksum_driver)) {
1574 printk(KERN_ERR "JBD2: Cannot load crc32c driver.\n");
1575 err = PTR_ERR(journal->j_chksum_driver);
1576 journal->j_chksum_driver = NULL;
1577 goto out;
1578 }
1579 }
1580
1581 /* Check superblock checksum */
1582 if (!jbd2_superblock_csum_verify(journal, sb)) {
1583 printk(KERN_ERR "JBD2: journal checksum error\n");
1584 err = -EFSBADCRC;
1585 goto out;
1586 }
1587
1588 /* Precompute checksum seed for all metadata */
1589 if (jbd2_journal_has_csum_v2or3(journal))
1590 journal->j_csum_seed = jbd2_chksum(journal, ~0, sb->s_uuid,
1591 sizeof(sb->s_uuid));
jbd2_journal_set_features()の中で、jbd2_journal_check_used_features()を呼んだ後にも似たようなコードが続いているが、そっちとの整合性はいまいちわからない。jbd2_journal_set_features()は他の系からもダイレクトに呼ばれて、途中から機能の有効/無効を切り替えることができるという設計だからだろうか。kernel/fs/jbd2/journal.c:jbd2_journal_set_features()より、
1863 /* If enabling v2 checksums, turn on v3 instead */
1864 if (incompat & JBD2_FEATURE_INCOMPAT_CSUM_V2) {
1865 incompat &= ~JBD2_FEATURE_INCOMPAT_CSUM_V2;
1866 incompat |= JBD2_FEATURE_INCOMPAT_CSUM_V3;
1867 }
1868
1869 /* Asking for checksumming v3 and v1? Only give them v3. */
1870 if (incompat & JBD2_FEATURE_INCOMPAT_CSUM_V3 &&
1871 compat & JBD2_FEATURE_COMPAT_CHECKSUM)
1872 compat &= ~JBD2_FEATURE_COMPAT_CHECKSUM;
1873
1874 jbd_debug(1, "Setting new features 0x%lx/0x%lx/0x%lx\n",
1875 compat, ro, incompat);
1876
1877 sb = journal->j_superblock;
1878
1879 /* If enabling v3 checksums, update superblock */
1880 if (INCOMPAT_FEATURE_ON(JBD2_FEATURE_INCOMPAT_CSUM_V3)) {
1881 sb->s_checksum_type = JBD2_CRC32C_CHKSUM;
1882 sb->s_feature_compat &=
1883 ~cpu_to_be32(JBD2_FEATURE_COMPAT_CHECKSUM);
1884
1885 /* Load the checksum driver */
1886 if (journal->j_chksum_driver == NULL) {
1887 journal->j_chksum_driver = crypto_alloc_shash("crc32c",
1888 0, 0);
1889 if (IS_ERR(journal->j_chksum_driver)) {
1890 printk(KERN_ERR "JBD2: Cannot load crc32c "
1891 "driver.\n");
1892 journal->j_chksum_driver = NULL;
1893 return 0;
1894 }
1895
1896 /* Precompute checksum seed for all metadata */
1897 journal->j_csum_seed = jbd2_chksum(journal, ~0,
1898 sb->s_uuid,
1899 sizeof(sb->s_uuid));
1900 }
1901 }
1902
1903 /* If enabling v1 checksums, downgrade superblock */
1904 if (COMPAT_FEATURE_ON(JBD2_FEATURE_COMPAT_CHECKSUM))
1905 sb->s_feature_incompat &=
1906 ~cpu_to_be32(JBD2_FEATURE_INCOMPAT_CSUM_V2 |
1907 JBD2_FEATURE_INCOMPAT_CSUM_V3);
JBD2_FEATURE_INCOMPAT_CSUM_V1
jbd2_has_feature_csum2()とjbd2_has_feature_csum3()を追えばよい。chksum計算方法を変更するために使っている。(ごめん、下のJBD2_FEATURE_INCOMPAT_CSUM_V3と一緒にまとめて追うべきだが、これ以上詳細を追うのをあきらめた..)
JBD2_FEATURE_INCOMPAT_CSUM_V3のcrc32cドライバ
ロードされたcrc32cドライバはkernel/include/linux/jbd2.h:jbd2_chksum()を呼ぶことで使える。ただし**jbd2_journal_has_csum_v2or3()**のときのみ使えるという制約があり、これは呼び出し側で事前にチェックされている模様。
1466 static inline u32 jbd2_chksum(journal_t *journal, u32 crc,
1467 const void *address, unsigned int length)
1468 {
1469 struct {
1470 struct shash_desc shash;
1471 char ctx[JBD_MAX_CHECKSUM_SIZE];
1472 } desc;
1473 int err;
1474
1475 BUG_ON(crypto_shash_descsize(journal->j_chksum_driver) >
1476 JBD_MAX_CHECKSUM_SIZE);
1477
1478 desc.shash.tfm = journal->j_chksum_driver;
1479 desc.shash.flags = 0;
1480 *(u32 *)desc.ctx = crc;
1481
1482 err = crypto_shash_update(&desc.shash, address, length);
1483 BUG_ON(err);
1484
1485 return *(u32 *)desc.ctx;
1486 }
1407 static inline int jbd2_journal_has_csum_v2or3(journal_t *journal)
1408 {
1409 WARN_ON_ONCE(jbd2_journal_has_csum_v2or3_feature(journal) &&
1410 journal->j_chksum_driver == NULL);
1411
1412 return journal->j_chksum_driver != NULL;
1413 }
で、その**jbd2_journal_has_csum_v2or3()は2種類の意味で使われていて、1つめが先ほどのjbd2_chksum()**を呼んでいかどうか、2つめが各種ブロックを可変長にすべきかどうか、となっている。後者は具体的に下記の箇所となる。
kernel/fs/jbd2/commit.c:jbd2_journal_commit_transaction()より、commit時に各種ブロックをディスクに書き出すがそれのサイズ計算をしている。jbd2_journal_has_csum_v2or3()じゃなかったらcsum_sizeはゼロのままとなる。
381 if (jbd2_journal_has_csum_v2or3(journal))
382 csum_size = sizeof(struct jbd2_journal_block_tail);
kernel/fs/jbd2/revoke.c:count_tags()より、revokeブロックをディスクに書き出すときのサイズ計算に使っている。
584 /* Do we need to leave space at the end for a checksum? */
585 if (jbd2_journal_has_csum_v2or3(journal))
586 csum_size = sizeof(struct jbd2_journal_block_tail);
kernel/fs/jbd2/recovery.c:write_one_revoke_record()より、タグブロックのブロックのサイズ計算に使っている。
207 if (jbd2_journal_has_csum_v2or3(journal))
208 size -= sizeof(struct jbd2_journal_block_tail);
kernel/fs/jbd2/recovery.c:do_one_pass()より、journalを読み込むときのタグブロックのサイズ計算に使っている。
518 case JBD2_DESCRIPTOR_BLOCK:
519 /* Verify checksum first */
520 if (jbd2_journal_has_csum_v2or3(journal))
521 descr_csum_size =
522 sizeof(struct jbd2_journal_block_tail);
kernel/fs/jbd2/recovery.c:scan_revoke_records()より、revokeブロックを読むときのサイズ計算に使っている。
831 if (jbd2_journal_has_csum_v2or3(journal))
832 csum_size = sizeof(struct jbd2_journal_block_tail);
journalファイルのロード
kernel/fs/jbd2/recovery.c:do_one_pass()でmount時のjournalファイルのロードが行われている。エラーケースの考慮などが多いため構造がわかりにくいが、簡易に示すとこんな感じになる。while文でswtich,caseを回してブロックの種類を解釈している。
451 /*
452 * Now we walk through the log, transaction by transaction,
453 * making sure that each transaction has a commit block in the
454 * expected place. Each complete transaction gets replayed back
455 * into the main filesystem.
456 */
457
458 while (1) {
(--------------------snip--------------------)
483 err = jread(&bh, journal, next_log_block);
(--------------------snip--------------------)
513 /* OK, we have a valid descriptor block which matches
514 * all of the sequence number checks. What are we going
515 * to do with it? That depends on the pass... */
516
517 switch(blocktype) {
518 case JBD2_DESCRIPTOR_BLOCK:
(--------------------snip--------------------)
656 continue;
657
658 case JBD2_COMMIT_BLOCK:
(--------------------snip--------------------)
758 continue;
759
760 case JBD2_REVOKE_BLOCK:
(--------------------snip--------------------)
773 continue;
774
775 default:
776 jbd_debug(3, "Unrecognised magic %d, end of scan.\n",
777 blocktype);
778 brelse(bh);
779 goto done;
780 }
781 }
kernelは実コードなのでエラー処理含めてややこしいが、journalダンプツールであるe2fsprogsパッケージにに含まれるdebugfsプログラムのlogdumpコマンドが純粋にprintするだけのツールなので、これのソースコードだとわかりやすい。e2fsprogs/debugfs/logdump.c:void dump_journal()がそれ。ダンプツールなので左再帰下降な感じで読み進められる。
while (1) {
retval = read_journal_block(cmdname, source,
((ext2_loff_t) blocknr) * blocksize,
buf, blocksize);
(--------------------snip--------------------)
switch (blocktype) {
case JFS_DESCRIPTOR_BLOCK:
dump_descriptor_block(out_file, source, buf, jsb,
&blocknr, blocksize,
transaction);
continue;
case JFS_COMMIT_BLOCK:
transaction++;
blocknr++;
WRAP(jsb, blocknr);
continue;
case JFS_REVOKE_BLOCK:
dump_revoke_block(out_file, buf, jsb,
blocknr, blocksize,
transaction);
blocknr++;
WRAP(jsb, blocknr);
continue;
default:
fprintf (out_file, "Unexpected block type %u at "
"block %u.\n", blocktype, blocknr);
return;
}
ちなみに、以前の記事にも書いたけど、debugfsのlogdumpはこんな感じで使える。
root@tina:/home/rarul# debugfs /dev/sda1
debugfs 1.42.13 (17-May-2015)
debugfs: logdump -a
Journal starts at block 1, transaction 22623
Found expected sequence 22623, type 1 (descriptor block) at block 1
Dumping descriptor block, sequence 22623, at block 1:
FS block 0 logged at journal block 2 (flags 0x0)
FS block 524322 logged at journal block 3 (flags 0x2)
FS block 1048592 logged at journal block 4 (flags 0x2)
FS block 1 logged at journal block 5 (flags 0x2)
FS block 1048936 logged at journal block 6 (flags 0x2)
FS block 1048576 logged at journal block 7 (flags 0x2)
FS block 1058038 logged at journal block 8 (flags 0x2)
FS block 524332 logged at journal block 9 (flags 0x2)
FS block 532492 logged at journal block 10 (flags 0x2)
FS block 1058050 logged at journal block 11 (flags 0x2)
FS block 524881 logged at journal block 12 (flags 0x2)
FS block 524323 logged at journal block 13 (flags 0x2)
FS block 524819 logged at journal block 14 (flags 0x2)
FS block 525549 logged at journal block 15 (flags 0x2)
FS block 3145787 logged at journal block 16 (flags 0x2)
FS block 3145731 logged at journal block 17 (flags 0x2)
FS block 3145732 logged at journal block 18 (flags 0xa)
Found expected sequence 22623, type 2 (commit block) at block 19
Found expected sequence 22624, type 1 (descriptor block) at block 20
Dumping descriptor block, sequence 22624, at block 20:
FS block 524323 logged at journal block 21 (flags 0x0)
FS block 524819 logged at journal block 22 (flags 0x2)
(--------------------snip--------------------)
crc32cドライバ
余談になるけど、crc32cドライバはkernel/crypto/以下で実装されている機能で、crc32c等を計算する部分を抽象化している。一部CPUにはアクセラレータが乗っていてそれを使うことで高速にcrc32cやcrc32を計算でき、そうでないものはソフト的に計算する、という場合分けを行っている。x86_64に関わるものだけでも...書き出そうと思ったけどあまりにたくさんありすぎたため下記はあくまで一部です...
- CONFIG_CRYPTO_CRC32C_INTEL
- CONFIG_CRYPTO_CRC32_PCLMUL
- CONFIG_CRYPTO_POLY1305_X86_64
- CONFIG_CRYPTO_CRCT10DIF_PCLMUL
- CONFIG_CRYPTO_SHA1_SSSE3
- CONFIG_CRYPTO_SHA1_SSSE3
- CONFIG_CRYPTO_SHA512_SSSE3
- CONFIG_CRYPTO_SHA1_MB
- CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL
- CONFIG_CRYPTO_AES_586
- CONFIG_CRYPTO_AES_X86_64
- CONFIG_CRYPTO_AES_NI_INTEL
- CONFIG_CRYPTO_BLOWFISH_X86_64
- CONFIG_CRYPTO_CAMELLIA_X86_64
- CONFIG_CRYPTO_CAMELLIA_AESNI_AVX_X86_64
- CONFIG_CRYPTO_CAMELLIA_AESNI_AVX2_X86_64
終わりに
ext4のjbd2は得体の知れぬものというイメージが強いけど、ディスクレイアウトとブロックの種類から追えば、それほど複雑なものではないことがわかる。やはりext4とつるんだ時のタイミングとRAM(VFS)との兼ね合いが、Filesystemを理解する上でのネックなんじゃないかと思う。
現時点では私は電源断とCorruptionを一番の関心事項として調べているので、次回の記事はjournal_async_commitとnobarrierあたりになるのかなぁ。