19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ext4のjournalモードの確認

Last updated at Posted at 2017-02-11

はじめに

ext4についての資料はググればまだ探せばある方だけど、ことjournalについては、mountオプションと概要くらいしかなくて、つっこんだことまで説明した資料が皆無な状態。というわけで、詳細を追うことにした。無茶なことしやがって(AA略

なお、Linux-4.6くらいをみてます。

Documentation

概要やらmountオプションやらについては、下記のぐたぐたした文章を読むよりもkernel/Documentation/filesystems/ext4.txtのドキュメントを参照する方がよい。

jdb2

ext4(またはext3)のjournalは正確にはext4の一部ではなくて**jbd2(Journaling block device 2)**というモジュールとして汎用化されている。ただ実体は、ext4はまだ他のjournalに載せ替えれそうな設計になっているが、jdb2はもうext4専用といわざるをえないほど密結合しているように読めた。

ext4としては多くの場合journalはinode(つまり普通のファイル)としてストレージに保存する。kernel/fs/ext4.hより、

ext4.h
209 /*
210  * Special inodes numbers
211  */
212 #define EXT4_BAD_INO             1      /* Bad blocks inode */
213 #define EXT4_ROOT_INO            2      /* Root inode */
214 #define EXT4_USR_QUOTA_INO       3      /* User quota inode */
215 #define EXT4_GRP_QUOTA_INO       4      /* Group quota inode */
216 #define EXT4_BOOT_LOADER_INO     5      /* Boot loader inode */
217 #define EXT4_UNDEL_DIR_INO       6      /* Undelete directory inode */
218 #define EXT4_RESIZE_INO          7      /* Reserved group descriptors inode */
219 #define EXT4_JOURNAL_INO         8      /* Journal inode */

となっていて、通常はinode番号8が使われる。が、コードとしてはsuerblockにあるs_journal_inumのものが使われるので、古いe2fsprogsを使った、過去から引きついだなどの特殊な場合はこの限りでない模様。で、このinode番号のものはmount/umount時に特殊に扱われ、journalの読み書きに使われる。

journalファイルは通常e2fsprogsの**mke2fs(mkfs.ext4)**で作られる。lib/ext2fs/mkjournal.c:ext2fs_add_journal_inode2()より、

mkjournal.c
	} else {
		if ((mount_flags & EXT2_MF_BUSY) &&
		    !(fs->flags & EXT2_FLAG_EXCLUSIVE)) {
			retval = EBUSY;
			goto errout;
		}
		journal_ino = EXT2_JOURNAL_INO;
		if ((retval = write_journal_inode(fs, journal_ino,
						  num_blocks, goal, flags)))
			return retval;
	}

mountされている状態から作ろうとすると、EXT2_IOC_SETFLAGSを使って**UF_NODUMP|UF_IMMUTABLE(immutableかつno dump)**フラグを立てるが、mountされていなかったら上記の通り特になにもしていない。lookupできない(名前からたどれない)inodeだから特にIMMUTALBEにしなくてもよいということなんだろうか。

# debugfs /dev/sa1
debugfs 1.42.13 (17-May-2015)
debugfs: stat <8>
Inode: 8   Type: regular    Mode:  0600   Flags: 0x80000
Generation: 0    Version: 0x00000000:00000000
User:     0   Group:     0   Size: 134217728
File ACL: 0    Directory ACL: 0
Links: 1   Blockcount: 262144
Fragment:  Address: 0    Number: 0    Size: 0
 ctime: 0x5878dec7:00000000 -- Fri Jan 13 23:05:59 2017
 atime: 0x5878dec7:00000000 -- Fri Jan 13 23:05:59 2017
 mtime: 0x5878dec7:00000000 -- Fri Jan 13 23:05:59 2017
crtime: 0x5878dec7:00000000 -- Fri Jan 13 23:05:59 2017
Size of extra inode fields: 28
EXTENTS:
(0-32766):2655233-2687999, (32767):2688000

手元でinode番号8を確認すると、flags: 0x80000 なので、EXT4_EXTENTS_FLのみのようだった。

ちなみに、外部journalもサポートしていて、mkfs.ext4 -J device=/dev/sda1で作る。ただ、ページにも書いてあるとおり、外部ブロックデバイス名がいつも同じになるとは限らなかったりするので、オペレーションに手間を掛けたくないなら使わない方がよい。またjournal_async_commitでないとダメと書かれていてさらに不安になる。どうダメなのか裏付けしたかったけど、kernelソース的には特にそれっぽいのは見あたらなかった。

journalモード

