Upload
yoshihiro-yunomae
View
210
Download
6
Embed Size (px)
DESCRIPTION
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
Citation preview
ファイルシステム(2)2014/07/27
Yoshihiro YUNOMAE
1
今回のメイン• extentに管理されている物理ブロックの取得
• 初期化・未初期化 • extentの分割 • extentの追加
• extentがぎっしり詰まっているところにextentを追加したらどうなる?
2
extentの復習 (http://www.slideshare.net/
YoshihiroYunomae/f-36905134)
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
extentにおけるmap• 論理ブロックは1つのextentに管理される範囲内か
• 2つ以上にまたがっていたら隣のextentも気にしなければならない • 簡単のため、またがった状況は考えない
• 論理ブロックは初期化されているか • 1つのextentで管理される論理ブロックは、 全て初期化さてれいる or 全く初期化されていないのどちらか
• 周囲の論理ブロックは初期化されているか • 前後の論理ブロックとマージ出来るならマージ
• extentを追加しようと思ったときに、そのブロック内に空きがあるか
5
lblk
1 eof_blockextent Nの管理領域
mapped blocks initialized blocks uninitialized blocks
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)); }
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
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()
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
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
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
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*/
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*/
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);
マージ処理• マージのための条件 ext4_can_extents_be_merged()より
• 前後のextentが両方とも初期化済あるいは未初期化であること • 前のextentの論理ブロックが後のextentの論理ブロックとつながること • 前後のextentの論理ブロックの長さの合計がextentの管理最大数を超えないこと
• 前のextentの物理ブロックが後のextentの論理ブロックとつながること
15
マージ処理
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
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; }
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
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
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()
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);
ext4_split_extent_at()• あるextentをsplitする関数
• P.19の2の左側のときはsplitする必要がないので前方のextentとマージ出来るならマージしてしまう
• それ意外のときはsplitする右側部分をnewexと定義し、ext4_ext_store_pblock()を実行
22
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);
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
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: /* 後で説明 */
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--; }
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;! }! }!…!
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;! }! }!…!
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を加えたい
ext4_ext_split()
extent
extent
…
depth最も深いところの
目的のleaf以下の長さをはかって・・・
m
30
ext4_ext_split()
31
extent
extent
…
depth
eh_entry=m
newblock
…
上書きされないように新しいブロックに待避
memmove m
m
ext4_ext_split()
32
eh_entry - m
extent
extent
empty
…
depth
eh_entry=m
newblock
…
上書きされないように新しいブロックに待避
m
ext4_ext_split()
33
extent
extent
empty
…
depthnewblock0
…
depth - 1
…
newblock1
…
newblock0用
memmove
空きindexのある 深さの前まで続ける
ext4_ext_insert_index() (ext4_ext_split()の延長)
34
挿入したいextentの 論理ブロックはei_blockの前?
eh_entry++
…
newblockへ
eh_entry++
…
newblockへ
前 後
empty
depth=at
…
or
memmove
35
eh_entry++
…
newblockNへ
depth=at depth-1
…
at+1
…
extent
extent
…
depth
…
ext4_ext_split()の結果1
36
eh_entry++
newblockNへ
depth=atdepth
(newblock0)
…
depth - 1 (newblock1)
…
newblock0用
…
at+1 (newblockN)
…
at+2用
ext4_ext_split()の結果2
… ………
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;! }! }!…!
ext4_ext_grow_indepth()
38
hdr
index
index
extent
extent
depth=0 (i_data) depth=1 depth=k
… … …
…
depth
…
全て埋まっているので、 新しく深くしなければならない
ext4_ext_grow_indepth()
39
hdr
index
index
depth=0 (i_data) newblock
…
hdr
memmoveeh_maxの更新
ext4_ext_grow_indepth()
40
hdr
newblockへ
depth=0 (i_data)
depth=0のヘッダの更新 eh_entry=1 eh_depth++
新しい空きが出来た -> ext4_ext_split()を実行 ※indexが元々無い場合、 これで終了
newblock
…
hdr
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;! }! }!…!
やっと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
ext4_ext_insert_extent()
extent
extent
…
depth
前に挿入する場合 (後ろ全部をmemmove)
後に挿入する場合 (後ろ全部をmemmove)
44
ext4_ext_insert_extent()
eh_entries++
extent
extent
newext
…
depth
新しいextentを追加出来たので 以後はnewextを利用
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
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
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;
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