Successfully reported this slideshow.

Ext4 filesystem(2)

1,019 views

Published on

Investigation on ext4 filesystem of current Linux
This slide focuses on addition of ext4 extents.

EXT4 filesystem(1):
http://www.slideshare.net/YoshihiroYunomae/f-36905134

Published in: Technology
  • Be the first to comment

Ext4 filesystem(2)

  1. 1. ファイルシステム(2) 2014/07/27 Yoshihiro YUNOMAE 1
  2. 2. 今回のメイン • extentに管理されている物理ブロックの取得 • 初期化・未初期化 • extentの分割 • extentの追加 • extentがぎっしり詰まっているところに extentを追加したらどうなる? 2
  3. 3. extentの復習 (http://www.slideshare.net/ YoshihiroYunomae/f-36905134)
  4. 4. ext4_map_blocks()! -> ext4_ext_map_blocks() // extent用! -> ext4_ext_handle_unwritten_extents()! -> ext4_ext_convert_to_initialized() ! -> ext4_split_extent()! -> ext4_split_extent_at()! -> ext4_ext_insert_extent()! -> ext4_create_new_leaf() 4 ext4_map_blocks() • ext4用のlblockからpblockを取得する関数 • pblockの取得のことをmapという • map中はi_data_semのwrite lockがかかる • i_dataがこの操作中に変わることがあるため i_data_sem wlock
  5. 5. extentにおけるmap • 論理ブロックは1つのextentに管理される範囲内か • 2つ以上にまたがっていたら隣のextentも気にしなければならない • 簡単のため、またがった状況は考えない • 論理ブロックは初期化されているか • 1つのextentで管理される論理ブロックは、                全て初期化さてれいる or 全く初期化されていないのどちらか • 周囲の論理ブロックは初期化されているか • 前後の論理ブロックとマージ出来るならマージ • extentを追加しようと思ったときに、そのブロック内に空きがあるか 5 lblk 1 eof_blockextent Nの管理領域 mapped blocks initialized blocks uninitialized blocks
  6. 6. extentの初期化済・未初期化 • 正確にはwritten/unwrittenが正しい • initialized/uninitializedを使わないようにしようという動きもある (http://www.spinics.net/lists/linux-ext4/msg42877.html) • ここではwritten: 初期化済, unwritten: 未初期化 という意味 • ext4_extent構造体extのee_len(16bit)のMSBがunwritten(未初期化)フラグ • EXT_INIT_MAX_LEN = (1UL << 15) 6 static inline int ext4_ext_is_unwritten(struct ext4_extent *ext) { return (le16_to_cpu(ext->ee_len) > EXT_INIT_MAX_LEN); } static inline int ext4_ext_get_actual_len(struct ext4_extent *ext) { return (le16_to_cpu(ext->ee_len) <= EXT_INIT_MAX_LEN ? le16_to_cpu(ext->ee_len) : (le16_to_cpu(ext->ee_len) - EXT_INIT_MAX_LEN)); }
  7. 7. ext4_ext_map_blocks() • extent用のmapするブロックを取得する関数 • extentでないときはext4_ind_map_blocks() • mapするブロックが複数にまたがっていないとき、ブロックが初期化済か  未初期化かで処理がわかれる。 • 初期化済 & 未初期化に変更: ext4_ext_convert_initialized_extent() • fallocate(2)でzero埋めするとき(FALLOC_FL_ZERO_RANGE)に使用 • 初期化済: そのままそのブロックを使用 • 未初期化: ext4_ext_handle_unwritten_extents() • ここでは未初期化の場合を考える 7
  8. 8. 8 ext4_map_blocks()! -> ext4_ext_map_blocks() // extent用! -> ext4_ext_handle_unwritten_extents()! -> ext4_ext_convert_to_initialized() ! -> ext4_split_extent()! -> ext4_split_extent_at()! -> ext4_ext_insert_extent()! -> ext4_create_new_leaf()! ! ! ! ! ! ! ! -> ext4_ext_split()! ! ! ! ! ! ! ! -> ext4_ext_grow_indepth()
  9. 9. ext4_ext_handle_unwritten_extents() • 以下の条件を満たしたときに、この関数を実行 • あるextentsで管理している論理ブロック内に、mapしようとしている  論理ブロックが存在(またがっていない) • そのextentsで管理している論理ブロックは初期化されていない • ext4_map_blocks()で使われるフラグによって様々な関数に分岐し、実際に mapするブロック数(allocated)を取得 • ここではbuffered read/writeを想定 • ext4_ext_convert_to_initialized()を実行して、その結果をallocatedとして 返す • et4_ext_convert_to_initialized()の結果によっては、要求したブロック数に 対して多くアロケーションする場合があり(前後のextentとマージした場 合など)、その場合は要求したブロック数に修正する 9
  10. 10. ext4_ext_convert_to_initialized() • ここから先は、初期化されない論理ブロックと、初期化される論理ブロック (mapする論理ブロック)を分ける作業 • 可能であれば前後のextentとマージしてしまう • extentを2つあるいは3つにsplit • 今からmapする論理ブロックがextentのどの位置にあるか、また前後の論理 ブロックの状態(初期化済か未初期化)で、マージするか分裂するか決定 ! • m_lblk: mapする最初の論理ブロック(mapped logical block) • map_len: mapする論理ブロック数 • ee_block: extentの最初の論理ブロック • ee_len: extentの使用論理ブロック数 10
  11. 11. ext4_ext_convert_to_initialized() • ここから先は、初期化されない論理ブロックと、初期化される倫理ブロック (mapする論理ブロック)を分ける作業 • 可能であれば前後のextentとマージしてしまう! • extentを2つあるいは3つにsplit • 今からmapする論理ブロックがextentのどの位置にあるか、また前後の論理 ブロックの状態(初期化済か未初期化)で、マージするか分裂するか決定 ! • m_lblk: mapする最初の論理ブロック(mapped logical block) • map_len: mapする論理ブロック数 • ee_block: extentの最初の論理ブロック • ee_len: extentの使用論理ブロック数 11
  12. 12. ext4_ext_convert_to_initialized() • 前方のextentとマージする条件 • L0: extentの最初からmap • L1: extentの範囲内(mal_len >= ee_lenはextentの削除を意味) • L2: 前方にextentが存在 • C1: 前方のextentが初期化済 • C2, C3: 前方のextetのブロックと論理的・物理的に連続 • C4: マージした後もextentの最大サイズを超えない 12 if ((map->m_lblk == ee_block) && /*L0*/ (map_len < ee_len) && /*L1*/ (ex > EXT_FIRST_EXTENT(eh))) { /*L2*/ … if ((!ext4_ext_is_unwritten(abut_ex)) && /*C1*/ ((prev_lblk + prev_len) == ee_block) && /*C2*/ ((prev_pblk + prev_len) == ee_pblk) && /*C3*/ (prev_len < (EXT_INIT_MAX_LEN - map_len))) { /*C4*/
  13. 13. ext4_ext_convert_to_initialized() • 後方のextentとマージする条件 • L0: extentの最後までmap • L1: extentの範囲内(mal_len >= ee_lenはextentの削除を意味) • L2: 後方にextentが存在 • C1: 後方のextentが初期化済 • C2, C3: 後方のextetのブロックと論理的・物理的に連続 • C4: マージした後もextentの最大サイズを超えない 13 } else if (((map->m_lblk + map_len) == (ee_block + ee_len)) && /*L0*/ (map_len < ee_len) && /*L1*/ ex < EXT_LAST_EXTENT(eh)) { /*L2*/ … if ((!ext4_ext_is_unwritten(abut_ex)) && /*C1*/ ((map->m_lblk + map_len) == next_lblk) && /*C2*/ ((ee_pblk + ee_len) == next_pblk) && /*C3*/ (next_len < (EXT_INIT_MAX_LEN - map_len))) { /*C4*/
  14. 14. ext4_ext_convert_to_initialized() • ee_len(extentの長さ) <= ゼロ初期化長の場合、そのextent全体をゼロ初期化 ! ! • s_extent_max_zeroout_kb: デフォルト32 • extent_max_zeroout_kbというsysfsファイルが用意されている • /sys/fs/ext4/sda1/extent_max_zeroout_kb ! • 周囲とマージ出来る場合はマージする • ext4_ext_try_to_merge() 14 max_zeroout = sbi->s_extent_max_zeroout_kb >> (inode->i_sb->s_blocksize_bits - 10);
  15. 15. マージ処理 • マージのための条件 ext4_can_extents_be_merged()より • 前後のextentが両方とも初期化済あるいは未初期化であること • 前のextentの論理ブロックが後のextentの論理ブロックとつながること • 前後のextentの論理ブロックの長さの合計がextentの管理最大数を超えな いこと • 前のextentの物理ブロックが後のextentの論理ブロックとつながること 15
  16. 16. マージ処理 16 static void ext4_ext_try_to_merge() { … int merge_done = 0; … if (ex > EXT_FIRST_EXTENT(eh)) /* 左側のextentとマージ */ merge_done = ext4_ext_try_to_merge_right(inode, path, ex - 1); ! if (!merge_done) /* 右側のextentとマージ */ (void) ext4_ext_try_to_merge_right(inode, path, ex); ! /* inode->i_dataの中に収められるなら収める */ ext4_ext_try_to_merge_up(handle, inode, path); }
  17. 17. マージ処理 17 static int ext4_ext_try_to_merge_right() { … int merge_done = 0; … while (ex < EXT_LAST_EXTENT(eh)) { if (!ext4_can_extents_be_merged(inode, ex, ex + 1)) break; … ex->ee_len = cpu_to_le16(ext4_ext_get_actual_len(ex) + ext4_ext_get_actual_len(ex + 1)); … if (ex + 1 < EXT_LAST_EXTENT(eh)) { … memmove(ex + 1, ex + 2, len); } le16_add_cpu(&eh->eh_entries, -1); merge_done = 1; … } return merge_done; }
  18. 18. ext4_ext_convert_to_initialized() • ここから先は、初期化されない論理ブロックと、初期化される倫理ブロック (今からmapする論理ブロック)を分ける作業 • 可能であれば前後のextentとマージしてしまう • extentを2つあるいは3つにsplit! • 今からmapする論理ブロックがextentのどの位置にあるか、また前後の論理 ブロックの状態(初期化済か未初期化)で、マージするか分裂するか決定 ! • m_lblk: mapする最初の論理ブロック(mapped logical block) • map_len: mapする論理ブロック数 • ee_block: extentの最初の論理ブロック • ee_len: extentの使用論理ブロック数 18
  19. 19. ext4_ext_convert_to_initialized() 19 • ここまで来たということは、ee_len > max_zeroout • splitには4つのパターンが存在(ここでは4パターンにするだけ) 1. 3つに分かれるパターン(真ん中に書き込まれる論理ブロックが存在) 2. 2つに分かれ、前半部分がmax_zerooutより小さいなら最初から0初期化 3. 2つに分かれ、後半部分がmax_zerooutより小さいなら最後まで0初期化 4. 2つに分かれるが初期化されない(両方ともmax_zerooutより大きい) mapped blocks max_zeroout ee_len 1 2 43
  20. 20. 20 ext4_map_blocks()! -> ext4_ext_map_blocks() // extent用! -> ext4_ext_handle_unwritten_extents()! -> ext4_ext_convert_to_initialized() ! -> ext4_split_extent()! -> ext4_split_extent_at()! -> ext4_ext_insert_extent()! -> ext4_create_new_leaf()! ! ! ! ! ! ! ! -> ext4_ext_split()! ! ! ! ! ! ! ! -> ext4_ext_grow_indepth()
  21. 21. ext4_split_extent() • extent全体をsplitする関数 • 実際にsplitするのはext4_split_extent_at() 21 // mapped blocksがextent内 P.19 1, 2の右側 if (map->m_lblk + map->m_len < ee_block + ee_len) { … flags1 = flags | EXT4_GET_BLOCKS_PRE_IO; /* 後で出てくる */ … err = ext4_split_extent_at(handle, inode, path, map->m_lblk + map->m_len, split_flag1, flags1); … // 分岐しているが実際は全パターンで通過 p.19, 1-4の左側 if (map->m_lblk >= ee_block) { … err = ext4_split_extent_at(handle, inode, path, map->m_lblk, split_flag1, flags);
  22. 22. ext4_split_extent_at() • あるextentをsplitする関数 • P.19の2の左側のときはsplitする必要がないので前方のextentとマージ出 来るならマージしてしまう • それ意外のときはsplitする右側部分をnewexと定義し、 ext4_ext_store_pblock()を実行 22
  23. 23. ext4_split_extent_at() 23 static int ext4_split_extent_at(…, ext4_lblk_t split,…) { … struct ext4_extent newex; struct ext4_extent *ex2 = NULL; … if (split == ee_block) { // P.19 2の左側だったら・・・ … ext4_ext_try_to_merge(handle, inode, path, ex); … goto out; } … ex2 = &newex; ex2->ee_block = cpu_to_le32(split); ex2->ee_len = cpu_to_le16(ee_len - (split - ee_block)); ext4_ext_store_pblock(ex2, newblock); … err = ext4_ext_insert_extent(handle, inode, path, &newex, flags);
  24. 24. ext4_map_blocks()! -> ext4_ext_map_blocks() // extent用! -> ext4_ext_handle_unwritten_extents()! -> ext4_ext_convert_to_initialized() ! -> ext4_split_extent()! -> ext4_split_extent_at()! -> ext4_ext_insert_extent()! -> ext4_create_new_leaf()! ! ! ! ! ! ! ! -> ext4_ext_split()! ! ! ! ! ! ! ! -> ext4_ext_grow_indepth() 24
  25. 25. ext4_ext_insert_extent() • 新しいextentを追加する関数 • 前後のextentとマージ出来るならマージしてしまう • もしマージ出来ないなら、今あるdepthにextentを追加するための空きがあ るかチェック • 空きがあったら新しいextentを作成する • 空きが無かったらext4_create_new_leaf()を実行し、空きスペースを作る • 空きスペースが出来たら、新しいextentを作成する if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max)) /* 今のdepthに空きがある */ goto has_space; … /* 今のdepthに空きが無い */ err = ext4_ext_create_new_leaf(handle, inode, mb_flags, gb_flags, path, newext); … has_space: /* 後で説明 */
  26. 26. ext4_create_new_leaf() • 新しいextentを割り込んで挿入するために、leafに空きを作る関数 • ext4_ext_find_extent()から取得したpath配列から、各深さに対して空のindex を探し出す • 出来るだけ深いところから(leafから)indexの空きを探し出す ! ! ! ! ! ! ! • もし空のindexがあった場合、ext4_ext_split()でindexを追加する • もし全ての深さに空のindexが無かった場合、ext4_ext_grow_index()で    1段深くする • 結果空きのindexが出来るのでext4_ext_split()でindexを追加する 26 i = depth = ext_depth(inode); curp = path + depth; // 最初はleafから // eh_entries < eh_max ? while (i > 0 && !EXT_HAS_FREE_INDEX(curp)) { i--; curp--; }
  27. 27. ext4_create_new_leaf() 27 static int ext4_ext_create_new_leaf()! {! …! repeat:! ! // indexの空きを検索(前ページ)! …! if (EXT_HAS_FREE_INDEX(curp)) {! ! // 空き領域にindexを追加! err = ext4_ext_split(handle, inode, mb_flags, path, newext, i);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! } else {! // 1段深くする! err = ext4_ext_grow_indepth(handle, inode, mb_flags, newext);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! /*! * only first (depth 0 -> 1) produces free space;! * in all other cases we have to split the grown tree! */! depth = ext_depth(inode);! if (path[depth].p_hdr->eh_entries == path[depth].p_hdr->eh_max) {! /* now we need to split */! goto repeat;! }! }! …!
  28. 28. ext4_create_new_leaf() 28 static int ext4_ext_create_new_leaf()! {! …! repeat:! ! // indexの空きを検索(前ページ)! …! if (EXT_HAS_FREE_INDEX(curp)) {! ! // 空き領域にindexを追加! err = ext4_ext_split(handle, inode, mb_flags, path, newext, i);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! } else {! // 1段深くする! err = ext4_ext_grow_indepth(handle, inode, mb_flags, newext);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! /*! * only first (depth 0 -> 1) produces free space;! * in all other cases we have to split the grown tree! */! depth = ext_depth(inode);! if (path[depth].p_hdr->eh_entries == path[depth].p_hdr->eh_max) {! /* now we need to split */! goto repeat;! }! }! …!
  29. 29. ext4_ext_split() 29 hdr index index empty extent extent depth=0 (i_data) depth=1 depth=at … … … … depth … path[1].p_idx path[depth].p_ext ここに 新しいextentを加えたい
  30. 30. ext4_ext_split() extent extent … depth 最も深いところの 目的のleaf以下の長さを はかって・・・ m 30
  31. 31. ext4_ext_split() 31 extent extent … depth eh_entry=m newblock … 上書きされないように 新しいブロックに待避 memmove m m
  32. 32. ext4_ext_split() 32 eh_entry - m extent extent empty … depth eh_entry=m newblock … 上書きされないように 新しいブロックに待避 m
  33. 33. ext4_ext_split() 33 extent extent empty … depth newblock0 … depth - 1 … newblock1 … newblock0用 memmove 空きindexのある 深さの前まで続ける
  34. 34. ext4_ext_insert_index() (ext4_ext_split()の延長) 34 挿入したいextentの 論理ブロックはei_blockの前? eh_entry++ … newblockへ eh_entry++ … newblockへ 前 後 empty depth=at … or memmove
  35. 35. 35 eh_entry++ … newblockNへ depth=at depth-1 … at+1 … extent extent … depth … ext4_ext_split()の結果1
  36. 36. 36 eh_entry++ newblockNへ depth=at depth (newblock0) … depth - 1 (newblock1) … newblock0用 … at+1 (newblockN) … at+2用 ext4_ext_split()の結果2 … ………
  37. 37. ext4_create_new_leaf() 37 static int ext4_ext_create_new_leaf()! {! …! repeat:! ! // indexの空きを検索(前ページ)! …! if (EXT_HAS_FREE_INDEX(curp)) {! ! // 空き領域にindexを追加! err = ext4_ext_split(handle, inode, mb_flags, path, newext, i);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! } else {! // 1段深くする! err = ext4_ext_grow_indepth(handle, inode, mb_flags, newext);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! /*! * only first (depth 0 -> 1) produces free space;! * in all other cases we have to split the grown tree! */! depth = ext_depth(inode);! if (path[depth].p_hdr->eh_entries == path[depth].p_hdr->eh_max) {! /* now we need to split */! goto repeat;! }! }! …!
  38. 38. ext4_ext_grow_indepth() 38 hdr index index extent extent depth=0 (i_data) depth=1 depth=k … … … … depth … 全て埋まっているので、 新しく深くしなければならない
  39. 39. ext4_ext_grow_indepth() 39 hdr index index depth=0 (i_data) newblock … hdr memmove eh_maxの更新
  40. 40. ext4_ext_grow_indepth() 40 hdr newblockへ depth=0 (i_data) depth=0のヘッダの更新 eh_entry=1 eh_depth++ 新しい空きが出来た -> ext4_ext_split()を実行 ※indexが元々無い場合、 これで終了 newblock … hdr
  41. 41. ext4_create_new_leaf() 41 static int ext4_ext_create_new_leaf()! {! …! repeat:! ! // indexの空きを検索(前ページ)! …! if (EXT_HAS_FREE_INDEX(curp)) {! ! // 空き領域にindexを追加! err = ext4_ext_split(handle, inode, mb_flags, path, newext, i);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! } else {! // 1段深くする! err = ext4_ext_grow_indepth(handle, inode, mb_flags, newext);! …! ! ! ! // ext4_ext_find_extent()を使って配列pathの更新! /*! * only first (depth 0 -> 1) produces free space;! * in all other cases we have to split the grown tree! */! depth = ext_depth(inode);! if (path[depth].p_hdr->eh_entries == path[depth].p_hdr->eh_max) {! /* now we need to split */! goto repeat;! }! }! …!
  42. 42. やっとextentに空きが 出来た・・・ ext4_ext_insert_extent() { … if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max)) /* 今のdepthに空きがある */ goto has_space; … /* 今のdepthに空きが無い */ err = ext4_ext_create_new_leaf(handle, inode, mb_flags, gb_flags, path, newext); … has_space: /* 後で説明 */
  43. 43. 43 ext4_ext_insert_extent() extent extent … depth 前に挿入する場合 (後ろ全部をmemmove) 後に挿入する場合 (後ろ全部をmemmove)
  44. 44. 44 ext4_ext_insert_extent() eh_entries++ extent extent newext … depth 新しいextentを追加出来たので 以後はnewextを利用
  45. 45. ext4の積み残し(適当な分類) • ラスボス級(確実に1回以上かかる) • ジャーナリング(jbd/jbd2) • 中ボス級(1回で終わるかも) • マウントオプション • extend status tree • fallocate(2) • 一般級(複数個まとめて出来る) • delayed / multiblock / persistent allocation • inline data / xattr • その他 • big allocation時のsmall fileの対策? • ext4のfsckが高速化したのはなぜ? • ext4_inode構造体の更新頻度は? 45
  46. 46. Appendix1: ext4_ext_find_extent() • 目的の論理ブロックを管理している、各深さのindex(tree)/extent(leaf)を バイナリサーチで探し出す関数 • ext4_ext_path構造体の深さ分の長さを持った配列pathを更新 • p_block: index/extentのある物理ブロック • p_depth: 管理している深さ(0であればextent) • p_ext: 目的のextentのポインタ(p_depth==0のときのみ存在) • p_idx: 目的のextentを管理しているある深さのindexへのポインタ (p_depth>0) • p_hdr: ある深さのheaderへのポインタ • p_bh: index/extentのある物理ブロックのバッファヘッダへのポインタ • pathの配列順所は深さと逆 • path[max_depth].p_depth == 0 • path[0].p_depth == max_depth 46
  47. 47. Appendix2: i_size以上の管理 • max_zerooutを取得する前に、以下のようなソースあり。 ! ! • EXT4_EXT_MAY_ZEROOUT: もしENOSPCでsplitに失敗したらそのextentを初 期化するフラグ • i_size以上の領域に初期化済のブロックがあると、fsckで不正なinode sizeだと されてしまうため、eof_blockより大きいextentにはENOSPCでsplit出来なく ても初期化しない • 一昔前(4年前)ではinode->i_size以上のブロック領域をextentで管理出来た • EXT4_INODE_EOFBLOCKS (inode用), EXT4_EOFBLOCKS_FL(user空間用) • しかしこの機能はもはや使われていない • カーネル内ではEXT4_INODE_EOFBLOCKSをチェックして特別な処理を する機能は無い • fsckもEXT4_EOFBLOCKS_FLをremove 47 split_flag |= ee_block + ee_len <= eof_block ? EXT4_EXT_MAY_ZEROOUT : 0;
  48. 48. buffered writeのprocdedure ext4_write_begin()! -> _ext4_get_block() // EXT4_GET_BLOCKS_CREATE(buffered write)! -> ext4_map_blocks()! -> ext4_ext_map_blocks()// ext4_es_lookup_extent()×! -> ext4_ext_handle_unwritten_extents() // m_lblkがextentの管理内!                        & extentはunwritten! ! ! ! ! ! ! ! add EXT4_GET_BLOCKS_METADATA_NOFAIL! -> ext4_ext_convert_to_initialized() ! -> ext4_split_extent() // mapがextentの管理の途中から開始! ! ! ! ! ! ! ! ! ! mapがextentと同一長さでない! ! ! ! ! ! ! ! & ee_len * blocksize > extent_max_zeroout_kb! (extent_max_zeroout_kbはデフォルト32、sysfsでも変更可能)! -> ext4_split_extent_at()! -> ext4_ext_insert_extent()! -> ext4_create_new_leaf() 48

×