3種類のモードがある。default mount option(HOGE_DEFM_HOGEのやつ)、もしくはmountオプションに指定する。指定がない場合data=orderedになる。

  • data=writeback、metadata(ファイルサイズなど)とデータの中身の書き出し順が保障されない。セキュリティ面でもよくないとドキュメントに書いてあり、よほどパフォーマンスを気にする場面以外ではもう使わないんじゃないかと思う。 そのパフォーマンスも、手元でテストしてみた限りdata=orderedと有意な差がなかった。 data=writebackだと差がわかる程度には少し早かった。ただdata=journalはさすがに遅かった。
  • data=ordered、metaデータとデータの中身の書き出し順が保障される。標準のモード。
  • data=journal、データの中身も一度journalに書き出してから実際の書き込みが行われる。もうjournalというよりtransactionといった方がいいかもしれない。

ただ、ext4が増築で機能が増えてきた経緯もあってか、journalモードによって使える機能に制限がかかることが多い。つまみ食い程度に下記に具体例を。

kernel/fs/ext4/super.c:ext4_fill_super()より、data=journalだとDELALLOCは無効になる。さかのぼる限り、dd919b982、つまりDEALLOC当初からの模様。実装が難しくなる(他のjournalモードとは別のpathになる)からじゃないかと思われる。

