iノードとデータブロックの結合の解除

ファイルのトランケート処理は以下の場合に呼び出される。明示的なtruncateシステムコール呼び出しでは、iノード内のi_sizeで示されるところまで解放する。

  • 明示的なtruncateシステムコール呼び出し
  • トランケートモードのopen時
  • ファイルの削除時

注意 2000.6.16現時点では、open/truncateシステムコールの延長ではtruncate処理は呼び出されない。VFSレベルではinodeサイズのみ変更しており、実際のファイル切り詰め処理を行う下記関数は呼び出されない。ユーザ見え的にはファイルは小さくなったように見えるので問題無いが、フリーブロックが増えないという小さい問題は残る。再度ファイル拡張が発生したとき処理が高速になるというメリットはある。

  ext2_truncate(iノード)
       先行拡張したブロックの解放(ext2_discard_prealloc関数)
       while(ブロックが残っている間) {
            直接ブロックの解放(trunc_direct関数)
            間接ブロックの解放(trunc_indirect関数)
            二段間接ブロックの解放(trunc_dindirect関数)
            三段間接ブロックの解放(trunc_tindirect関数)
            if(全ての解放が完了したら)
                 break
            if(SYNC属性 かつ iノードがDIRTY) {
                 ◆iノードのディスクへの書き込み(ext2_sync_inode関数)
            }
            スケジューラを呼び出し少し待つ(schedule関数)
       }
       iノード更新時間更新
       ◇iノードの遅延書き込み要求(mark_inode_dirty関数)

  trunc_direct(iノード)
       iノードのファイルサイズから解放する直接ブロックの先頭エントリを求める
       while(iノードに解放する直接ブロックある間) {
	    if(その直接ブロックエントリが空?)
                  continue;
            iノードのそのブロックが登録されていたエントリをクリア
            iノードに登録されているブロック数を減らす(メンバi_blocks)
            ◇iノードの遅延書き込み要求(mark_inode_dirty関数)
            連続分したブロックをまとめて解放(ext2_free_blocks関数)
       }
       連続分したブロックをまとめて解放(ext2_free_blocks関数)
       return 全て解放できたら0、失敗したら1

  trunc_indirect(iノード, 間接ブロックが登録されているエントリ番号, 
                   間接ブロックが登録されているエントリ)
       if(間接ブロックがない) return 0;
       間接ブロックを読み出す(bread関数)

       while(間接ブロックに解放するブロックがある間) {
            if(解放しようとしているブロックのバッファが使用中)
                   continue;
            間接ブロックのそのブロックが登録されていたエントリをクリア
            iノードに登録されているブロック数を減らす(メンバi_blocks)
            ◇iノードの遅延書き込み要求(mark_inode_dirty関数)
            ◇間接ブロックの遅延書き込み要求(mark_buffer_dirty関数)
            連続分したブロックをまとめて解放(ext2_free_blocks関数)
       }
       連続分したブロックをまとめて解放(ext2_free_blocks関数)
       間接ブロック自体の解放(check_block_empty関数)
       return 全て解放できたら0、失敗したら1

  trunc_dindirect(iノード, 二段間接ブロックが登録されているエントリ番号, 
                   二段間接ブロックが登録されているエントリ)
       if(二段間接ブロックがない) return 0;
       二段間接ブロックを読み出す(bread関数)

       while(二段間接ブロックに解放する間接ブロックがある間) {
            間接ブロックに登録されているデータブロックの解放(trunc_indirect関数)
       }
       二段間接ブロック自体の解放(check_block_empty関数)
       return 全て解放できたら0、失敗したら1

  trunc_tindirect(iノード)
       if(三段間接ブロックがない) return 0;
       三段間接ブロックを読み出す(bread関数)

       while(三段間接ブロックに解放する二段間接ブロックがある間) {
            二間接ブロックに登録されている間接ブロックと
               データブロックの解放(trunc_dindirect関数)
       }
       三段間接ブロック自体の解放(check_block_empty関数)
       return 全て解放できたら0、失敗したら1

  check_block_empty(inode, bh, ind_bh)
       if(間接ブロックのバッファがI/O中なら)
             I/O完了を待ち合わせる

       if (間接ブロックに何か残っているなら) {
             if(SYNC属性 かつ 間接ブロックのバッファがDIRTY) {
                 ◆間接ブロックの書き込み(ll_rw_block関数, wait_on_buffer関数)
             }
             間接ブロックのバッファの解放(brelse関数)
             return 0;
       }
       if (他から、この間接ブロックのバッファが参照されている)
             return 1;
       if (間接ブロックが空なら) {
             iノード(または別の間接ブロック)から、この間接ブロックの登録を消す
             iノードに登録されているブロック数を減らす(メンバi_blocks)
             ◇iノードの遅延書き込み要求(mark_inode_dirty関数)
             間接ブロックのバッファを無効にする(bforget関数)
             間接ブロック自体を解放(ext2_free_blocks関数)
       }

下図は、ファイルトランケート処理の順番を図に表した物である。◆は同期書き込み、◇は遅延書き込みを表している。◆◇の後ろの数字は、その処理の順番を示している。

img42.gif

問題点など

SYNCモードでマウントしたときの問題点

  1. iノード/間接ブロックの更新より、フリービットマップへの登録が 先に動作する。フリービットマップの方が先に更新されると フリービットマップとファイル構造の両方に登録された 状態になり、 DUPブロックが発生する可能性がある。 さらに、iノード/間接ブロックの更新前に、 解放されたブロックがフリービットマップから別のファイル構造に 組み込まれてしまう可能性もある。
  2. iノードと間接ブロックの書き込み順が逆である。 間接ブロックのほうが先に解放されるため、iノードが 解放済みの間接ブロックを参照しているタイミングが存在する。 これもシステムクラッシュ時のDUPブロックの原因となる。
  3. ファイル削除時は、既に全てのディレクトリからの参照は あり得ないが、解放するiノードや間接ブロックの 同期書き込みが動作することがある。(単純に性能上の問題)
  4. これ自体、ファイルシステムの構造に影響を与えるものでは無いが、 ファイルの先頭に近い方のブロックから解放しているのは 気になる。
  5. ファイルトランケート時に、ファイル生成時間まで 設定し直しているが、POSIX的にはこれは正しいのだろうか?

(NIS)HirokazuTakahashi
2000年12月09日 (土) 23時55分06秒 JST
1