LoginSignup
7
11

More than 5 years have passed since last update.

ext4のjbd2のデータ構造

Last updated at Posted at 2017-02-19

はじめに

Linuxのext4のjournalとして使われているjbd2モジュールが書き出すjournalファイルのデータ構造を調べることで、ext4のjbd2の設計を理解しようという趣旨の雑記です。

なお、Linux-4.6くらいを見ていて、内部ジャーナル(同じFilesystem内のs_journal_inumで指定されたinode番号)の場合だけを考えています。

ディスクレイアウト

kernelのext4のWikiを見るなり、直接ソースコードを見る(include/linux/jbd2.h)なりするのがよい。というか、ろくに内部構造を説明した資料が英語も含めてないので、これしか手段がない。

ブロックの種類

journalはブロックで構成される。各ブロックは下記のいずれかとなる

jbd2.h
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となる。e2fsprogsmke2fs.cより、

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)で決まる。

getsectsize.c
/*
 * 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()より、

ioctl.c
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.cblk_queue_logical_block_size(), blk_queue_physical_block_size()で設定した値に行き着く。これは、SCSIコマンドのread_capacity_16(SERVICE_ACTION_IN_16==0x9e, SAI_READ_CAPACITY_16=0x10)で取得した値。具体的に、下記サンプルプログラムを手元のSSDで実行して見るとこうなった。

blockdev.c
#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のファイルサイズ

オプションで指定されたファイルサイズを尊重しつつ、指定がない場合はe2fsprogsmkjournal.c:ext2fs_default_journal_size()で決まる。

mkjournal.c
/*
 * 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のバージョンチェックがされる。

journal.c
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のこれ。

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を設定している。

super.c
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_csumEXT4_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の読み込みが行われる。

journal.c
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ドライバの準備をしている。

super.c
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()より、

journal.c
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()のときのみ使えるという制約があり、これは呼び出し側で事前にチェックされている模様。

jbc2.h
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 }
jbd2.h
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はゼロのままとなる。

commit.c
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ブロックをディスクに書き出すときのサイズ計算に使っている。

revoke.c
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()より、タグブロックのブロックのサイズ計算に使っている。

recovery.c
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を読み込むときのタグブロックのサイズ計算に使っている。

recovery.c
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ブロックを読むときのサイズ計算に使っている。

recovery.c
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を回してブロックの種類を解釈している。

recovery.c
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()がそれ。ダンプツールなので左再帰下降な感じで読み進められる。

logdump.c
    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_commitnobarrierあたりになるのかなぁ。

7
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
11