super.c
3335         if (test_opt(sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA) {
3336                 printk_once(KERN_WARNING "EXT4-fs: Warning: mounting "
3337                             "with data=journal disables delayed "
3338                             "allocation and O_DIRECT support!\n");
3339                 if (test_opt2(sb, EXPLICIT_DELALLOC)) {
3340                         ext4_msg(sb, KERN_ERR, "can't mount with "
3341                                  "both data=journal and delalloc");
3342                         goto failed_mount;
3343                 }
3344                 if (test_opt(sb, DIOREAD_NOLOCK)) {
3345                         ext4_msg(sb, KERN_ERR, "can't mount with "
3346                                  "both data=journal and dioread_nolock");
3347                         goto failed_mount;
3348                 }
3349                 if (test_opt(sb, DAX)) {
3350                         ext4_msg(sb, KERN_ERR, "can't mount with "
3351                                  "both data=journal and dax");
3352                         goto failed_mount;
3353                 }
3354                 if (test_opt(sb, DELALLOC))
3355                         clear_opt(sb, DELALLOC);
3356         } else {
3357                 sb->s_iflags |= SB_I_CGROUPWB;
3358         }

kernel/fs/ext4/super.c:parse_options()より、data=orderedだとjournal_async_commitは使えないd4f761074で入った。

super.c
1794         if (test_opt(sb, DATA_FLAGS) == EXT4_MOUNT_ORDERED_DATA &&
1795             test_opt(sb, JOURNAL_ASYNC_COMMIT)) {
1796                 ext4_msg(sb, KERN_ERR, "can't mount with journal_async_commit "
1797                          "in data=ordered mode");
1798                 return 0;
1799         }

kernel/fs/ext4/super.c:ext4_mount_opts[]より、journal_async_commitだとjournal_checksumも立つ。

super.c
1420         {Opt_journal_async_commit, (EXT4_MOUNT_JOURNAL_ASYNC_COMMIT |
1421                                     EXT4_MOUNT_JOURNAL_CHECKSUM),

が、kernel/fs/ext4/super.c:set_journal_csum_feature_set()によると、JOURNAL_ASYNC_COMMITが立っているときはJOURNAL_CHECKSUMは意味をなしていないように見える。他にJOURNAL_CHECKSUMを参照している箇所もなく、jbd2の中でasync_commitが立っているとchecksumも動く、とかいう風にも見えず、何のためなのかわからなかった。

super.c
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         }

kernel/fs/ext4/super.c:ext4_fill_super()より、journalなしだとjournal_async_commitは使えない。・・・はさすがに自明か。

super.c
3736                 if (test_opt(sb, JOURNAL_ASYNC_COMMIT)) {
3737                         ext4_msg(sb, KERN_ERR, "can't mount with "
3738                                  "journal_async_commit, fs mounted w/o journal");
3739                         goto failed_mount_wq;
3740                 }

コードを読む

オプションの解釈

kernel/fs/ext4/super.cのtokensとext4_mount_optsより、

super.c
1233         {Opt_data_journal, "data=journal"},
1234         {Opt_data_ordered, "data=ordered"},
1235         {Opt_data_writeback, "data=writeback"},
super.c
1448         {Opt_data_journal, EXT4_MOUNT_JOURNAL_DATA, MOPT_NO_EXT2 | MOPT_DATAJ},
1449         {Opt_data_ordered, EXT4_MOUNT_ORDERED_DATA, MOPT_NO_EXT2 | MOPT_DATAJ},
1450         {Opt_data_writeback, EXT4_MOUNT_WRITEBACK_DATA,
1451          MOPT_NO_EXT2 | MOPT_DATAJ},

data=writebackの場合はEXT4_MOUNT_WRITEBACK_DATAが、data=orderedの場合はEXT4_MOUNT_ORDERED_DATAが、data=journalの場合はEXT4_MOUNT_JOURNAL_DATAが、それぞれ立つ。

journalの読み込みもまたkernel/fs/ext4/super.c:ext4_fill_super()で行われる。

super.c
3717         /*
3718          * The first inode we look at is the journal inode.  Don't try
3719          * root first: it may be modified in the journal!
3720          */
3721         if (!test_opt(sb, NOLOAD) && ext4_has_feature_journal(sb)) {
3722                 if (ext4_load_journal(sb, es, journal_devnum))
3723                         goto failed_mount3a;
3724         } else if (test_opt(sb, NOLOAD) && !(sb->s_flags & MS_RDONLY) &&
3725                    ext4_has_feature_journal_needs_recovery(sb)) {
3726                 ext4_msg(sb, KERN_ERR, "required journal recovery "
3727                        "suppressed and not mounted read-only");
3728                 goto failed_mount_wq;
3729         } else {
3730                 /* Nojournal mode, all journal mount options are illegal */
3731                 if (test_opt2(sb, EXPLICIT_JOURNAL_CHECKSUM)) {
3732                         ext4_msg(sb, KERN_ERR, "can't mount with "
3733                                  "journal_checksum, fs mounted w/o journal");
3734                         goto failed_mount_wq;
3735                 }
3736                 if (test_opt(sb, JOURNAL_ASYNC_COMMIT)) {
3737                         ext4_msg(sb, KERN_ERR, "can't mount with "
3738                                  "journal_async_commit, fs mounted w/o journal");
3739                         goto failed_mount_wq;
3740                 }
3741                 if (sbi->s_commit_interval != JBD2_DEFAULT_MAX_COMMIT_AGE*HZ) {
3742                         ext4_msg(sb, KERN_ERR, "can't mount with "
3743                                  "commit=%lu, fs mounted w/o journal",
3744                                  sbi->s_commit_interval / HZ);
3745                         goto failed_mount_wq;
3746                 }
3747                 if (EXT4_MOUNT_DATA_FLAGS &
3748                     (sbi->s_mount_opt ^ sbi->s_def_mount_opt)) {
3749                         ext4_msg(sb, KERN_ERR, "can't mount with "
3750                                  "data=, fs mounted w/o journal");
3751                         goto failed_mount_wq;
3752                 }
3753                 sbi->s_def_mount_opt &= EXT4_MOUNT_JOURNAL_CHECKSUM;
3754                 clear_opt(sb, JOURNAL_CHECKSUM);
3755                 clear_opt(sb, DATA_FLAGS);
3756                 sbi->s_journal = NULL;
3757                 needs_recovery = 0;
3758                 goto no_journal;
3759         }
  • noloadじゃない、かつ、superブロックにjournalオプションがある場合、journalを読みにいってダメならmountエラー
  • noload、かつ、readonlyじゃない、かつ、superブロックにrecoveryオプションがあるなら、mountエラー
  • 上記じゃない場合、journalなしモード(sbi->s_journalがNULL)となって各種チェックを行う。

という感じになっている。

EXT4_MOUNT_WRITEBACK_DATA, EXT4_MOUNT_ORDERED_DATA, EXT4_MOUNT_JOURNAL_DATAは、実質的にkernel/fs/ext4/ext4_jbd2.h:ext4_inode_journal_mode()でのみ使われている

ext4_jbd2.h
392 static inline int ext4_inode_journal_mode(struct inode *inode)
393 {
394         if (EXT4_JOURNAL(inode) == NULL)
395                 return EXT4_INODE_WRITEBACK_DATA_MODE;  /* writeback */
396         /* We do not support data journalling with delayed allocation */
397         if (!S_ISREG(inode->i_mode) ||
398             test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA)
399                 return EXT4_INODE_JOURNAL_DATA_MODE;    /* journal data */
400         if (ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA) &&
401             !test_opt(inode->i_sb, DELALLOC))
402                 return EXT4_INODE_JOURNAL_DATA_MODE;    /* journal data */
403         if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_ORDERED_DATA)
404                 return EXT4_INODE_ORDERED_DATA_MODE;    /* ordered */
405         if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_WRITEBACK_DATA)
406                 return EXT4_INODE_WRITEBACK_DATA_MODE;  /* writeback */
407         else
408                 BUG();
409 }
410 
411 static inline int ext4_should_journal_data(struct inode *inode)
412 {
413         return ext4_inode_journal_mode(inode) & EXT4_INODE_JOURNAL_DATA_MODE;
414 }
415 
416 static inline int ext4_should_order_data(struct inode *inode)
417 {
418         return ext4_inode_journal_mode(inode) & EXT4_INODE_ORDERED_DATA_MODE;
419 }
420 
421 static inline int ext4_should_writeback_data(struct inode *inode)
422 {
423         return ext4_inode_journal_mode(inode) & EXT4_INODE_WRITEBACK_DATA_MODE;
424 }

inodeごとに判断されるものの、結局、ext4_should_journal_data(), ext4_should_order_data(), **ext4_should_writeback_data()**を確認すればよいとなる。...厳密に言うと、kernel/fs/ext4/inode.c:ext4_set_aops()もあるけど、こっちはどんどん脱線方向になりそうで、今回は省略する...

inode.c
3533 void ext4_set_aops(struct inode *inode)
3534 {
3535         switch (ext4_inode_journal_mode(inode)) {
3536         case EXT4_INODE_ORDERED_DATA_MODE:
3537                 ext4_set_inode_state(inode, EXT4_STATE_ORDERED_MODE);
3538                 break;
3539         case EXT4_INODE_WRITEBACK_DATA_MODE:
3540                 ext4_clear_inode_state(inode, EXT4_STATE_ORDERED_MODE);
3541                 break;
3542         case EXT4_INODE_JOURNAL_DATA_MODE:
3543                 inode->i_mapping->a_ops = &ext4_journalled_aops;
3544                 return;
3545         default:
3546                 BUG();
3547         }
3548         if (test_opt(inode->i_sb, DELALLOC))
3549                 inode->i_mapping->a_ops = &ext4_da_aops;
3550         else
3551                 inode->i_mapping->a_ops = &ext4_aops;
3552 }

journalモードによる違い

ext4_should_writeback_data()

kernel/fs/ext4/mballoc.c:ext4_free_blocks()で使われている。

mballoc.c
4820         /*
4821          * We need to make sure we don't reuse the freed block until after the
4822          * transaction is committed. We make an exception if the inode is to be
4823          * written in writeback mode since writeback mode has weak data
4824          * consistency guarantees.
4825          */
4826         if (ext4_handle_valid(handle) &&
4827             ((flags & EXT4_FREE_BLOCKS_METADATA) ||
4828              !ext4_should_writeback_data(inode))) {
4829                 struct ext4_free_data *new_entry;
4830                 /*
4831                  * We use __GFP_NOFAIL because ext4_free_blocks() is not allowed
4832                  * to fail.
4833                  */
4834                 new_entry = kmem_cache_alloc(ext4_free_data_cachep,
4835                                 GFP_NOFS|__GFP_NOFAIL);
4836                 new_entry->efd_start_cluster = bit;
4837                 new_entry->efd_group = block_group;
4838                 new_entry->efd_count = count_clusters;
4839                 new_entry->efd_tid = handle->h_transaction->t_tid;
4840 
4841                 ext4_lock_group(sb, block_group);
4842                 mb_clear_bits(bitmap_bh->b_data, bit, count_clusters);
4843                 ext4_mb_free_metadata(handle, &e4b, new_entry);
4844         } else {
4845                 /* need to update group_info->bb_free and bitmap
4846                  * with group lock held. generate_buddy look at
4847                  * them with group lock_held
4848                  */
4849                 if (test_opt(sb, DISCARD)) {
4850                         err = ext4_issue_discard(sb, block_group, bit, count);
4851                         if (err && err != -EOPNOTSUPP)
4852                                 ext4_msg(sb, KERN_WARNING, "discard request in"
4853                                          " group:%d block:%d count:%lu failed"
4854                                          " with %d", block_group, bit, count,
4855                                          err);
4856                 } else
4857                         EXT4_MB_GRP_CLEAR_TRIMMED(e4b.bd_info);
4858 
4859                 ext4_lock_group(sb, block_group);
4860                 mb_clear_bits(bitmap_bh->b_data, bit, count_clusters);
4861                 mb_free_blocks(inode, &e4b, bit, count_clusters);
4862         }

free(ファイルが消える・小さくなる)時に、metadata(ファイルサイズ)の変更とデータのbitmap変更とのどちらを先にするか、のようだ。

ext4_should_order_data()

2箇所ある。

1つめはkernel/fs/ext4/inode.c:ext4_evict_inode()、ファイルを消すときにファイルサイズを先にゼロにしている。

inode.c
226         if (ext4_should_order_data(inode))
227                 ext4_begin_ordered_truncate(inode, 0);

2つめは、kernel/fs/ext4/inode.c:ext4_setattr()、ファイルサイズを小さくするときに**ext4_begin_ordered_truncate()**を呼ぶ。

inode.c
5006                 if (ext4_should_order_data(inode) &&
5007                     (attr->ia_size < inode->i_size)) {
5008                         error = ext4_begin_ordered_truncate(inode,
5009                                                             attr->ia_size);
5010                         if (error)
5011                                 goto err_out;
5012                 }

ext4_begin_ordered_truncate()は、kernel/fs/jbd2/transaction.c:jbd2_journal_begin_ordered_truncate()を呼ぶ。結局のところ、journalがまだcommitされていなかったらfilemap_fdatawrite_range()を呼ぶ。これはWB_SYNC_ALLで**__filemap_fdatawrite_range()**を呼ぶ、つまり書き出し待ちをする。

transaction.c
2565         if (inode_trans == commit_trans) {
2566                 ret = filemap_fdatawrite_range(jinode->i_vfs_inode->i_mapping,
2567                         new_size, LLONG_MAX);
2568                 if (ret)
2569                         jbd2_journal_abort(journal, ret);
2570         }

ext4_should_journal_data()

たくさんあって長々となってしまっているので、気長に読んで...

kernel/fs/ext4/super.c:ext4_quota_on()では、quota処理を開始する前に明示的にflushしている。コメントから察するに、たぶんflushせずにquota処理しようとすると複雑になるんだろう。

super.c
5069         /*
5070          * When we journal data on quota file, we have to flush journal to see
5071          * all updates to the file when we bypass pagecache...
5072          */
5073         if (EXT4_SB(sb)->s_journal &&
5074             ext4_should_journal_data(d_inode(path->dentry))) {
5075                 /*
5076                  * We don't need to lock updates but journal_flush() could
5077                  * otherwise be livelocked...
5078                  */
5079                 jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal);
5080                 err = jbd2_journal_flush(EXT4_SB(sb)->s_journal);
5081                 jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal);
5082                 if (err)
5083                         return err;
5084         }

kernel/fs/ext4/inline.c:ext4_convert_inline_data_to_extent()は、すべてのページごとに**do_journal_get_write_access()関数を呼ぶ。kernel/fs/ext4/inode.c:do_journal_get_write_access()は、いまいち理解しきれないけど、dirtyならext4_handle_dirty_metadata()**を呼ぶことで書き込むタイミングを確定させているっぽい?

inline.c
590         if (!ret && ext4_should_journal_data(inode)) {
591                 ret = ext4_walk_page_buffers(handle, page_buffers(page),
592                                              from, to, NULL,
593                                              do_journal_get_write_access);
594         }
inode
1005 /*
1006  * To preserve ordering, it is essential that the hole instantiation and
1007  * the data write be encapsulated in a single transaction.  We cannot
1008  * close off a transaction and start a new one between the ext4_get_block()
1009  * and the commit_write().  So doing the jbd2_journal_start at the start of
1010  * prepare_write() is the right place.
1011  *
1012  * Also, this function can nest inside ext4_writepage().  In that case, we
1013  * *know* that ext4_writepage() has generated enough buffer credits to do the
1014  * whole page.  So we won't block on the journal in that case, which is good,
1015  * because the caller may be PF_MEMALLOC.
1016  *
1017  * By accident, ext4 can be reentered when a transaction is open via
1018  * quota file writes.  If we were to commit the transaction while thus
1019  * reentered, there can be a deadlock - we would be holding a quota
1020  * lock, and the commit would never complete if another thread had a
1021  * transaction open and was blocking on the quota lock - a ranking
1022  * violation.
1023  *
1024  * So what we do is to rely on the fact that jbd2_journal_stop/journal_start
1025  * will _not_ run commit under these circumstances because handle->h_ref
1026  * is elevated.  We'll still have enough credits for the tiny quotafile
1027  * write.
1028  */
1029 int do_journal_get_write_access(handle_t *handle,
1030                                 struct buffer_head *bh)
1031 {
1032         int dirty = buffer_dirty(bh);
1033         int ret;
1034 
1035         if (!buffer_mapped(bh) || buffer_freed(bh))
1036                 return 0;
1037         /*
1038          * __block_write_begin() could have dirtied some buffers. Clean
1039          * the dirty bit as jbd2_journal_get_write_access() could complain
1040          * otherwise about fs integrity issues. Setting of the dirty bit
1041          * by __block_write_begin() isn't a real problem here as we clear
1042          * the bit before releasing a page lock and thus writeback cannot
1043          * ever write the buffer.
1044          */
1045         if (dirty)
1046                 clear_buffer_dirty(bh);
1047         BUFFER_TRACE(bh, "get write access");
1048         ret = ext4_journal_get_write_access(handle, bh);
1049         if (!ret && dirty)
1050                 ret = ext4_handle_dirty_metadata(handle, NULL, bh);
1051         return ret;
1052 }

kernel/fs/ext4/indirect.c:ext4_clear_blocks()は、EXT4_FREE_BLOCKS_FORGETを立てることで、**ext4_free_blocks()の中からext4_forget()**を呼ぶようにしている。**ext4_forget()**は、data=journalの場合はjbd2のforgetを呼ぶ、そうでない場合はjbd2のrevokeを呼ぶ。

indirect.c
959         if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
960                 flags |= EXT4_FREE_BLOCKS_FORGET | EXT4_FREE_BLOCKS_METADATA;
961         else if (ext4_should_journal_data(inode))
962                 flags |= EXT4_FREE_BLOCKS_FORGET;

kernel/fs/ext4/fsync.c:ext4_sync_file()は、fsync(2)は本来ならデータを書き込むところまで待つべきだが、data=journalモードだとデータはjournalに先に書くので、journalだけ書き出せばよいことになる、からそうしているようだ。

fsync.c
118         /*
119          * data=writeback,ordered:
120          *  The caller's filemap_fdatawrite()/wait will sync the data.
121          *  Metadata is in the journal, we wait for proper transaction to
122          *  commit here.
123          *
124          * data=journal:
125          *  filemap_fdatawrite won't do anything (the buffers are clean).
126          *  ext4_force_commit will write the file data into the journal and
127          *  will wait on that.
128          *  filemap_fdatawait() will encounter a ton of newly-dirtied pages
129          *  (they were dirtied by commit).  But that's OK - the blocks are
130          *  safe in-journal, which is all fsync() needs to ensure.
131          */
132         if (ext4_should_journal_data(inode)) {
133                 ret = ext4_force_commit(inode->i_sb);
134                 goto out;
135         }

kernel/fs/ext4/move_extent.c:ext4_move_extents()は、data=journalだとonline defragができないみたい。ただ現時点(2017/02/08)でもまだTODO:のままだった。ぐぬぬ...ちなみにonline defragていうのはe2fsprogsに含まれるe4defrag(8)というツールのことみたい。ioctlのEXT4_IOC_MOVE_EXTでできるみたい。

move_extent.c
592         /* TODO: it's not obvious how to swap blocks for inodes with full
593            journaling enabled */
594         if (ext4_should_journal_data(orig_inode) ||
595             ext4_should_journal_data(donor_inode)) {
596                 ext4_msg(orig_inode->i_sb, KERN_ERR,
597                          "Online defrag not supported with data journaling");
598                 return -EOPNOTSUPP;
599         }

kernel/fs/ext4/inode.c:ext4_evict_inode()は、
消そうとしているファイルはVFS的にcleanと判断されるけどjournal的には残っているので、それをケアしないといけないみたい?

inode.c
189         if (inode->i_nlink) {
190                 /*
191                  * When journalling data dirty buffers are tracked only in the
192                  * journal. So although mm thinks everything is clean and
193                  * ready for reaping the inode might still have some pages to
194                  * write in the running transaction or waiting to be
195                  * checkpointed. Thus calling jbd2_journal_invalidatepage()
196                  * (via truncate_inode_pages()) to discard these buffers can
197                  * cause data loss. Also even if we did not discard these
198                  * buffers, we would have no way to find them after the inode
199                  * is reaped and thus user could see stale data if he tries to
200                  * read them before the transaction is checkpointed. So be
201                  * careful and force everything to disk here... We use
202                  * ei->i_datasync_tid to store the newest transaction
203                  * containing inode's data.
204                  *
205                  * Note that directories do not have this problem because they
206                  * don't use page cache.
207                  */
208                 if (ext4_should_journal_data(inode) &&
209                     (S_ISLNK(inode->i_mode) || S_ISREG(inode->i_mode)) &&
210                     inode->i_ino != EXT4_JOURNAL_INO) {
211                         journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
212                         tid_t commit_tid = EXT4_I(inode)->i_datasync_tid;
213 
214                         jbd2_complete_transaction(journal, commit_tid);
215                         filemap_write_and_wait(&inode->i_data);
216                 }
217                 truncate_inode_pages_final(&inode->i_data);
218 
219                 goto no_delete;
220         }

kernel/fs/ext4/inode.c:ext4_write_begin()は、先の**ext4_convert_inline_data_to_extent()**と同じ。

inode
1218         if (!ret && ext4_should_journal_data(inode)) {
1219                 ret = ext4_walk_page_buffers(handle, page_buffers(page),
1220                                              from, to, NULL,
1221                                              do_journal_get_write_access);
1222         }

kernel/fs/ext4/inode.c:ext4_writepage()は...よくわからない。43ce1d23bあたりで入ったように見えるけど、これ以前の内容も多く、history追跡してもいまいち意図がわからず。mmapされている場合に何かしたいみたい。

inode.c
2049         if (PageChecked(page) && ext4_should_journal_data(inode))
2050                 /*
2051                  * It's mmapped pagecache.  Add buffers and journal it.  There
2052                  * doesn't seem much point in redirtying the page here.
2053                  */
2054                 return __ext4_journalled_writepage(page, len);

kernel/fs/ext4/inode.c:ext4_writepages()は、遅延アロケーションじゃないという決め打ちで簡易コードに分岐させているみたい。

inode.c
2619         if (ext4_should_journal_data(inode)) {
2620                 struct blk_plug plug;
2621 
2622                 blk_start_plug(&plug);
2623                 ret = write_cache_pages(mapping, wbc, __writepage, mapping);
2624                 blk_finish_plug(&plug);
2625                 goto out_writepages;
2626         }

kernel/fs/ext4/inode.c:ext4_direct_IO()は、data=journalだとO_DIRECTモードは使えない。

inode.c
3447         /*
3448          * If we are doing data journalling we don't support O_DIRECT
3449          */
3450         if (ext4_should_journal_data(inode))
3451                 return 0;

kernel/fs/ext4/inode.c:__ext4_block_zero_page_range()は、先のext4_convert_inline_data_to_extent()と同じ。

inode.c
3619         if (ext4_should_journal_data(inode)) {
3620                 BUFFER_TRACE(bh, "get write access");
3621                 err = ext4_journal_get_write_access(handle, bh);
3622                 if (err)
3623                         goto unlock;
3624         }
3625         zero_user(page, offset, length);
3626         BUFFER_TRACE(bh, "zeroed end of block");
3627 
3628         if (ext4_should_journal_data(inode)) {
3629                 err = ext4_handle_dirty_metadata(handle, inode, bh);
3630         } else {
3631                 err = 0;
3632                 mark_buffer_dirty(bh);
3633                 if (ext4_test_inode_state(inode, EXT4_STATE_ORDERED_MODE))
3634                         err = ext4_jbd2_file_inode(handle, inode);
3635         }

kernel/fs/ext4/inode.c:ext4_setattr()は、inodeが消されかけの時にIO待ちをしている。先にあったのと同じく、data=journalだとデータだけじゃなくjournalも書き出さないといけない。

inode.c
5054                 /*
5055                  * Blocks are going to be removed from the inode. Wait
5056                  * for dio in flight.  Temporarily disable
5057                  * dioread_nolock to prevent livelock.
5058                  */
5059                 if (orphan) {
5060                         if (!ext4_should_journal_data(inode)) {
5061                                 ext4_inode_block_unlocked_dio(inode);
5062                                 inode_dio_wait(inode);
5063                                 ext4_inode_resume_unlocked_dio(inode);
5064                         } else
5065                                 ext4_wait_for_tail_page_commit(inode);
5066                 }

kernel/fs/ext4/inode.c:ext4_writepage_trans_blocks()は、書き込み予約ページ数を返すために、journalに書かれるデータの分も計上している。

inode.c
5189 /*
5190  * Calculate the total number of credits to reserve to fit
5191  * the modification of a single pages into a single transaction,
5192  * which may include multiple chunks of block allocations.
5193  *
5194  * This could be called via ext4_write_begin()
5195  *
5196  * We need to consider the worse case, when
5197  * one new block per extent.
5198  */
5199 int ext4_writepage_trans_blocks(struct inode *inode)
5200 {
5201         int bpp = ext4_journal_blocks_per_page(inode);
5202         int ret;
5203 
5204         ret = ext4_meta_trans_blocks(inode, bpp, bpp);
5205 
5206         /* Account for data blocks for journalled mode */
5207         if (ext4_should_journal_data(inode))
5208                 ret += bpp;
5209         return ret;
5210 }

kernel/fs/ext4/inode.c:ext4_page_mkwrite()の先にある方は...よくわからない。**ext4_page_mkwrite()**はDocumentationのLockingによると、read-onlyからwritableに変わるときに呼ばれるエントリ関数らしい...

inode.c
5526         /* Delalloc case is easy... */
5527         if (test_opt(inode->i_sb, DELALLOC) &&
5528             !ext4_should_journal_data(inode) &&
5529             !ext4_nonda_switch(inode->i_sb)) {
5530                 do {
5531                         ret = block_page_mkwrite(vma, vmf,
5532                                                    ext4_da_get_block_prep);
5533                 } while (ret == -ENOSPC &&
5534                        ext4_should_retry_alloc(inode->i_sb, &retries));
5535                 goto out_ret;
5536         }
555         ->page_mkwrite() is called when a previously read-only pte is
556 about to become writeable. The filesystem again must ensure that there are
557 no truncate/invalidate races, and then return with the page locked. If
558 the page has been truncated, the filesystem should not look up a new page
559 like the ->fault() handler, but simply return with VM_FAULT_NOPAGE, which
560 will cause the VM to retry the fault.
561 
562         ->pfn_mkwrite() is the same as page_mkwrite but when the pte is
563 VM_PFNMAP or VM_MIXEDMAP with a page-less entry. Expected return is
564 VM_FAULT_NOPAGE. Or one of the VM_FAULT_ERROR types. The default behavior
565 after this call is to make the pte read-write, unless pfn_mkwrite returns
566 an error.

kernel/fs/ext4/inode.c:ext4_page_mkwrite()の後にある方は、先の**ext4_convert_inline_data_to_extent()**と同じ。

inode.c
5579         if (!ret && ext4_should_journal_data(inode)) {
5580                 if (ext4_walk_page_buffers(handle, page_buffers(page), 0,
5581                           PAGE_SIZE, NULL, do_journal_get_write_access)) {
5582                         unlock_page(page);
5583                         ret = VM_FAULT_SIGBUS;
5584                         ext4_journal_stop(handle);
5585                         goto out;
5586                 }
5587                 ext4_set_inode_state(inode, EXT4_STATE_JDATA);
5588         }

kernel/fs/ext4/ext4_jbd2.c:__ext4_forget()は、data=journalの場合はrevokeではなくforgetを呼ぶようにしている。

ext4_jbd2.c
206         /* Never use the revoke function if we are doing full data
207          * journaling: there is no need to, and a V1 superblock won't
208          * support it.  Otherwise, only skip the revoke on un-journaled
209          * data blocks. */
210 
211         if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA ||
212             (!is_metadata && !ext4_should_journal_data(inode))) {
213                 if (bh) {
214                         BUFFER_TRACE(bh, "call jbd2_journal_forget");
215                         err = jbd2_journal_forget(handle, bh);
216                         if (err)
217                                 ext4_journal_abort_handle(where, line, __func__,
218                                                           bh, handle, err);
219                         return err;
220                 }
221                 return 0;
222         }

kernel/fs/ext4/extents.c:get_default_free_blocks_flags()は、デフォルトEXT4_FREE_BLOCKS_FORGETにして、先の**ext4_clear_blocks()**の場合へとつながる。

extentc.c
2483 static inline int get_default_free_blocks_flags(struct inode *inode)
2484 {
2485         if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
2486                 return EXT4_FREE_BLOCKS_METADATA | EXT4_FREE_BLOCKS_FORGET;
2487         else if (ext4_should_journal_data(inode))
2488                 return EXT4_FREE_BLOCKS_FORGET;
2489         return 0;
2490 }

kernel/fs/ext4/extents.c:ext4_zero_range()の1つめは、**ext4_force_commit()**を呼ぶことで、現在あるトランザクションを強制的にcommitしてそれが終わるまで待つ。

extents.c
5632         /* Call ext4_force_commit to flush all data in case of data=journal */
5633         if (ext4_should_journal_data(inode)) {
5634                 ret = ext4_force_commit(inode->i_sb);
5635                 if (ret)
5636                         return ret;
5637         }

kernel/fs/ext4/extents.c:ext4_zero_range()の2つめは、journal記録に必要なブロック数の計算。

extents.c
4866         /*
4867          * In worst case we have to writeout two nonadjacent unwritten
4868          * blocks and update the inode
4869          */
4870         credits = (2 * ext4_ext_index_trans_blocks(inode, 2)) + 1;
4871         if (ext4_should_journal_data(inode))
4872                 credits += 2;
4873         handle = ext4_journal_start(inode, EXT4_HT_MISC, credits);

kernel/fs/ext4/extents.c:ext4_collapse_range()は、先と同じく**ext4_force_commit()**を呼んでいる。

extents.c
5486         /* Call ext4_force_commit to flush all data in case of data=journal. */
5487         if (ext4_should_journal_data(inode)) {
5488                 ret = ext4_force_commit(inode->i_sb);
5489                 if (ret)
5490                         return ret;
5491         }

kernel/fs/ext4/extents.c:ext4_insert_range()は、先と同じく**ext4_force_commit()**を呼んでいる。

extents.c
5632         /* Call ext4_force_commit to flush all data in case of data=journal */
5633         if (ext4_should_journal_data(inode)) {
5634                 ret = ext4_force_commit(inode->i_sb);
5635                 if (ret)
5636                         return ret;
5637         }

...やっと**ext4_should_journal_data()**の分が終わった...

more study

debugfsのlogdumpが、journalを読む手助けになる。

# debugfs /dev/sa1
debugfs 1.42.13 (17-May-2015)
debugfs: logdump
Journal starts at block 1, transaction 21047
Found expected sequence 21047, type 1 (descriptor block) at block 1
Found expected sequence 21047, type 2 (commit block) at block 18
Found expected sequence 21048, type 1 (descriptor block) at block 19
Found expected sequence 21048, type 2 (commit block) at block 30
Found expected sequence 21049, type 1 (descriptor block) at block 31
(---snip---)

sleuthkitのjlsがあるという情報も見つかったが、ツールがext3以来メンテされていなくてext4に対応してないっぽかった。

あとは、kernel wiki の ext4 Disk LayoutのJBD2関連を参考にしながら・・・というか、jbd2のdescriptorを定義するstructのヘッダとヘキサダンプとを並べてにらめっこするしかない模様。

あとがき

ext4のjournalのモードとそれによる処理の流れが上記のようにちょっとはわかった気になれた。けど、肝心のjournalの中身は全くわからない。結局のところ、実際のjournalというかtransactionというかのあたりは、ブロックというかページというかの単位でjbd2任せにしているので、そっちも読みすすめなければいけない。

またjournalと密接に関連する機能として、metadata_csumjournal_checksumのチェックサム機能もあり、データ保護の観点ではこれらもきちんと追わないといけない。

私も順を追って理解していくつもりです。REQ_FUAとかREQ_PREFLUSHとかREQ_METAとかREQ_SYNCとか。・・・でもその辺はblock/writeback_cache_control.txtを理解すればいいだけ? ...と思ってたら、a2b809672(v4.10-rc1)からマクロ名というかポリシーが変わっていた。(WRITE_FUAREQ_FUAとか。) そういうのはソースコード追いづらくなるからヤメテ... 誤解でした、マクロの意味を厳密に再定義しただけで、マクロ名を置き換えたりまではしてませんでした。

19
14
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
19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?