XFS 0.1
エディッション 0
製作著作 © 2006 Silicon Graphics Inc.
© Copyright 2006 Silicon Graphics Inc. All rights reserved. Permission is granted to copy, distribute, and/or modify this document under the terms of the Creative Commons Attribution-Share Alike, Version 3.0 or any later version published by the Creative Commons Corp. A copy of the license is available at http://creativecommons.org/licenses/by-sa/3.0/us/.日本語訳は、2011年、kanda.motohiro@gmail.com による。
概要
この文書は、XFS ファイルシステム構造を説明します。
この文書は、XFS ファイルシステムのレイアウトを説明します。
XFS ファイルシステムドライバーと一緒に提供される xfs_db ユーザ空間ツールを使って、手作業で内部を調査する例も示します。
TODO:
すべての以下の XFS タイプは xfs_types.h にあります。NULL 値は、ディスク上では常に、−1(つまり、その値のすべてのビットが1)です。
xfs_ino_t
Unsigned 64 bit 絶対 inode 番号 (「inode 番号」).
xfs_off_t
Signed 64 bit ファイルオフセット
xfs_daddr_t
Signed 64 bit ディスクアドレス
xfs_agnumber_t
Unsigned 32 bit AG (3章アロケーショングループ) 番号
xfs_agblock_t
Unsigned 32 bit AG 相対ブロック番号
xfs_extlen_t
Unsigned 32 bit エクステント (5章データエクステント) の、ブロック単位の長さ
xfs_extnum_t
Signed 32 bit ファイルに含まれるエクステントの数
xfs_dablk_t
Unsigned 32 bit ディレクトリ (6章ディレクトリ) と、拡張属性 (8章拡張属性) のブロック番号
xfs_dahash_t
Unsigned 32 bit ディレクトリのファイル名あるいは拡張属性名のハッシュ
xfs_dfsbno_t
Unsigned 64 bit ファイルシステムブロック番号で、 AG (3章アロケーショングループ) 番号と、 AG の中でのブロックオフセットから構成される。
xfs_drfsbno_t
Unsigned 64 bit raw の、ファイルシステムブロック番号
xfs_drtbno_t
Unsigned 64 bit エクステント番号で、リアルタイム (「リアルタイムデバイス」) サブボリュームで使われる
xfs_dfiloff_t
Unsigned 64 bit ファイルの中でのブロックオフセット
xfs_dfilblks_t
Unsigned 64 bit ファイルの中でのブロック数
XFS ファイルシステムは、いくつかの、等しいサイズの、アロケーショングループと呼ばれるかたまりに分割されます。それぞれの AG は、それぞれ自身の空き領域管理をする、1つの独立したファイルシステムと考えることができます。それぞれの AG は、最大長が1テラバイト(512 bytes * 231)です。これは、元になるデバイスのセクター長によりません。
それぞれの AG は、以下の機能を持ちます:
ファイルシステム全体の情報を記述するスーパーブロックを持ちます。
空き領域管理
Inode 確保と検索
複数の AG を持つことで、XFS は、同時実行されるアクセス数が増えても、多くの操作を、性能低下なしに、並列して実行することができます。
唯一、グローバルな情報が、最初の AG (プライマリー)で管理されており、それはファイルシステム全体での空き領域情報と inode の総数です。XFS_SB_VERSION2_LAZYSBCOUNTBIT フラグがスーパーブロックにセットされている場合、これらがディスク上で更新されるのは、ファイルシステムがクリーンにアンマウント(アンマウントもしくはシャットダウン)されるときだけです。
mkfs.xfs の直後、プライマリー AG は、以下のディスクレイアウトを持っています。続く他の AG には、 inode は割り当てられていません。
それぞれの構造体は、以下のセクションで説明されます。
AG は、スーパーブロックで始まります。最初のは、プライマリースーパーブロックと呼ばれ、すべての AG を合計した情報もあわせて格納します。セカンダリースーパーブロックは、プライマリースーパーブロックがこわれたときに、 xfs_repair が使用することがあるだけで、他では使われません。
スーパーブロックは以下の構造体で定義されます。それぞれのフィールドの説明をします。
typedef struct xfs_sb { __uint32_t sb_magicnum; __uint32_t sb_blocksize; xfs_drfsbno_t sb_dblocks; xfs_drfsbno_t sb_rblocks; xfs_drtbno_t sb_rextents; uuid_t sb_uuid; xfs_dfsbno_t sb_logstart; xfs_ino_t sb_rootino; xfs_ino_t sb_rbmino; xfs_ino_t sb_rsumino; xfs_agblock_t sb_rextsize; xfs_agblock_t sb_agblocks; xfs_agnumber_t sb_agcount; xfs_extlen_t sb_rbmblocks; xfs_extlen_t sb_logblocks; __uint16_t sb_versionnum; __uint16_t sb_sectsize; __uint16_t sb_inodesize; __uint16_t sb_inopblock; char sb_fname[12]; __uint8_t sb_blocklog; __uint8_t sb_sectlog; __uint8_t sb_inodelog; __uint8_t sb_inopblog; __uint8_t sb_agblklog; __uint8_t sb_rextslog; __uint8_t sb_inprogress; __uint8_t sb_imax_pct; __uint64_t sb_icount; __uint64_t sb_ifree; __uint64_t sb_fdblocks; __uint64_t sb_frextents; xfs_ino_t sb_uquotino; xfs_ino_t sb_gquotino; __uint16_t sb_qflags; __uint8_t sb_flags; __uint8_t sb_shared_vn; xfs_extlen_t sb_inoalignmt; __uint32_t sb_unit; __uint32_t sb_width; __uint8_t sb_dirblklog; __uint8_t sb_logsectlog; __uint16_t sb_logsectsize; __uint32_t sb_logsunit; __uint32_t sb_features2; } xfs_sb_t;
sb_magicnum
ファイルシステムを識別します。値は、 XFS_SB_MAGIC = 0x58465342 "XFSB" です。
sb_blocksize
基本的な領域アロケーションの単位をバイトで表したものです。典型的には、これは4096(4KB)ですが、512から65536の範囲をとります。
sb_dblocks
ファイルシステムで、データとメタデータのために使用可能なブロックの総数。
sb_rblocks
リアルタイムディスクデバイスのブロック数。詳しくは、「リアルタイムデバイス」 を参照。
sb_rextents
リアルタイムデバイスのエクステントの数。
sb_uuid
ファイルシステムの UUID (Universally Unique ID)。ファイルシステムは、デバイス名でなく、UUID を指定してマウントすることができます。
sb_logstart
ログがインターナル(つまり、別のディスクデバイスにあるのではない)場合、ジャーナルログの最初のブロック番号。エクスターナルログデバイスの場合、これはゼロ。(ログはログデバイスの最初のブロックから開始します。)
sb_rootino
ファイルシステムのルート inode 番号。典型的には、4KBのブロック長の時、128です。
sb_rbmino
リアルタイムエクステントで使われる、ビットマップ inode
sb_rsumino
リアルタイムビットマップのサマリー inode
sb_rextsize
ブロック数を単位とするリアルタイムエクステント数
sb_agblocks
ブロック数を単位とする、それぞれの AG の長さ。最後の AG の実際の長さについては、「AG 空き領域管理」 agf_lengthの値を参照。
sb_agcount
ファイルシステムの AG の数。
sb_rbmblocks
リアルタイムビットマップブロックの数。
sb_logblocks
ジャーナルログのためのブロックの数。
sb_versionnum
ファイルシステムのバージョン番号。これは、ファイルシステムを作成するときに決まる、有効にされた機能を示すビットマップです。ディスクチェックツールや、ドライバーが、もし設定されているビットのいずれか1つでも理解できない場合は、ファイルシステムに対して操作をしてはいけません。多くのフラグは、歴史と共に追加されてきた機能を示します。値は4でなくてはならず、上位ビットに以下のフラグを持ちます。
sb_sectsize
元になるディスクセクター長をバイトを単位として示します。ほとんどの場合、これは512です。これは、ダイレクトI/Oを含む、最小のI/Oのアラインメントを決めます。
sb_inodesize
inode の長さをバイトを単位として示します。デフォルトは、256(通常のセクターあたり2つ)ですが、ファイルシステムを作成するとき、最大2048バイトまで大きく出来ます。
sb_inopblock
ブロックあたりの inode 数。これは、sb_blocksize / sb_inodesize に等しいです。
sb_fname[12]
ファイルシステムの名前。マウントコマンドで指定できます。
sb_blocklog
sb_blocksize の log2。つまり、sb_blocksize = 2 ** sb_blocklog
sb_sectlog
sb_sectsize の log2
sb_inodelog
sb_inodesize の log2
sb_inopblog
sb_inopblock の log2
sb_agblklog
sb_agblocks の log2(切り上げ)。この値は、 inode 番号と、エクステントマップで使われる絶対ブロック番号を生成するときに使われます。
sb_rextslog
sb_rextents の log2
sb_inprogress
ファイルシステムが作成途中であることを示すフラグ
sb_imax_pct
ファイルシステムの領域のうち、 inode に使うことのできる最大パーセント。デフォルトは、25%。
sb_icount
ファイルシステムで作成された inode 数のグローバルなカウンター。これは最初のスーパーブロックでのみ、管理されています。
sb_ifree
ファイルシステムでの空き inode 数のグローバルなカウンター。これは最初のスーパーブロックでのみ、管理されています。
sb_fdblocks
ファイルシステムでの空きデータブロック数のグローバルなカウンター。これは最初のスーパーブロックでのみ、管理されています。
sb_frextents
ファイルシステムでの、空きリアルタイムエクステント数のグローバルなカウンター。これは最初のスーパーブロックでのみ、管理されています。
sb_uquotino
ユーザクオータに使われる inode 。これと以下の2つのクオータ関連フィールドは、sb_versionnum に、XFS_SB_VERSION_QUOTABIT フラグがオンのときだけ、使われます。詳しくは、 「クオータ inode 」 を参照下さい。
sb_gquotino
グループあるいはプロジェクトクオータのための inode 。グループとプロジェクトクオータは、同時には使えません。
sb_qflags
クオータフラグ。以下の組み合わせです。
sb_flags
各種フラグ
sb_shared_vn
予備で、ゼロでなくてはいけません。(「vn」はバージョン番号のこと)
sb_inoalignmt
ファイルシステムブロックを単位とする、 inode のチャンクのアラインメント
sb_unit
ブロックを単位とする、元になるデバイスのストライプあるいは RAID ユニット
sb_width
ストライプあるいは RAID ユニットの幅
sb_dirblklog
ファイルシステムブロックを単位とする、ディレクトリブロック確保の粒度の log2 係数
sb_logsectlog
サブボリュームのセクター長の log2 係数。これは、ジャーナリングログが別のディスクデバイスにある(つまり、インターナルでない)ときだけ使われます。
sb_logsectsize
ファイルシステムがログに別のデバイスを使っているとき、ログのセクター長。
sb_logsunit
ログデバイスの、ストライプあるいは RAID ユニット長。これは、バージョン2ログ(sb_versionnum で、XFS_SB_VERSION_LOGV2BIT がオン)のときだけ使われます。
sb_features2
sb_versionnum に、 XFS_SB_VERSION_MOREBITSBIT が立っている時の、その他のバージョンフラグ。現在定義されているのは、以下。
XFS_SB_VERSION2_LAZYSBCOUNTBIT (0x02): 遅延グローバルカウンター。ファイシステムがクリーンにアンマウントされたときだけ、プライマリースーパーブロックを更新する。
XFS_SB_VERSION2_ATTR2BIT (0x08): 拡張属性バージョン2.ファイルシステムでこれを使うと、拡張属性の inode でのレイアウトが最適化されます。
XFS_SB_VERSION2_PARENTBIT (0x10): 親ポインター。すべての inode は、親 inode を指す拡張属性を持つ。この主な用途は、バックアップシステムです。
1つの SATA ディスクで、ファイルシステムが以下のコマンドで作成されました。
# mkfs.xfs -i attr=2 -n size=16384 -f /dev/sda7 meta-data=/dev/sda7 isize=256 agcount=16, agsize=3923122 blks = sectsz=512 attr=2 data = bsize=4096 blocks=62769952, imaxpct=25 = sunit=0 swidth=0 blks, unwritten=1 naming =version 2 bsize=16384 log =internal log bsize=4096 blocks=30649, version=1 = sectsz=512 sunit=0 blks realtime =none extsz=65536 blocks=0, rtextents=0
xfs_db で、スーパーブロックを見ます。
xfs_db> sb xfs_db> p magicnum = 0x58465342 blocksize = 4096 dblocks = 62769952 rblocks = 0 rextents = 0 uuid = 32b24036-6931-45b4-b68c-cd5e7d9a1ca5 logstart = 33554436 rootino = 128 rbmino = 129 rsumino = 130 rextsize = 16 agblocks = 3923122 agcount = 16 rbmblocks = 0 logblocks = 30649 versionnum = 0xb084 sectsize = 512 inodesize = 256 inopblock = 16 fname = "\000\000\000\000\000\000\000\000\000\000\000\000" blocklog = 12 sectlog = 9 inodelog = 8 inopblog = 4 agblklog = 22 rextslog = 0 inprogress = 0 imax_pct = 25 icount = 64 ifree = 61 fdblocks = 62739235 frextents = 0 uquotino = 0 gquotino = 0 qflags = 0 flags = 0 shared_vn = 0 inoalignmt = 2 unit = 0 width = 0 dirblklog = 2 logsectlog = 0 logsectsize = 0 logsunit = 0 features2 = 8
XFS ファイルシステムは、アロケーションブロックでの空き領域を、B+ツリーを使って管理します。1つのB+ツリーが、ブロック番号順に空きを管理し、もうひとつのは空き領域ブロックの大きさ順に管理します。これにより、XFS はあるブロックの近くの空きを探したり、指定した長さの空きを探したりするのが速くできます。
すべてのブロック番号、インデクス、カウントは、AG 内相対です。
AG の2つめのセクターは、2つの空き領域B+ツリーの情報と、AG の空き領域情報を持ちます。「AG 空き領域ブロック」(AGF)は、以下の構造体です。
typedef struct xfs_agf { __be32 agf_magicnum; __be32 agf_versionnum; __be32 agf_seqno; __be32 agf_length; __be32 agf_roots[XFS_BTNUM_AGF]; __be32 agf_spare0; __be32 agf_levels[XFS_BTNUM_AGF]; __be32 agf_spare1; __be32 agf_flfirst; __be32 agf_fllast; __be32 agf_flcount; __be32 agf_freeblks; __be32 agf_longest; __be32 agf_btreeblks; } xfs_agf_t;
セクターの残りのバイトは、ゼロです。XFS_BTNUM_AGF は2です。インデックス0は count B+tree 、インデックス1は size B+tree です。
agf_magicnum
AGF セクターのマジックナンバーです。「XAGF」 (0x58414746).
agf_versionnum
XFS_AGF_VERSION で、現在、1です。
agf_seqno
AG の通し番号です。最初のが0。原文は誤り。
agf_length
ファイルシステムブロックを単位とする AG の長さ。最後の AG を除くすべての AG で、これはスーパーブロックの sb_agblocks に等しいです。最後の AG では、sb_agblocks より小さくなることがあります。AG 長を知るには、この値を使うべきです。
agf_roots
2つの空き領域管理のB+ツリーのルートノードのブロック番号です。
agf_levels
2つの空き領域管理のB+ツリーの深さのレベルです。新しい AG では、これは1で、「ルート」はレベルゼロの1つのリーフを指します。
agf_flfirst
最初の「フリーリスト」ブロックのインデックスです。フリーリストについては、後で説明します。
agf_fllast
最後の「フリーリスト」ブロックのインデックスです。
agf_flcount
「フリーリスト」のブロック数です。
agf_freeblks
AG での現在の空きブロック数です。
agf_longest
AG の、最長の連続空き領域のブロック数です。
agf_btreeblks
空き領域B+ツリーに使われるブロック数です。これは、sb_features2 で、XFS_SB_VERSION2_LAZYSBCOUNTBIT がオンのときだけ使われます。
2つの空き領域B+ツリーは、B+ツリーのリーフに、ブロックオフセットとブロック数をソートした配列で持ちます。最初のB+ツリーはオフセットで、2つめのはカウント、つまり長さでソートされます。
ツリーは、以下のヘッダーを持ちます。
typedef struct xfs_btree_sblock { __be32 bb_magic; __be16 bb_level; __be16 bb_numrecs; __be32 bb_leftsib; __be32 bb_rightsib; } xfs_btree_sblock_t;
リーフはオフセットとカウントの組のソートされた配列を持ち、それはノードキーとしても使われます。
typedef struct xfs_alloc_rec { __be32 ar_startblock; __be32 ar_blockcount; } xfs_alloc_rec_t, xfs_alloc_key_t;
ノードポインターは、AG 相対ブロック番号です。
typedef __be32 xfs_alloc_ptr_t;
空き領域管理は、AG 内で行われるので、すべてのブロック番号は AG 相対で32ビットです。
bb_magic の値は、B+ツリーによります。ブロックオフセットB+ツリーでは、「ABTB」 (0x41425442) で、ブロックカウントB+ツリーでは、「ABTC」 (0x41425443) です。
xfs_btree_sblock_t ヘッダーが、中間とリーフのB+ツリーノードで使われます。
典型的な、4KB ファイルシステムブロック長では、xfs_alloc_ptr_t 配列のオフセットは、0xab0 (2736 decimal) です。
xfs_btree.h には、XFS で使われるB+ツリーのオフセット、カウント、最大値、などを得るための一連のマクロがあります。
以下の図は、1つのリーフからなる1レベルのB+ツリーを示します。
中間ノードでは、リーフポインターは、ブロックの2/3くらいにある独立した配列に置かれます。以下の図は、2レベルの空き領域B+ツリーを示します。
AG フリーリストは、それぞれの AG の4つめのセクターにあり、AGFL と呼ばれます。それは、空き領域B+ツリーを伸ばすために予約されている領域の、AG 相対ブロックポインターの配列です。この領域は、 inode 、データ、ディレクトリ、そして拡張属性などの普通のユーザデータのためには使えません。
作成したばかりのファイルシステムでは、空き領域B+ツリーのルートブロックの直後の4ブロックが、予約されます。空き領域が断片化して、そのブロックが使われてしまうと、AG から追加のブロックが予約されて、この フリーリスト配列に入ります。
フリーリスト配列は、単一のセクターに入っているので、典型的なデバイスでは、配列に128個の要素が入ります。(セクターが512バイトで、AG 相対ブロックポインターは4バイトですから。)実際のサイズは、XFS_AGFL_SIZE マクロで求めます。
配列の有効な要素は、AGF (「AG 空き領域ブロック」) の agf_flfirst, agf_fllast と agf_flcount で表されます。配列は、循環リストとして扱われます。
この予約ブロックがあるため、AG が満杯の時に、エクステントの変更によってあるブロックが解放された時、B+ツリーが必ず更新、つまりスプリットされることができるのを保証できます。
xfs_db の例
以下の例は、意図的に断片化した AG で行われました。
AGF:
xfs_db> agf <ag#> xfs_db> p magicnum = 0x58414746 versionnum = 1 seqno = 0 length = 3923122 bnoroot = 7 cntroot = 83343 bnolevel = 2 cntlevel = 2 flfirst = 22 fllast = 27 flcount = 6 freeblks = 3654234 longest = 3384327 btreeblks = 0
AGFL で、有効な要素は、22から27,境界を含む、であり、前の例の agf の flfirst と fllast から得られます。
xfs_db> agfl 0 xfs_db> p bno[0-127] = 0:4 1:5 2:6 3:7 4:83342 5:83343 6:83344 7:83345 8:83346 9:83347 10:4 11:5 12:80205 13:80780 14:81496 15:81766 16:83346 17:4 18:5 19:80205 20:82449 21:81496 22:81766 23:82455 24:80780 25:5 26:80205 27:83344
ブロックオフセットでソートされた空き領域B+ツリー。ルートブロックは、AGF の bnoroot にあります。
xfs_db> fsblock 7 xfs_db> type bnobt xfs_db> p magic = 0x41425442 level = 1 numrecs = 4 leftsib = null rightsib = null keys[1-4] = [startblock,blockcount] 1:[12,16] 2:[184586,3] 3:[225579,1] 4:[511629,1] ptrs[1-4] = 1:2 2:83347 3:6 4:4
ブロック 2, 83347, 6 と 4 は、空き領域B+ツリーのリーフがあり、開始ブロックを持ちます。ブロック2は、16以上、184586 未満のオフセットを持ち、ブロック4は、511629 から AG の最後までのすべてのオフセットを持ちます。
空き領域B+ツリーの、ブロックカウントでソートされたもの。ルートブロックは、AGF の cntroot にあります。
xfs_db> fsblock 83343 xfs_db> type cntbt xfs_db> p magic = 0x41425443 level = 1 numrecs = 4 leftsib = null rightsib = null keys[1-4] = [blockcount,startblock] 1:[1,81496] 2:[1,511729] 3:[3,191875] 4:[6,184595] ptrs[1-4] = 1:3 2:83345 3:83342 4:83346
ブロック3にあるリーフ。この例では、1つのブロック数だけを持ちます。ブロック数が同じの時は、オフセットの昇順にソートされます。
ブロック 83346 を見ると、最後のところに、最大のブロックがあるのがわかります。
xfs_db> fsblock 83346 xfs_db> type cntbt xfs_db> p magic = 0x41425443 level = 0 numrecs = 344 leftsib = 83342 rightsib = null recs[1-344] = [startblock,blockcount] 1:[184595,6] 2:[187573,6] 3:[187776,6] ... 342:[513712,755] 343:[230317,258229] 344:[538795,3384327]
最長のブロック数は、AGF の longest の値に等しいはずです。
XFS の inode 番号は、2つの形式があります。AG 相対と、絶対です。
AG 相対 inode 番号は、常に32ビットに収まります。実際に使われるビット数は、スーパーブロック(「スーパーブロック」) の sb_inoplog と sb_agblklog の和で決まります。相対 inode 番号は、AG inode 構造体で使われます。
絶対 inode 番号は、AG 相対 inode 番号の上の高位ビットに AG 番号を持ちます。絶対 inode 番号は、ディレクトリ (6章ディレクトリ) エントリーで使われます。
それぞれの AG は自分の inode を管理します。AG の3つめのセクターは、AG の inode についての情報を持ち、AGI と呼ばれます。
AGI は以下の構造体です。
typedef struct xfs_agi { __be32 agi_magicnum; __be32 agi_versionnum; __be32 agi_seqno __be32 agi_length; __be32 agi_count; __be32 agi_root; __be32 agi_level; __be32 agi_freecount; __be32 agi_newino; __be32 agi_dirino; __be32 agi_unlinked[64]; } xfs_agi_t;
agi_magicnum
AGI セクターのマジック番号です。「XAGI」 (0x58414749)
agi_versionnum
XFS_AGI_VERSION であり、現在1です。
agi_seqno
AG の通し番号です。最初のが0。原文は誤り。
agi_length
ファイルシステムブロックを単位とした AG のサイズです。
agi_count
AG に確保されている inode の数です。
agi_root
inode B+ツリーのルートノードのブロック番号です。
agi_level
inode B+ツリーのレベル数です。
agi_freecount
AG での、空き inode 数です。
agi_newino
最近に割り当てられた AG 相対 inode 番号です。
agi_dirino
廃止され、使われていません。常に NULL (−1)です。
agi_unlinked[64]
まだ参照が残っている、unlinked (deleted) inode のハッシュテーブルです。詳しくは、「Unlinked Pointer」 を参照下さい。
inode は、64個ずつのかたまりで確保され、これらのかたまりが確保、解放されるのを管理するのに、B+ツリーが使われます。B+ツリーのルートを持つブロックは、AGI の agi_root にあります。
ノードとリーフのB+ツリーヘッダーは、xfs_btree_sblock 構造体を使い、それはAGF B+ツリー(「AG 空き領域B+ツリー」) が使っているものと同じです。
typedef struct xfs_btree_sblock xfs_inobt_block_t;
リーフは、以下の構造体の配列を持ちます。
typedef struct xfs_inobt_rec { __be32 ir_startino; __be32 ir_freecount; __be64 ir_free; } xfs_inobt_rec_t;
ノードは、以下のタイプを使って、キーとポインターの組を持ちます。
typedef struct xfs_inobt_key { __be32 ir_startino; } xfs_inobt_key_t; typedef __be32 xfs_inobt_ptr_t;
リーフエントリーで、ir_startino が、チャンクの中での開始 inode を示します。ir_freecount が、チャンクの中の空きエントリーの数を示し、ir_free は64要素のビット配列で、どのエントリーが空きかを示します。
以下の図は、1レベルの inode B+ツリーです。
そして、2レベルの inode B+ツリー。
xfs_db の例
TODO:
TODO:
すべてのファイル、ディレクトリ、そしてリンクは、ディスク上では inode で表され、スーパーブロック (「スーパーブロック」) に inode 番号が書いてあるルート inode からたどることができます。AG inode 管理についての以前のセクション (「AG inode 管理」) では、ディスク上の inode の確保と管理について述べました。このセクションでは、inode 自身の中身について述べます。
inode は3つの部分に分けられます。
コアは、inode の本体、つまり、stat のデータと、データ及び属性フォークについての情報です。
di_u 「データフォーク」は、inode についての通常のデータを持ちます。その内容は di_core.di_mode (通常ファイル、ディレクトリ、リンク、など)で指定されるファイル種別で決まります。そして、どれだけの情報が保存されているかは、 di_core.di_format で決まります。以下のユニオンが、このデータを表します。
union { xfs_bmdr_block_t di_bmbt; xfs_bmbt_rec_t di_bmx[1]; xfs_dir2_sf_t di_dir2sf; char di_c[1]; xfs_dev_t di_dev; uuid_t di_muuid; char di_symlink[1]; } di_u;
di_a 「属性フォーク」は、拡張属性を持ちます。そのレイアウトは、di_core.di_aformat 値で決まります。それはこのように表されます。
union { xfs_bmdr_block_t di_abmbt; xfs_bmbt_rec_t di_abmx[1]; xfs_attr_shortform_t di_attrsf; } di_a;
注意:上記の2つのユニオンは、XFS のソースコードではあまり使われません。その代わりに、ユニオンの中の構造体が、di_mode/di_format と di_aformat の値に従って、直接、キャストされます。これらのユニオンは、この文書で、inode で使われるいろいろな構造体をわかりやすく説明するために使われます。
di_next_unlinked の後のinode の残りのスペースは、2つのフォークが置かれており、「リテラル領域」と呼ばれます。inode のオフセット100(0x64) から始まります。
リテラル領域での、2つのそれぞれのフォークのスペースは、inode の長さとdi_core.di_forkoffによって決まります。データフォークは、リテラル領域の開始点からdi_forkoffまでを占めます。属性フォークは、di_forkoff から inode の終端までを占めます。
inode のコアは、96バイトの長さで、ほとんどの stat データを含むファイル自身の情報や、inode の中にあり、コアに続くデータと属性フォークの情報などを含みます。以下の構造体を使います。
typedef struct xfs_dinode_core { __uint16_t di_magic; __uint16_t di_mode; __int8_t di_version; __int8_t di_format; __uint16_t di_onlink; __uint32_t di_uid; __uint32_t di_gid; __uint32_t di_nlink; __uint16_t di_projid; __uint8_t di_pad[8]; __uint16_t di_flushiter; xfs_timestamp_t di_atime; xfs_timestamp_t di_mtime; xfs_timestamp_t di_ctime; xfs_fsize_t di_size; xfs_drfsbno_t di_nblocks; xfs_extlen_t di_extsize; xfs_extnum_t di_nextents; xfs_aextnum_t di_anextents; __uint8_t di_forkoff; __int8_t di_aformat; __uint32_t di_dmevmask; __uint16_t di_dmstate; __uint16_t di_flags; __uint32_t di_gen; } xfs_dinode_core_t;
di_magic
inode のシグネチャで、この2バイトは、0x494e つまり、ASCII の 「IN」です。
di_mode
stat.h にある標準の S_Ixxx 値を使った、モードアクセスビットとファイルのタイプです。
di_version
inode のバージョンで、現在、1あるいは2です。inode のバージョンは、inode コアでの di_onlink, di_nlink と di_projid の値の使い方を指定します。最初は inode はバージョン1で作成されますが、必要があれば、オンザフライでバージョン2に変換できます。
di_format
di_mode タイプと共に、データフォークの形式を指定します。これは以下の値をとります。ディレクトリとリンクでは、「ローカル」つまりそのファイルに関連づくすべてのメタデータが、inode に格納されている、「エクステント」つまり inode は他のファイルシステムブロックを指すエクステントの配列を持ち、そのブロックが関連づくメタデータあるいはデータを格納している、あるいは、「B ツリー」つまり inode はB+ツリーのルートノードを持ち、それがメタデータあるいはデータを格納しているファイルシステムブロックを指す、のいずれかです。3つのフォーマットの間の遷移は inode に関連づくメタデータの量にしたがって行われます。「 dev 」はキャラクターとブロックデバイスに使われ、「 uuid 」は現在は使われていません。
typedef enum xfs_dinode_fmt { XFS_DINODE_FMT_DEV, XFS_DINODE_FMT_LOCAL, XFS_DINODE_FMT_EXTENTS, XFS_DINODE_FMT_BTREE, XFS_DINODE_FMT_UUID } xfs_dinode_fmt_t;
di_onlink
v1 inode では、これは inode へのリンクを示します。これが65535を超えると、 inode は v2 に変換され、リンク数は di_nlink に格納されます。
di_uid
inode 所有者の UID です。
di_gid
inode 所有者の GID です。
di_nlink
inode へのリンク数です。現在の XFS では、どちらの inode バージョンでも有効です。過去のバージョンの XFS は v2 inode をサポートしておらず、この値は使われていなかったため、予備領域(di_pad の一部)とされていました。
di_projid
v2 inode で、所有者のプロジェクト ID を示します。プロジェクト ID が設定されると、 inode は v2 に変換されます。v1 inode では、この値はゼロでなくてはいけません。
di_pad[8]
予備領域。ゼロでなくてはいけません。
di_flushiter
フラッシュのたびに加算されます。
di_atime
以下の UNIX 標準の時刻を表す構造体を使って、ファイルの最終アクセス時刻を示します。これは、ファイルシステムが「noatime」オプションでマウントされているときは、不定値となることがあります。
typedef struct xfs_timestamp { __int32_t t_sec; __int32_t t_nsec; } xfs_timestamp_t;
di_mtime
ファイルが最後に更新された時刻です。
di_ctime
inode の状態が最後に変わった時刻です。
di_size
inode の EOF をバイト単位で示します。これは、エクステント領域(つまり、実際のディスク領域)と比べて、大きくなる時も小さくなる時もあります。通常ファイルでは、これはバイトで表したファイル長です。ディレクトリでは、ディレクトリエントリが占める領域長、リンクでは、シンボリックリンクの長さです。
di_nblocks
inode のデータを格納するために使われたファイルシステムブロック数で、B+ツリーのようなメタデータの分も含みます。これは拡張属性のために使われたブロック数は含みません。
di_extsize
リアルタイムデバイスを持つファイルシステムではエクステントの長さを、通常のファイルシステムでは、エクステント長のヒントを示します。通常のファイルシステムおよびディレクトリでは、このフィールドが使われている場合、XFS_DIFLAG_EXTSZINHERIT フラグが、di_flags でオンでなければいけません。これが立っているディレクトリに作られる inode は、 di_extsize 値を継承し、di_flags に XFS_DIFLAG_EXTSIZE が立ちます。ファイルが現在確保されている領域を超えて書きこまれた場合、XFS は、追加のディスク領域を、この値を元に確保しようとします。
di_nextents
この inode に関連づいたデータエクステントの数
di_anextents
この inode に関連づいた拡張属性エクステントの数
di_forkoff
inode のリテラル領域で拡張属性フォークが開始するオフセットです。これは8ビットの値で、実際のバイト単位のオフセットを得るために8倍されます。(つまり、属性データは64ビットにアラインされています。)これにより、inode の最大値も、2048バイトと制限されます。この値は、拡張属性がない場合はゼロです。属性が作られたとき、 di_forkoff の意味は、スーパーブロックの XFS_SB_VERSION2_ATTR2BIT で決まります。詳しくは、拡張属性のバージョンの節(「拡張属性バージョン」)を参照下さい。
di_aformat
属性フォークの形式です。di_format と同じ値をとりますが、拡張属性データの場合は「ローカル」、「エクステント」、「Bツリー」に限られます。
di_dmevmask
DMAPI イベントマスク
di_dmstate
DMAPI state.
di_flags
inode についてのフラグです。以下の値の組み合わせです。
di_gen
inode の識別に使われる、ジェネレーション番号です。これは、バックアップや、 xfsdump など、 inode のスキャンをするツールで使われます。 inode のジェネレーション番号は、ファイルを削除して、同じ inode 番号を再使用するときに、変更されます。
inode の di_next_unlinked は、unlink(delete) されたが、まだ参照されている inode を管理するために使われます。 inode がunlink されたが、参照が残っている場合、 inode はいずれか1つの AGI (「AG inode 管理」) の agi_unlinked ハッシュバケツに加わります。AGI unlinked bucket は、 inode を指し、di_next_unlinked はチェーンの次の inode を指します。チェーンの最後の inode は、di_next_unlinked が NULL (-1) です。
最後の参照がなくなったとき、 inode は unlinked hash chain から外れ、di_next_unlinked は NULL になります。システムクラッシュがあったとき、XFS のリカバリー処理は、このリストにある inode すべてについて、削除処理を完了します。
ディスク上で unlinked フィールドが使われているのを見ることができるのは、アクティブなファイルシステムもしくは、クラッシュしたシステムにおいてです。クリーンにアンマウントされたり、リカバリーされたファイルシステムでは、この unlink hash chain に inode が下がっているのを見ることはないはずです。
inode のデータフォークの構造は、 inode のタイプと、di_format により決まります。それは、常に、 inode のオフセット100(0x64)のスペースから開始します。 inode の「リテラル領域」の開始点でもあります。データフォークの長さは、タイプとフォーマットによって決まります。最大長は、 inode のサイズと、di_forkoff によって決まります。ソースコードでは、XFS_DFORK_PTR マクロの「which」引数に、XFS_DATA_FORK を与えて使います。あるいは、XFS_DFORK_DPTR マクロを使ってもよいです。
以下のサブセクションは、 inode のタイプごとにデータフォークの内容を説明します。
データフォークは、ファイルのデータエクステントを示します。エクステントはファイルの実際のデータがファイルシステム内でどこにあるかを示します。エクステントは2つの形式があり、di_format の値で区別します。
XFS_DINODE_FMT_EXTENTS: エクステント情報は、 inode に完全に含まれており、ファイルのデータがあるファイルシステムブロックを指すエクステントの配列になっています。エクステントをアクセスするには、XFS_DFORK_DPTR の戻り値を、 xfs_bmbt_rec_t* にキャストします。
XFS_DINODE_FMT_BTREE: エクステント情報は、B+ツリーのリーフに含まれています。 inode はツリーのルートノードを持っており、それをアクセスするには、XFS_DFORK_DPTR の戻り値を、 xfs_bmdr_block_t* にキャストします。
これらのデータエクステント形式の詳細は、後のデータエクステントのセクション(5章データエクステント)で説明されています。
データフォークは、ディレクトリのエントリと、関連づくデータを持ちます。エントリの形式は、やはり di_format で決まり、以下の3つのいずれかです。
XFS_DINODE_FMT_LOCAL: ディレクトリのエントリは、完全に inode に含まれています。これをアクセスするには、XFS_DFORK_DPTR の戻り値を、xfs_dir2_sf_t* にキャストします。
XFS_DINODE_FMT_EXTENTS: 実際のディレクトリのエントリは、他のファイルシステムブロックに含まれており、 inode はこれらのファイルシステムブロックを指すエクステントの配列を持ちます。(xfs_bmbt_rec_t*)
XFS_DINODE_FMT_BTREE: ディレクトリのエントリは、B+ツリーのリーフに含まれています。 inode はルートノードを持ちます。(xfs_bmdr_block_t*)
これらのそれぞれのディレクトリの形式は、後のディレクトリのセクション(6章ディレクトリ) で説明されています。
データフォークは、シンボリックリンクの内容を持ちます。リンクの形式は、di_format で決まり、以下の2つのいずれかです。
XFS_DINODE_FMT_LOCAL: シンボリックリンクは、 inode に完全に含まれています。これをアクセスするには、XFS_DFORK_DPTR の戻り値を char* にキャストします。
XFS_DINODE_FMT_EXTENTS: 実際のシンボリックリンクは、別のファイルシステムブロックにあり、 inode はこれらのファイルシステムブロックを指すエクステントを持ちます。(xfs_bmbt_rec_t*)
シンボリックリンクの詳細は、後のシンボリックリンクのセクション(7章シンボリックリンク) で説明されています。
キャラクターとブロックデバイス(S_IFCHR と S_IFBLK) の場合、XFS_DFORK_DPTR の戻り値をxfs_dev_t* にキャストします。
inode にある属性フォークは、常に、その inode に関連づく拡張属性の位置を持ちます。
inode のリテラル領域(オフセット100から inode の終端まで)での属性フォークの位置は、inode のコアの di_forkoff の値で指定されます。この値がゼロの時、inode は拡張属性を持ちません。ゼロでない場合、リテラル領域でのオフセット=di_forkoff * 8 であり、そのため、inode の最大長は2048バイトに制限されます。属性は、ディスク上では、64ビット境界に確保されなくてはいけません。ソースコードで拡張属性をアクセスするには、XFS_DFORK_PTR マクロの「which」引数に、XFS_ATTR_FORK を指定します。あるいは、XFS_DFORK_APTR マクロを使ってもいいです。
属性フォークのどの構造体が使われるかは、inode の di_aformat の値で決まります。それは、以下のどれかです。
XFS_DINODE_FMT_LOCAL: 拡張属性は、完全に inode に含まれています。XFS_DFORK_APTR を xfs_attr_shortform_t* にキャストしてアクセスします。
XFS_DINODE_FMT_EXTENTS: 属性は、他のファイルシステムブロックに含まれています。 inode は、これらのファイルシステムブロックへを指すエクステントの配列を持ちます。これをアクセスするには、XFS_DFORK_APTR の戻り値を xfs_bmbt_rec_t* にキャストします。
XFS_DINODE_FMT_BTREE: 属性のエクステントは、B+ツリーのリーフに含まれています。inode は、ツリーのルートノードを持っていて、それは、XFS_DFORK_APTR を xfs_bmdr_block_t* にキャストしてアクセスされます。
拡張属性のレイアウトについての詳細は、この文書の後の方の、拡張属性のセクション(8章拡張属性)で説明されます。
拡張属性には2つのバージョンがあります。「attr1」と「attr2」です。属性バージョンは、スーパーブロックの sb_features2 にある XFS_SB_VERSION2_ATTR2BIT フラグで指定されます。これは、inode の残りのスペースが、di_u と di_a の両方のフォークでどのように分割されるかを決め、さらに、di_forkoff の値が inode のコアでどのように使われるかを決めます。
「attr1」属性では、di_forkoff は、コアとinode の終端の中間のどこかに設定されて、変わることはありません。(それは、データ情報のためのスペースを、わざわざ制限するようなものです。)データフォークが伸びて、di_forkoff に届くと、次のレベルの形式にデータを移動しなければいけません(つまり local > extent > btree)。このバージョンでは、属性あるいはデータに必要なスペースがとても少ない場合、inode のかなりのスペースが、むだにされます。
「attr2」は、inode のリテラル領域を最大限に使うために導入されました。di_forkoff は、inode の最後から始まり、属性が追加されるにつれてデータフォークの方に伸びていきます。拡張属性が使われるならば、attr2 を使うべきです。
以下の図は、2つのバージョンを比較します。
XFS は、ファイルの領域を、エクステント、つまり開始位置と長さ、を使って割り当てます。XFS のエクステントはさらに、ファイル内の論理的開始位置も示します。これにより、スパースファイル(つまり、ファイル内の「ホール」)が自動的にサポートされます。エクステントが事前確保されて書きこまれていない(未書き込みエクステント)であることを示すフラグもあります。
ファイルは、そのファイルの長さだけの連続したディスク領域が無いときなど、1つ以上のエクステントを持つことがあります。ファイルが伸びると、XFS の領域アロケーターは、領域を連続にしようと試み、エクステントをマージします。同じ AG に、1つ以上のファイルが同時に領域割り当てを行うと、エクステントがインタリーブするため、ファイルのエクステントが複数になることがあります。この影響は、XFS ドライバーで使用される領域アロケーターによって、大きく変わります。
エクステントは128ビットあって、以下のパックされたレイアウトを持ちます。
エクステントは、xfs_bmbt_rec_t 構造体で表され、ディスク上ではビッグエンディアン形式です。メモリ上のエクステントの管理は、xfs_bmbt_irec_t 構造体で表され、それは xfs_bmbt_rec_t の、アンパックされたバージョンです。
typedef struct xfs_bmbt_irec { xfs_fileoff_t br_startoff; xfs_fsblock_t br_startblock; xfs_filblks_t br_blockcount; xfs_exntst_t br_state; } xfs_bmbt_irec_t;
エクステントの br_state フィールドは、以下の enum 宣言を使います。
typedef enum { XFS_EXT_NORM, XFS_EXT_UNWRITTEN, XFS_EXT_INVALID } xfs_exntst_t;
エクステントについて、その他のこと
xfs_bmbt_rec_32_t と xfs_bmbt_rec_64_t s構造体は、実際には、 xfs_bmbt_rec_t と同じことで、同じ、ディスク上の128ビットのビッグエンディアン形式のものの、別の表示です。
ファイルが作成されて、書き込まれると、XFS はこれらのエクステントを、inode と同じ AG に置くように努力します。AG がビジーであったり、空きがないときには他の AG が使われることがあります。
ファイルが0バイトであるとき、エクステントがありません。di_nblocks と di_nexents はゼロになります。データのあるファイルはどれも、少なくとも1つのエクステントを持ち、それぞれのエクステントは1ブロック以上、200万ブロック(221) をファイルシステムで使うことができます。デフォルトの 4KB ブロックのファイルシステムでは、1つのエクステントの最大長は8GBです。
以下の2つのサブセクションで、ファイルのエクステント情報を格納する2つの方法について述べます。最初のは、高速で単純で、inode がファイルデータのエクステント配列を完全に含むというものです。もう1つのは、低速で複雑なB+ツリーであり、数千から数百万のエクステントを効率よく扱うことができます。
ローカルエクステントでは、inode のデータフォーク自身に、すべてのエクステント配列が格納されます。これが、スピードとリソース使用の点では最適と言えます。トレードオフとしては、inode がいっぱいになってしまうために、ファイルは数個のエクステントしか持てないという事です。
inode の「データフォーク」は、エクステントの配列を持ちます。配列の大きさは、inode の、di_nextents の値で決まります。
inode に入るエクステントの数は、inode 長とdi_forkoff で決まります。デフォルトの256バイト inode で、拡張属性がないときは、ファイルはこの形式だと19エクステントまで使えます。これを超えると、B+ツリー形式が必要です。
8MBのファイルで、エクステント1つ。
xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 0100644 core.version = 1 core.format = 2 (extents) ... core.size = 8294400 core.nblocks = 2025 core.extsize = 0 core.nextents = 1 core.naextents = 0 core.forkoff = 0 ... u.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,25356,2025,0]
24MBのファイルで、エクステント3つ。
xfs_db> inode <inode#> xfs_db> p ... core.format = 2 (extents) ... core.size = 24883200 core.nblocks = 6075 core.nextents = 3 ... u.bmx[0-2] = [startoff,startblock,blockcount,extentflag] 0:[0,27381,2025,0] 1:[2025,31431,2025,0] 2:[4050,35481,2025,0]
inode の生のディスクバージョン。3つめのエクステントがハイライトされています。(di_u は、常にオフセット 0x64 から始まります。)
ハイライトされた部分をさらに展開して、MSB から LSB の順のビット配列で見ます。ファイルオフセットとブロック数がハイライトされています。
4MBファイルで、2つのエクステント、真ん中にホール。最初のエクステントは、64KBのデータがあり、2つめは4MBオフセットあたりに、32KBのデータ。(write 64KB, lseek ~4MB, write 32KB 操作を順にしたわけです。)
xfs_db> inode <inode#> xfs_db> p ... core.format = 2 (extents) ... core.size = 4063232 core.nblocks = 24 core.nextents = 2 ... u.bmx[0-1] = [startoff,startblock,blockcount,extentflag] 0:[0,37506,16,0] 1:[984,37522,8,0]
単純なエクステント配列を超えて、大きなエクステントのマップを効率よく管理するために、XFS はB+ツリーを使います。B+ツリーのルートノードは、inode のデータフォークに格納されます。エクステントB+ツリーで使われるすべてのブロックポインターは64ビットの絶対ブロック番号です。
1レベルのB+ツリーででは、ルートノードはB+ツリーのリーフを指します。それぞれのリーフは1つのファイルシステムのブロックを占め、ヘッダーと、ファイル内のオフセットでソートされたエクステントの配列を持ちます。それぞれのリーフは隣接する左と右(後方と前方)ブロックポインターを持ちます。
マルチレベルのB+ツリーでは、ルートノードは他のB+ツリーノードを指し、それらが最終的にエクステントのリーフを指します。B+ツリーのキーは、ファイルのオフセットを元にします。B+ツリーのそれぞれのレベルのノードは、隣接するノードを指します。
ベースB+ツリーノードが、エクステント、ディレクトリ、そして拡張属性に使われます。inode のB+ツリールートに使われる構造体は、以下です。
typedef struct xfs_bmdr_block { __be16 bb_level; __be16 bb_numrecs; } xfs_bmdr_block_t; typedef struct xfs_bmbt_key { xfs_dfiloff_t br_startoff; } xfs_bmbt_key_t, xfs_bmdr_key_t; typedef xfs_dfsbno_t xfs_bmbt_ptr_t, xfs_bmdr_ptr_t;
ディスク上で、B+ツリーノードは、xfs_bmbr_block_t ヘッダーで始まり、xfs_bmbt_key_t 値の配列が続き、その後にxfs_bmbt_ptr_t 値の配列が続きます。両方の配列の長さはヘッダーの bb_numrecs にあります。
通常の256バイト inode にあるルートノードは最大19のキーとポインターの組を持つことができ、それを超えるとルートとリーフの間に新しいレベルのノードが追加されます。この数は、di_forkoff がゼロでないとき(拡張属性が inode で使われているとき)には、もっと小さくなります。
それ以外のB+ツリーのノードとリーフは、xfs_bmbt_block_t で表されます。
typedef struct xfs_btree_lblock xfs_bmbt_block_t; typedef struct xfs_btree_lblock { __be32 bb_magic; __be16 bb_level; __be16 bb_numrecs; __be64 bb_leftsib; __be64 bb_rightsib; } xfs_btree_lblock_t;
中間のノードでは、xfs_bmbt_block_t に続くデータは、ルートノードと同じです。xfs_bmbt_key_t 値の配列と、その後の xfs_bmbt_ptr_t 値の配列です。後者は、ブロックの真ん中へんから始まります。(4096バイトファイルシステムブロックの場合、オフセット 0x808 )
リーフでは、xfs_bmbt_block_t ヘッダーに続いて、xfs_bmbt_rec_t エクステント配列があります。
ノードとリーフの bb_magic 値は同じです。
#define XFS_BMAP_MAGIC 0x424d4150 /* 'BMAP' */
bb_level が、ノードが中間ノードかリーフかを示します。リーフはbb_level がゼロ、ノードは1以上です。
中間ノードは、リーフと同じように、標準の4KBファイルシステムブロック長の場合、最大で254のリーフブロックへのポインターを持つことができます。キーとポインターは、64ビットですから。
以下の図は、シングルレベルのエクステントB+ツリーを示します。
以下の図は、2レベルのエクステントB+ツリーを示します。
TODO:
ここでは v2 ディレクトリしか扱いません。 v1 ディレクトリは廃止されました。
「ディレクトリブロック」の長さは、スーパーブロック (「スーパーブロック」) のsb_dirblklog にあります。バイト単位の長さ=sb_blocksize * 2sb_dirblklog。例えば、sb_blocksize = 4096, sb_dirblklog = 2 ならば、ディレクトリブロック長は 16384 です。ディレクトリブロックは、sb_dirblklog で計算される倍数で常に確保されます。ディレクトリブロックは、65536 バイトを超えることはできません。
注記
注意:「ブロック」という語は、特にことわらなければ、このセクションではディレクトリブロックのことで、ファイルシステムブロックではありません。
すべてのディレクトリエントリーは以下の「データ」を持ちます。
エントリーの名前(1バイトの namelen と、その後の8ビット文字の配列で、NULL 終了を含まない name からなる、長さ付き文字列)
エントリーの絶対 inode 番号 (「inode 番号」) 、それは短形式ディレクトリでの特別な場合を除いて、常に64ビット(8バイト)です。
readdir コールの繰り返しに使う、offset もしくは tag 。
短形式でないすべてのディレクトリは、さらに、「leaf」と「freespace index」の2つの構造を持ちます。
Leaf は、ソートされた name のハッシュ値 (xfs_da_hashname() in xfs_da_btree.c) と、対応する「アドレス」、それはディレクトリのデータ構造内の実際のオフセットです、の組を持っています。Leaf は、検索操作を高速化するのに使われます。
Freespace index は、空き領域/空のエントリーの検索に使われ、新しいエントリーのために適当な長さの位置を素早く見つけることができます。それぞれの「データ」ブロックでの最長の空き領域を記憶します。
以下は、ディレクトリ構造体で使われる、一般的な型です。
typedef __uint16_t xfs_dir2_data_off_t; typedef __uint32_t xfs_dir2_dataptr_t;
ディレクトリエントリーは、 inode の中に格納されています。
名前、 inode 番号、そしてオフセットだけが格納されます。 inode にはごく少ないエントリーしか入らないので、「leaf」や「freespace index」は、必要ありません。
「.」は格納されません(それは、その inode にあるのですから)そして、「..」はヘッダーの専用の parent フィールドで表されます
inode に格納できるディレクトリエントリーの数は、 inode の大きさ(4章ディスク上inode )と、エントリーの数、エントリー名の長さ、そして拡張属性のデータに依存します。
エントリーの数が、 inode の領域を超えると、「ブロックディレクトリ」形式に変換されます。
短形式ディレクトリのデータは、ディスク上で、出来る限り詰めて配置され、残りはゼロで埋められます。
typedef struct xfs_dir2_sf { xfs_dir2_sf_hdr_t hdr; xfs_dir2_sf_entry_t list[1]; } xfs_dir2_sf_t; typedef struct xfs_dir2_sf_hdr { __uint8_t count; __uint8_t i8count; xfs_dir2_inou_t parent; } xfs_dir2_sf_hdr_t; typedef struct xfs_dir2_sf_entry { __uint8_t namelen; xfs_dir2_sf_off_t offset; __uint8_t name[1]; xfs_dir2_inou_t inumber; } xfs_dir2_sf_entry_t;
inode 番号は、そのディレクトリのすべての inode 番号が、4バイト(32ビット)に収まるかどうかで、4バイト、あるいは8バイトを使って格納されます。すべての inode 番号が4バイトに収まるときは、ヘッダーの count 値は、ディレクトリのエントリーの数を示し、 i8count はゼロになります。もし4バイトに収まらない inode 番号があると、すべての inode 番号は8バイトで表されて、ヘッダーの i8count の値がエントリーの数を示し、count の方はゼロになります。以下のユニオンは、短形式の inode 番号の構造を示します。
typedef struct { __uint8_t i[8]; } xfs_dir2_ino8_t; typedef struct { __uint8_t i[4]; } xfs_dir2_ino4_t; typedef union { xfs_dir2_ino8_t i8; xfs_dir2_ino4_t i4; } xfs_dir2_inou_t;
ディレクトリが作られ、4つのファイルがあります。すべての inode 番号は4バイトです。
xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 040755 core.version = 1 core.format = 1 (local) core.nlinkv1 = 2 ... core.size = 94 core.nblocks = 0 core.extsize = 0 core.nextents = 0 ... u.sfdir2.hdr.count = 4 u.sfdir2.hdr.i8count = 0 u.sfdir2.hdr.parent.i4 = 128 /* parent = root inode */ u.sfdir2.list[0].namelen = 15 u.sfdir2.list[0].offset = 0x30 u.sfdir2.list[0].name = "frame000000.tst" u.sfdir2.list[0].inumber.i4 = 25165953 u.sfdir2.list[1].namelen = 15 u.sfdir2.list[1].offset = 0x50 u.sfdir2.list[1].name = "frame000001.tst" u.sfdir2.list[1].inumber.i4 = 25165954 u.sfdir2.list[2].namelen = 15 u.sfdir2.list[2].offset = 0x70 u.sfdir2.list[2].name = "frame000002.tst" u.sfdir2.list[2].inumber.i4 = 25165955 u.sfdir2.list[3].namelen = 15 u.sfdir2.list[3].offset = 0x90 u.sfdir2.list[3].name = "frame000003.tst" u.sfdir2.list[3].inumber.i4 = 25165956
ディスク上の生データ。最初のエントリーがハイライトされています。最初のエントリーの前に、6バイトのヘッダーがあります。
次に、1つのエントリー(frame000001.tst)が削除されます。削除されたエントリーの後ろのすべてのエントリーは、ホールを「カバー」するように、移動あるいは詰め込まれます。
xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 040755 core.version = 1 core.format = 1 (local) core.nlinkv1 = 2 ... core.size = 72 core.nblocks = 0 core.extsize = 0 core.nextents = 0 ... u.sfdir2.hdr.count = 3 u.sfdir2.hdr.i8count = 0 u.sfdir2.hdr.parent.i4 = 128 u.sfdir2.list[0].namelen = 15 u.sfdir2.list[0].offset = 0x30 u.sfdir2.list[0].name = "frame000000.tst" u.sfdir2.list[0].inumber.i4 = 25165953 u.sfdir2.list[1].namelen = 15 u.sfdir2.list[1].offset = 0x70 u.sfdir2.list[1].name = "frame000002.tst" u.sfdir2.list[1].inumber.i4 = 25165955 u.sfdir2.list[2].namelen = 15 u.sfdir2.list[2].offset = 0x90 u.sfdir2.list[2].name = "frame000003.tst" u.sfdir2.list[2].inumber.i4 = 25165956
生のディスクデータ。短形式のエントリーの後ろの領域は不定で、ゼロでないことがあります。
xfs_db> type text xfs_db> p 00: 49 4e 41 ed 01 01 00 02 00 00 00 00 00 00 00 00 INA............. 10: 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 03 ................ 20: 44 b2 45 a2 09 fd e4 50 44 b2 45 a3 12 ee b5 d0 D.E....PD.E..... 30: 44 b2 45 a3 12 ee b5 d0 00 00 00 00 00 00 00 48 D.E............H 40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 50: 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 ................ 60: ff ff ff ff 03 00 00 00 00 80 0f 00 30 66 72 61 ............0fra 70: 6d 65 30 30 30 30 30 30 2e 74 73 74 01 80 00 81 me000000.tst.... 80: 0f 00 70 66 72 61 6d 65 30 30 30 30 30 32 2e 74 ..pframe000002.t 90: 73 74 01 80 00 83 0f 00 90 66 72 61 6d 65 30 30 st.......frame00 a0: 30 30 30 33 2e 74 73 74 01 80 00 84 0f 00 90 66 0003.tst.......f b0: 72 61 6d 65 30 30 30 30 30 33 2e 74 73 74 01 80 rame000003.tst.. c0: 00 84 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
TODO: 8-byte inode 番号の例
短形式のディレクトリ領域が inode の領域を超えると、ディレクトリデータは、 inode の外の新しい1つのディレクトリブロックに移動します。 inode の形式は、「ローカル」から「エクステント」になります。以下はブロックディレクトリの特徴です。
すべてのディレクトリデータは、1つのディレクトリブロックに格納されます。「.」と「..」エントリーも同じで、これらは必須です。
ブロックには、「leaf」と「freespace index 」情報もあります。
ブロックの位置は、 inode のコアにあるエクステントリスト(「エクステントリスト」)、di_u.u_bmx[0] の値、にあります。エクステント中のファイルオフセットは常にゼロであり、length = (directory block size / filesystem block size) です。ブロック番号は、ディレクトリデータを持つファイルシステムブロックを指しています。
ブロックディレクトリデータは、以下の構造体で示されます。
#define XFS_DIR2_DATA_FD_COUNT 3 typedef struct xfs_dir2_block { xfs_dir2_data_hdr_t hdr; xfs_dir2_data_union_t u[1]; xfs_dir2_leaf_entry_t leaf[1]; xfs_dir2_block_tail_t tail; } xfs_dir2_block_t; typedef struct xfs_dir2_data_hdr { __uint32_t magic; xfs_dir2_data_free_t bestfree[XFS_DIR2_DATA_FD_COUNT]; } xfs_dir2_data_hdr_t; typedef struct xfs_dir2_data_free { xfs_dir2_data_off_t offset; xfs_dir2_data_off_t length; } xfs_dir2_data_free_t; typedef union { xfs_dir2_data_entry_t entry; xfs_dir2_data_unused_t unused; } xfs_dir2_data_union_t; typedef struct xfs_dir2_data_entry { xfs_ino_t inumber; __uint8_t namelen; __uint8_t name[1]; xfs_dir2_data_off_t tag; } xfs_dir2_data_entry_t; typedef struct xfs_dir2_data_unused { __uint16_t freetag; /* 0xffff */ xfs_dir2_data_off_t length; xfs_dir2_data_off_t tag; } xfs_dir2_data_unused_t; typedef struct xfs_dir2_leaf_entry { xfs_dahash_t hashval; xfs_dir2_dataptr_t address; } xfs_dir2_leaf_entry_t; typedef struct xfs_dir2_block_tail { __uint32_t count; __uint32_t stale; } xfs_dir2_block_tail_t;
xfs_dir2_data_entry_t 構造体の tag は、ブロック先頭からのエントリーのオフセットを持ちます。
空き領域の開始は、freetag が 0xffff である xfs_dir2_data_unused_t で示されます。freetag と length は、そのエントリーの inumber を上書きします。 tag は、ディスク上の unused エントリーの開始点から、 length - sizeof(tag) のところを指します。
ヘッダーの bestfree 配列は、そのブロックに、新しいエントリーを格納するために使われ、最大の長さの空き領域から順に3つまでを、ソートして持ちます。空き領域が3つ未満の時は、残りの bestfree 要素はゼロです。offset は、ブロック先頭からの、バイト単位のオフセットを示し、length は空き領域のバイト単位の長さです。それぞれがポイントする位置には、前記の xfs_dir2_data_unused_t 構造体がなくてはいけません。ブロック長は64KBまでなので、それぞれは16ビットの値です。bestfree は、エントリーを作成するために必要な領域を探す時間を短くするために使われます。作成されるエントリーを置くのに十分な領域を、毎回、ブロック全体で探す処理をしないで済みます。
tail 構造体は、 leaf 配列にある要素の数と、配列の stale エントリーの数を示します。tail は、常にブロックの終端にあります。tail 構造体のすぐ前には、leaf データがあります。
leaf 配列は、ブロック終端の、tail 構造体のすぐ前から上に伸びていき、エントリー名をハッシュ値で高速に検索するためのハッシュ/アドレスの組の配列を持ちます。ハッシュ値は、ディレクトリの紹介のところで説明されます。ディスク上の address は、ブロック内のオフセットを、8(XFS_DIR2_DATA_ALIGN)で割ったものです。ハッシュ/アドレスの組は、大きなディレクトリでの検索を高速化するために、ディスク上に格納されます。それが格納されてないとすると、ディレクトリで検索があるたびに、すべてのエントリーについて、ハッシュ値を計算しなくてはいけません。
ディレクトリが、8エントリーで作成されました。ディレクトリブロック長=ファイルシステムブロック長。
xfs_db> sb 0 xfs_db> p magicnum = 0x58465342 blocksize = 4096 ... dirblklog = 0 ... xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 040755 core.version = 1 core.format = 2 (extents) core.nlinkv1 = 2 ... core.size = 4096 core.nblocks = 1 core.extsize = 0 core.nextents = 1 ... u.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,2097164,1,0]
「startblock」へ行って、生のディスクデータを見ます。
xfs_db> dblock 0 xfs_db> type text xfs_db> p 000: 58 44 32 42 01 30 0e 78 00 00 00 00 00 00 00 00 XD2B.0.x........ 010: 00 00 00 00 02 00 00 80 01 2e 00 00 00 00 00 10 ................ 020: 00 00 00 00 00 00 00 80 02 2e 2e 00 00 00 00 20 ................ 030: 00 00 00 00 02 00 00 81 0f 66 72 61 6d 65 30 30 .........frame00 040: 30 30 30 30 2e 74 73 74 80 8e 59 00 00 00 00 30 0000.tst..Y....0 050: 00 00 00 00 02 00 00 82 0f 66 72 61 6d 65 30 30 .........frame00 060: 30 30 30 31 2e 74 73 74 d0 ca 5c 00 00 00 00 50 0001.tst.......P 070: 00 00 00 00 02 00 00 83 0f 66 72 61 6d 65 30 30 .........frame00 080: 30 30 30 32 2e 74 73 74 00 00 00 00 00 00 00 70 0002.tst.......p 090: 00 00 00 00 02 00 00 84 0f 66 72 61 6d 65 30 30 .........frame00 0a0: 30 30 30 33 2e 74 73 74 00 00 00 00 00 00 00 90 0003.tst........ 0b0: 00 00 00 00 02 00 00 85 0f 66 72 61 6d 65 30 30 .........frame00 0c0: 30 30 30 34 2e 74 73 74 00 00 00 00 00 00 00 b0 0004.tst........ 0d0: 00 00 00 00 02 00 00 86 0f 66 72 61 6d 65 30 30 .........frame00 0e0: 30 30 30 35 2e 74 73 74 00 00 00 00 00 00 00 d0 0005.tst........ 0f0: 00 00 00 00 02 00 00 87 0f 66 72 61 6d 65 30 30 .........frame00 100: 30 30 30 36 2e 74 73 74 00 00 00 00 00 00 00 f0 0006.tst........ 110: 00 00 00 00 02 00 00 88 0f 66 72 61 6d 65 30 30 .........frame00 120: 30 30 30 37 2e 74 73 74 00 00 00 00 00 00 01 10 0007.tst........ 130: ff ff 0e 78 00 00 00 00 00 00 00 00 00 00 00 00 ...x............
「leaf」と「tail」構造体は、ブロックの終端に格納されます。このため、ディレクトリが大きくなると、真ん中が埋まっていきます。
fa0: 00 00 00 00 00 00 01 30 00 00 00 2e 00 00 00 02 .......0........ fb0: 00 00 17 2e 00 00 00 04 83 a0 40 b4 00 00 00 0e ................ fc0: 93 a0 40 b4 00 00 00 12 a3 a0 40 b4 00 00 00 06 ................ fd0: b3 a0 40 b4 00 00 00 0a c3 a0 40 b4 00 00 00 1e ................ fe0: d3 a0 40 b4 00 00 00 22 e3 a0 40 b4 00 00 00 16 ................ ff0: f3 a0 40 b4 00 00 00 1a 00 00 00 0a 00 00 00 00 ................
読みやすい形式では:
xfs_db> type dir2 xfs_db> p bhdr.magic = 0x58443242 bhdr.bestfree[0].offset = 0x130 bhdr.bestfree[0].length = 0xe78 bhdr.bestfree[1].offset = 0 bhdr.bestfree[1].length = 0 bhdr.bestfree[2].offset = 0 bhdr.bestfree[2].length = 0 bu[0].inumber = 33554560 bu[0].namelen = 1 bu[0].name = "." bu[0].tag = 0x10 bu[1].inumber = 128 bu[1].namelen = 2 bu[1].name = ".." bu[1].tag = 0x20 bu[2].inumber = 33554561 bu[2].namelen = 15 bu[2].name = "frame000000.tst" bu[2].tag = 0x30 bu[3].inumber = 33554562 bu[3].namelen = 15 bu[3].name = "frame000001.tst" bu[3].tag = 0x50 ... bu[8].inumber = 33554567 bu[8].namelen = 15 bu[8].name = "frame000006.tst" bu[8].tag = 0xf0 bu[9].inumber = 33554568 bu[9].namelen = 15 bu[9].name = "frame000007.tst" bu[9].tag = 0x110 bu[10].freetag = 0xffff bu[10].length = 0xe78 bu[10].tag = 0x130 bleaf[0].hashval = 0x2e bleaf[0].address = 0x2 bleaf[1].hashval = 0x172e bleaf[1].address = 0x4 bleaf[2].hashval = 0x83a040b4 bleaf[2].address = 0xe ... bleaf[8].hashval = 0xe3a040b4 bleaf[8].address = 0x16 bleaf[9].hashval = 0xf3a040b4 bleaf[9].address = 0x1a btail.count = 10 btail.stale = 0
ブロックディレクトリでは、xfs_db で表示されるフィールドは、すべて「b」でプレフィクスされます。
簡単な検索の例として、frame000000.tst のハッシュ値は 0xa3a040b4 です。訳注:原文は 0xb3a040b4 ですが、誤りです。その値を探すと、アドレス 0x6 であることがわかります。8倍して、オフセット 0x30 になり、そこにある inode は、33554561 です。
中間のエントリー (frame000004.tst) を削除すると、空き領域の詳細がどう変化するか、見ることができます。
bhdr.magic = 0x58443242 bhdr.bestfree[0].offset = 0x130 bhdr.bestfree[0].length = 0xe78 bhdr.bestfree[1].offset = 0xb0 bhdr.bestfree[1].length = 0x20 bhdr.bestfree[2].offset = 0 bhdr.bestfree[2].length = 0 ... bu[5].inumber = 33554564 bu[5].namelen = 15 bu[5].name = "frame000003.tst" bu[5].tag = 0x90 bu[6].freetag = 0xffff bu[6].length = 0x20 bu[6].tag = 0xb0 bu[7].inumber = 33554566 bu[7].namelen = 15 bu[7].name = "frame000005.tst" bu[7].tag = 0xd0 ... bleaf[7].hashval = 0xd3a040b4 bleaf[7].address = 0x22 bleaf[8].hashval = 0xe3a040b4 bleaf[8].address = 0 bleaf[9].hashval = 0xf3a040b4 bleaf[9].address = 0x1a btail.count = 10 btail.stale = 1
そのエントリーを指す新しい「bestfree」値が、追加されます。エントリーの開始位置には、 0xffff が書かれて、未使用とされます。(それにより、元のエントリーの inode 番号が上書きされます。)空き領域の長さが変化します。タグは、offset+length - sizeof(tag) のままで変わりません。ハッシュの指すアドレスもクリアされます。以下で、変更のあった部分はハイライトされています。
ブロックディレクトリ (「ブロックディレクトリ」) がブロックいっぱいになると、ディレクトリデータは、新しい形式に変換されます。それは、やはりエクステント (5章データエクステント) を使い、基本的な構造は同じですが、「data」と「leaf」がそれぞれのエクステントを持つように分割されます。「leaf」情報は1エクステントだけを占めます。「leaf」情報は、「data」情報よりずっとコンパクトです。一方、「data」エクステントは、1つ以上ある時が多いです。
ブロックからリーフへの変換は、データエントリーが今使っているブロックはそのままにして、リーフと空きインデックス情報のために、新しいブロックを1つ確保します。
すべてのディレクトリと同じく、データブロックは論理オフセットゼロから始まらなくてはいけません。
「leaf」ブロックは、XFS_DIR2_LEAF_OFFSET で定義される特別なオフセットを持ちます。現在、これは32GBで、エクステントで表すと、32GB/sb_blocksize のブロックオフセットで表示されます。4KBブロックのファイルシステムでは、これは 0x800000 (8388608 decimal) です。
「data」エクステントは、新しいヘッダーを持ちます。(「leaf」データはありません。)
typedef struct xfs_dir2_data { xfs_dir2_data_hdr_t hdr; xfs_dir2_data_union_t u[1]; } xfs_dir2_data_t;
「leaf」エクステントは、以下の構造体を使います。
typedef struct xfs_dir2_leaf { xfs_dir2_leaf_hdr_t hdr; xfs_dir2_leaf_entry_t ents[1]; xfs_dir2_data_off_t bests[1]; xfs_dir2_leaf_tail_t tail; } xfs_dir2_leaf_t; typedef struct xfs_dir2_leaf_hdr { xfs_da_blkinfo_t info; __uint16_t count; __uint16_t stale; } xfs_dir2_leaf_hdr_t; typedef struct xfs_dir2_leaf_tail { __uint32_t bestcount; } xfs_dir2_leaf_tail_t;
リーフは、xfs_da_blkinfo_t ファイルシステムブロックヘッダーを持ちます。このヘッダーはディレクトリと拡張属性(8章拡張属性) の、リーフとB+ツリーノードで使われます。
typedef struct xfs_da_blkinfo { __be32 forw; __be32 back; __be16 magic; __be16 pad; } xfs_da_blkinfo_t;
ents 配列の大きさは、hdr.count にあります。
bests 配列の大きさは、tail.bestcount にあり、それはディレクトリの「data」ブロックの数でもあります。bests 配列は、それぞれのデータブロックの bestfree[0].length の価を持ちます。
この例では、256エントリー (frame000000.tst to frame000255.tst) を持つディレクトリが作成されました。そして、いくつかのファイル (frame00005*, frame00018* そして frame000240.tst) を削除して、空きリストの様子を見られるようにしました。
xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 040755 core.version = 1 core.format = 2 (extents) core.nlinkv1 = 2 ... core.size = 12288 core.nblocks = 4 core.extsize = 0 core.nextents = 3 ... u.bmx[0-2] = [startoff,startblock,blockcount,extentflag] 0:[0,4718604,1,0] 1:[1,4718610,2,0] 2:[8388608,4718605,1,0]
この例でわかるように、「data」のある2つのエクステントのために、3ブロックが使われ、「leaf」エクステントは、論理オフセット 8388608 ブロック (32GB) を持ちます。
最初のブロックを見ます。
xfs_db> dblock 0 xfs_db> type dir2 xfs_db> p dhdr.magic = 0x58443244 dhdr.bestfree[0].offset = 0x670 dhdr.bestfree[0].length = 0x140 dhdr.bestfree[1].offset = 0xff0 dhdr.bestfree[1].length = 0x10 dhdr.bestfree[2].offset = 0 dhdr.bestfree[2].length = 0 du[0].inumber = 75497600 du[0].namelen = 1 du[0].name = "." du[0].tag = 0x10 du[1].inumber = 128 du[1].namelen = 2 du[1].name = ".." du[1].tag = 0x20 du[2].inumber = 75497601 du[2].namelen = 15 du[2].name = "frame000000.tst" du[2].tag = 0x30 du[3].inumber = 75497602 du[3].namelen = 15 du[3].name = "frame000001.tst" du[3].tag = 0x50 ... du[51].inumber = 75497650 du[51].namelen = 15 du[51].name = "frame000049.tst" du[51].tag = 0x650 du[52].freetag = 0xffff du[52].length = 0x140 du[52].tag = 0x670 du[53].inumber = 75497661 du[53].namelen = 15 du[53].name = "frame000060.tst" du[53].tag = 0x7b0 ... du[118].inumber = 75497758 du[118].namelen = 15 du[118].name = "frame000125.tst" du[118].tag = 0xfd0 du[119].freetag = 0xffff du[119].length = 0x10 du[119].tag = 0xff0
xfs_db のフィールド名は、「data」を表す 「d」でプレフィクスされます。
次の「データ」ブロック:
xfs_db> dblock 1 xfs_db> type dir2 xfs_db> p dhdr.magic = 0x58443244 dhdr.bestfree[0].offset = 0x6d0 dhdr.bestfree[0].length = 0x140 dhdr.bestfree[1].offset = 0xe50 dhdr.bestfree[1].length = 0x20 dhdr.bestfree[2].offset = 0xff0 dhdr.bestfree[2].length = 0x10 du[0].inumber = 75497759 du[0].namelen = 15 du[0].name = "frame000126.tst" du[0].tag = 0x10 ... du[53].inumber = 75497844 du[53].namelen = 15 du[53].name = "frame000179.tst" du[53].tag = 0x6b0 du[54].freetag = 0xffff du[54].length = 0x140 du[54].tag = 0x6d0 du[55].inumber = 75497855 du[55].namelen = 15 du[55].name = "frame000190.tst" du[55].tag = 0x810 ... du[104].inumber = 75497904 du[104].namelen = 15 du[104].name = "frame000239.tst" du[104].tag = 0xe30 du[105].freetag = 0xffff du[105].length = 0x20 du[105].tag = 0xe50 du[106].inumber = 75497906 du[106].namelen = 15 du[106].name = "frame000241.tst" du[106].tag = 0xe70 ... du[117].inumber = 75497917 du[117].namelen = 15 du[117].name = "frame000252.tst" du[117].tag = 0xfd0 du[118].freetag = 0xffff du[118].length = 0x10 du[118].tag = 0xff0
そして、最後のデータブロック:
xfs_db> dblock 2 xfs_db> type dir2 xfs_db> p dhdr.magic = 0x58443244 dhdr.bestfree[0].offset = 0x70 dhdr.bestfree[0].length = 0xf90 dhdr.bestfree[1].offset = 0 dhdr.bestfree[1].length = 0 dhdr.bestfree[2].offset = 0 dhdr.bestfree[2].length = 0 du[0].inumber = 75497918 du[0].namelen = 15 du[0].name = "frame000253.tst" du[0].tag = 0x10 du[1].inumber = 75497919 du[1].namelen = 15 du[1].name = "frame000254.tst" du[1].tag = 0x30 du[2].inumber = 75497920 du[2].namelen = 15 du[2].name = "frame000255.tst" du[2].tag = 0x50 du[3].freetag = 0xffff du[3].length = 0xf90 du[3].tag = 0x70
「leaf」ブロックを見てみましょう。(「leaf」ですから、フィールドは「l」でプレフィクスされます。)
いくつか、エントリーを削除する前のディレクトリ:
xfs_db> dblock 8388608 xfs_db> type dir2 xfs_db> p lhdr.info.forw = 0 lhdr.info.back = 0 lhdr.info.magic = 0xd2f1 lhdr.count = 258 lhdr.stale = 0 lbests[0-2] = 0:0x10 1:0x10 2:0xf90 lents[0].hashval = 0x2e lents[0].address = 0x2 lents[1].hashval = 0x172e lents[1].address = 0x4 lents[2].hashval = 0x23a04084 lents[2].address = 0x116 ... lents[257].hashval = 0xf3a048bc lents[257].address = 0x366 ltail.bestcount = 3
lbests 配列が、「data」ブロックの bestfree[0].length の価とどう関連するか、注意して下さい。
xfs_db> dblock 0 xfs_db> type dir2 xfs_db> p dhdr.magic = 0x58443244 dhdr.bestfree[0].offset = 0xff0 dhdr.bestfree[0].length = 0x10 ... xfs_db> dblock 1 xfs_db> type dir2 xfs_db> p dhdr.magic = 0x58443244 dhdr.bestfree[0].offset = 0xff0 dhdr.bestfree[0].length = 0x10 ... xfs_db> dblock 2 xfs_db> type dir2 xfs_db> p dhdr.magic = 0x58443244 dhdr.bestfree[0].offset = 0x70 dhdr.bestfree[0].length = 0xf90
次に、エントリーが削除された後:
xfs_db> dblock 8388608 xfs_db> type dir2 xfs_db> p lhdr.info.forw = 0 lhdr.info.back = 0 lhdr.info.magic = 0xd2f1 lhdr.count = 258 lhdr.stale = 21 lbests[0-2] = 0:0x140 1:0x140 2:0xf90 lents[0].hashval = 0x2e lents[0].address = 0x2 lents[1].hashval = 0x172e lents[1].address = 0x4 lents[2].hashval = 0x23a04084 lents[2].address = 0x116 ...
ご覧になってわかるように、lbests の値は、それぞれの hdr.bestfree[0].length になるように更新されました。リーフの hdr.stale の値の方も、配列の無効エントリーの数になるように更新されました。無効エントリーは、アドレスゼロを持ちます。
TODO: 大きな空き領域がいくつかあるときに、新しいエントリーがどこに作られるかという例が必要。
「リーフ」情報が1ブロックを超えると、エクステントはさらに分割されます。すべての「freeindex」情報は、それ単独のエクステントに移動します。リーフディレクトリ (「リーフディレクトリ」) と同じように、「リーフ」ブロックはそれぞれの「data」ブロックについての best free space 情報を管理していますが、リーフが1以上になると、それはできなくなります。
「データ」ブロックは、リーフディレクトリの時と同じです。
「leaf」ブロックは、最後には、B+ツリーになり、汎用のB+ツリーヘッダーが、リーフディレクトリのところで述べたように、ディレクトリ「leaf」を指します。上のレベルのブロックは「ノード」と呼ばれます。スプリットの前に、1つのリーフフロックがあるだけという状態になることもあります。ノードとリーフのブロックの区別は、ヘッダーのマジック番号を見て行います。リーフと空きインデックスが混在するブロックのマジック番号は XFS_DIR2_LEAF1_MAGIC (0xd2f1)、ノードディレクトリのリーフは、 XFS_DIR2_LEAFN_MAGIC (0xd2ff) そして、中間ノードは、 XFS_DA_NODE_MAGIC (0xfebe) です。
新しい、「freeindex」ブロックは、それぞれのデータブロックにある bestfree だけを持ちます。
空きインデックスブロックは、以下の構造体で表されます。
typedef struct xfs_dir2_free_hdr { __uint32_t magic; __int32_t firstdb; __int32_t nvalid; __int32_t nused; } xfs_dir2_free_hdr_t; typedef struct xfs_dir2_free { xfs_dir2_free_hdr_t hdr; xfs_dir2_data_off_t bests[1]; } xfs_dir2_free_t;
リーフブロックの位置は、どんな順でも構いません。正しい位置を得る唯一の方法は、ノードブロックの、ハッシュと before の値を使うことです。検索目的のハッシュ値を計算したら、ノードの btree 配列を読みます。そして、配列のうち、目的のハッシュ値を超える最初の hashval を探せば、目的エントリーは、その before の指すブロックにあります。
typedef struct xfs_da_intnode { struct xfs_da_node_hdr { xfs_da_blkinfo_t info; __uint16_t count; __uint16_t level; } hdr; struct xfs_da_node_entry { xfs_dahash_t hashval; xfs_dablk_t before; } btree[1]; } xfs_da_intnode_t;
空きインデックスの bests 配列は、ブロック終端から始まって、先頭に向かって伸びます。
データブロックが未使用(つまり、その中のすべてのエントリーが削除された)になったら、ブロックは解放されます。データエクステントはホールとなり、空きインデックスの hdr.nused の値は−1され、対応する bests[] エントリーは 0xffff に設定されます。
最初のデータブロックは、必ず「.」と「..」を持つので、ディレクトリが最初にホールを持つのはありえないことです。
空きインデックスの hdr.nvalid は、 name/inode を保持するために確保されたデータディレクトリブロックの数に等しいはずです。また、それは hdr.nused 以下のはずです。hdr.nused は、最後のデータディレクトリブロックのインデックス+1に等しいはずです。(つまり、最後のデータブロックが解放されると、nused と nvalid は−1されます。)
以下のノードディレクトリの例では、ファイルシステムは 4KB ブロック長で、16KB ディレクトリ長とします。ディレクトリには2000以上のエントリーがあります。
xfs_db> sb 0 xfs_db> p magicnum = 0x58465342 blocksize = 4096 ... dirblklog = 2 ... xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 040755 core.version = 1 core.format = 2 (extents) ... core.size = 81920 core.nblocks = 36 core.extsize = 0 core.nextents = 8 ... u.bmx[0-7] = [startoff,startblock,blockcount,extentflag] 0:[0,7368,4,0] 1:[4,7408,4,0] 2:[8,7444,4,0] 3:[12,7480,4,0] 4:[16,7520,4,0] 5:[8388608,7396,4,0] 6:[8388612,7524,8,0] 7:[16777216,7516,4,0]
見てお分かりのように、すべてのエクステントは、4ブロック単位に確保されます。
ブロック0から19(16+4−1)はデータのために使われています。ブロック16−19を見ると、シングルリーフ形式と同じですが、 length の値は、大きくなったディレクトリブロック長に従って、ずっと大きくなっています。
xfs_db> dblock 16 xfs_db> type dir2 xfs_db> p dhdr.magic = 0x58443244 dhdr.bestfree[0].offset = 0xb0 dhdr.bestfree[0].length = 0x3f50 dhdr.bestfree[1].offset = 0 dhdr.bestfree[1].length = 0 dhdr.bestfree[2].offset = 0 dhdr.bestfree[2].length = 0 du[0].inumber = 120224 du[0].namelen = 15 du[0].name = "frame002043.tst" du[0].tag = 0x10 du[1].inumber = 120225 du[1].namelen = 15 du[1].name = "frame002044.tst" du[1].tag = 0x30 du[2].inumber = 120226 du[2].namelen = 15 du[2].name = "frame002045.tst" du[2].tag = 0x50 du[3].inumber = 120227 du[3].namelen = 15 du[3].name = "frame002046.tst" du[3].tag = 0x70 du[4].inumber = 120228 du[4].namelen = 15 du[4].name = "frame002047.tst" du[4].tag = 0x90 du[5].freetag = 0xffff du[5].length = 0x3f50 du[5].tag = 0
次に、「ノード」ブロック。フィールドは、ノードブロックを示す 'n' でプレフィクスされます。
xfs_db> dblock 8388608 xfs_db> type dir2 xfs_db> p nhdr.info.forw = 0 nhdr.info.back = 0 nhdr.info.magic = 0xfebe nhdr.count = 2 nhdr.level = 1 nbtree[0-1] = [hashval,before] 0:[0xa3a440ac,8388616] 1:[0xf3a440bc,8388612]
以下のリーフブロックは、一度に取られたものです。XFS は、B+ツリーを確保するとき、2ブロック以上が必要だと判断したためです。このため、8ファイルシステムブロックの長さがあります。0xa3a440ac 未満のすべてのハッシュ値はディレクトリオフセット 8388616 にあり、0xf3a440bc 未満のハッシュ値は、オフセット 8388612 にあります。0xf3a440bc 以上のハッシュ値は、このディレクトリにはありません。
xfs_db> dblock 8388616 xfs_db> type dir2 xfs_db> p lhdr.info.forw = 8388612 lhdr.info.back = 0 lhdr.info.magic = 0xd2ff lhdr.count = 1023 lhdr.stale = 0 lents[0].hashval = 0x2e lents[0].address = 0x2 lents[1].hashval = 0x172e lents[1].address = 0x4 lents[2].hashval = 0x23a04084 lents[2].address = 0x116 ... lents[1021].hashval = 0xa3a440a4 lents[1021].address = 0x1fa2 lents[1022].hashval = 0xa3a440ac lents[1022].address = 0x1fca xfs_db> dblock 8388612 xfs_db> type dir2 xfs_db> p lhdr.info.forw = 0 lhdr.info.back = 8388616 lhdr.info.magic = 0xd2ff lhdr.count = 1027 lhdr.stale = 0 lents[0].hashval = 0xa3a440b4 lents[0].address = 0x1f52 lents[1].hashval = 0xa3a440bc lents[1].address = 0x1f7a ... lents[1025].hashval = 0xf3a440b4 lents[1025].address = 0x1f66 lents[1026].hashval = 0xf3a440bc lents[1026].address = 0x1f8e
xfs_db で見る、検索例。
xfs_db> hash frame001845.tst 0xf3a26094 Doing a binary search through the array, we get address 0x1ce6, which is offset 0xe730. Each fsblock is 4KB in size (0x1000), so it will be offset 0x730 into directory offset 14. From the extent map, this will be fsblock 7482: xfs_db> fsblock 7482 xfs_db> type text xfs_db> p ... 730: 00 00 00 00 00 01 d4 da 0f 66 72 61 6d 65 30 30 .........frame00 740: 31 38 34 35 2e 74 73 74 00 00 00 00 00 00 27 30 1845.tst.......0
空きインデックス情報を見ます。(「f」タグがついているフィールドです。)
xfs_db> fsblock 7516 xfs_db> type dir2 xfs_db> p fhdr.magic = 0x58443246 fhdr.firstdb = 0 fhdr.nvalid = 5 fhdr.nused = 5 fbests[0-4] = 0:0x10 1:0x10 2:0x10 3:0x10 4:0x3f50
リーフディレクトリ (「リーフディレクトリ」)と同じように、それぞれの fbests 値は、それぞれのデータブロックの bestfree[0].length の値に対応します。
生のディスクレイアウト。配列の後の古いデータは、クリアされていません。fbests 配列がハイライトされています。
TODO: 中間にホールのある例
inode 内のエクステントマップが inode の領域より大きくなると、 inode 形式は「btree」に変わります。 inode は、そのディレクトリブロックのB+ツリーエクステントマップのルートノードのファイルシステムブロックを持ちます。B+ツリーエクステントには、「データ」、「ノード」、「リーフ」そして「空きインデックス」のエクステントマップがあります。それらは、ノードディレクトリ (「ノードディレクトリ」) で述べたものと同じです。
XFS B+ツリーエクステントについて詳しくは、以前のB+ツリーデータエクステント (「B+ツリーエクステントリスト」) の節を参照下さい。
inode エクステントは一般的には、B+ツリーほどの多数のディレクトリブロックを保持できないため、以下のことが言えます。
ノード/リーフツリーは、1レベル以上の深さがあります。
1つ以上の空きインデックスブロックは可能ですが、とても稀でしょう。2つめの空きインデックスブロックを必要とするには、数百、数千の、とても長いファイル名を持ったファイルがなくてはいけません。
100文字の長さのエントリーを、200,000個、持つディレクトリを作りました。ファイルシステムブロックとディレクトリブロック長は4KBです。
xfs_db> inode 772 xfs_db> p core.magic = 0x494e core.mode = 040755 core.version = 1 core.format = 3 (btree) ... core.size = 22757376 core.nblocks = 6145 core.extsize = 0 core.nextents = 234 core.naextents = 0 core.forkoff = 0 ... u.bmbt.level = 1 u.bmbt.numrecs = 1 u.bmbt.keys[1] = [startoff] 1:[0] u.bmbt.ptrs[1] = 1:89 xfs_db> fsblock 89 xfs_db> type bmapbtd xfs_db> p magic = 0x424d4150 level = 0 numrecs = 234 leftsib = null rightsib = null recs[1-234] = [startoff,startblock,blockcount,extentflag] 1:[0,53,1,0] 2:[1,55,13,0] 3:[14,69,1,0] 4:[15,72,13,0] 5:[28,86,2,0] 6:[30,90,21,0] 7:[51,112,1,0] 8:[52,114,11,0] ... 125:[5177,902,15,0] 126:[5192,918,6,0] 127:[5198,524786,358,0] 128:[8388608,54,1,0] 129:[8388609,70,2,0] 130:[8388611,85,1,0] ... 229:[8389164,917,1,0] 230:[8389165,924,19,0] 231:[8389184,944,9,0] 232:[16777216,68,1,0] 233:[16777217,7340114,1,0] 234:[16777218,5767362,1,0]
名前と inode の組を格納するために、128エクステントと、合計5555ブロックを使いました。1つの空きインデックスブロックには、2000個程度しか入らないので、このために3ブロックを使いました。firstdb フィールドは、それぞれの配列の開始ディレクトリブロック番号を持ちます。
xfs_db> dblock 16777216 xfs_db> type dir2 xfs_db> p fhdr.magic = 0x58443246 fhdr.firstdb = 0 fhdr.nvalid = 2040 fhdr.nused = 2040 fbests[0-2039] = ... xfs_db> dblock 16777217 xfs_db> type dir2 xfs_db> p fhdr.magic = 0x58443246 fhdr.firstdb = 2040 fhdr.nvalid = 2040 fhdr.nused = 2040 fbests[0-2039] = ... xfs_db> dblock 16777218 xfs_db> type dir2 xfs_db> p fhdr.magic = 0x58443246 fhdr.firstdb = 4080 fhdr.nvalid = 1476 fhdr.nused = 1476 fbests[0-1475] = ...
ノードブロックのルートノードを見てお分かりのように、これはとても深いツリーです。
xfs_db> dblock 8388608 xfs_db> type dir2 xfs_db> p nhdr.info.forw = 0 nhdr.info.back = 0 nhdr.info.magic = 0xfebe nhdr.count = 2 nhdr.level = 2 nbtree[0-1] = [hashval,before] 0:[0x6bbf6f39,8389121] 1:[0xfbbf7f79,8389120] xfs_db> dblock 8389121 xfs_db> type dir2 xfs_db> p nhdr.info.forw = 8389120 nhdr.info.back = 0 nhdr.info.magic = 0xfebe nhdr.count = 263 nhdr.level = 1 nbtree[0-262] = ... 262:[0x6bbf6f39,8388928] xfs_db> dblock 8389120 xfs_db> type dir2 xfs_db> p nhdr.info.forw = 0 nhdr.info.back = 8389121 nhdr.info.magic = 0xfebe nhdr.count = 319 nhdr.level = 1 nbtree[0-318] = [hashval,before] 0:[0x70b14711,8388919] ...
ノードの最後のリーフは、常に、隣接する次のノードの最後のリーフを指します。ディレクトリブロック 8388928 の前方ポインターはブロック 8388919 で、逆も同じ事です。以下の例でハイライトされています。
ファイルへのシンボリックリンクは、以下の2つの形式のいずれかで格納されます。「ローカル」あるいは「エクステント」です。シンボリックリンクの長さは、 inode の di_size の値で示されます。
シンボリックリンクが inode のデータフォークに入るならば、それは「ローカル」di_format で格納されます。リンクデータは、文字の配列です。(データフォークのユニオンにある、di_symlink の長さの配列。)
ファイルへの短形式のシンボリックリンクが作成されました。
xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 0120777 core.version = 1 core.format = 1 (local) ... core.size = 12 core.nblocks = 0 core.extsize = 0 core.nextents = 0 ... u.symlink = "small_target"
生のディスクデータで、リンクの内容がハイライトされています。
シンボリックリンクの長さが inode のデータフォークの大きさを超えると、リンクは別の新しいファイルシステムブロックに移り、 inode の di_format は、「エクステント」になります。ブロックの位置は、データフォークの di_bmx[] 配列で示されます。シンボリックリンクは1024文字を超えることができないので、普通の場合、1ファイルシステムブロックだけが使われます。
長いリンクが作成されました。(156バイト以上)
xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 0120777 core.version = 1 core.format = 2 (extents) ... core.size = 182 core.nblocks = 1 core.extsize = 0 core.nextents = 1 ... u.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,37530,1,0] xfs_db> dblock 0 xfs_db> type symlink xfs_db> p "symlink contents..."
拡張属性は、ユーザーが、XFS ファイルシステムの inode に、名前:値の組を関連つけることを可能とします。それは、ファイルのメタ情報を格納するのに使うことができます。
属性名は、256バイトまでの長さで、最初に現れる0バイトで終端されます。属性名は、印字可能な ASCII (あるいは他の文字セット)名であるのが普通です。値は、任意の64KBまでのバイナリーデータが可能です。XFS の内部的な属性(例えば、親へのポインター)は、属性名が印字不可能なものもあります。
アクセス制御リスト(ACL)と、データ移行機能(DMF)は、そのために必要なメタデータを inode に格納するために、拡張属性を使います。
XFS では、すべての inode は、2つの独立した属性のネームスペースを持ちます。それは、ルートとユーザーアドレススペースです。ルートアドレススペースは、スーパーユーザーからだけアクセス可能で、関数コールに特別なフラグを指定する必要があります。他のユーザーは、ルートアドレススペースにある属性を見たり変更したりすることはできません。ユーザーアドレススペースは、通常のファイルパーミッション機構で保護されていて、ファイルの所有者は、誰がそのファイルの属性を見たり変更したりできるかを決めることができます。
コマンドラインから、拡張属性を見るには、 getfattr コマンドを使います。拡張属性を設定したり削除したりするには、 setfattr コマンドを使います。ACL の制御は getfacl と setfacl コマンドで行います。
XFS の属性は、3つのネームスペースをサポートします。「ユーザー」、「トラステッド」(あるいは、IRIX の用語を使えば、「ルート」)そして、「セキュア」です。
inode のリテラル領域での属性フォークの位置は、 inode core の di_forkoff の値で決まります。この値がゼロの時は、inode は拡張属性を持ちません。ゼロ以外の場合は、リテラル領域でのバイトオフセット=di_forkoff * 8 です。それにより、inode の最大値も、2048バイトと制限されます。属性は、ディスク上では、短形式の属性(これは、可能な限り詰めて置かれます。)を除いて、64ビット境界に置かれなければいけません。inode 先頭からのオフセットを得るには、di_forkoff * 8 に、さらに100(0x64)を加えて下さい。
以下の4つのセクションは、ディスク上のそれぞれのフォーマットを説明します。
すべての拡張属性が、inode の属性フォークに入ってしまう場合、inode の di_aformat は「ローカル」に設定され、属性は、オフセット di_forkoff * 8 から始まる inode のリテラル領域に格納されます。
短形式は、以下の構造体を使います。
typedef struct xfs_attr_shortform { struct xfs_attr_sf_hdr { __be16 totsize; __u8 count; } hdr; struct xfs_attr_sf_entry { __uint8_t namelen; __uint8_t valuelen; __uint8_t flags; __uint8_t nameval[1]; } list[1]; } xfs_attr_shortform_t; typedef struct xfs_attr_sf_hdr xfs_attr_sf_hdr_t; typedef struct xfs_attr_sf_entry xfs_attr_sf_entry_t;
namelen とvaluelen は、名前と値の組を持っている2つのバイト配列のそれぞれの長さを示します。値の無い拡張属性の valuelen はゼロです。
nameval[]は、1つの配列で、その長さは、 namelen と valuelen の和です。名前と値は、ディスク上でヌル終了されていません。配列内で、値は名前のすぐ後に続きます。
flags は、属性のネームスペースです。(0 = "user")
フラグ
XFS_ATTR_ROOT
XFS_ATTR_SECURE
説明
属性のネームスペースは、「trusted」です。
属性のネームスペースは、「secure」です。
ファイルが作成され、2つの属性が設定されました。
# setfattr -n user.empty few_attr # setfattr -n trusted.trust -v val1 few_attr
xfs_db を使って、 inode をダンプしましょう。
xfs_db> inode <inode#> xfs_db> p core.magic = 0x494e core.mode = 0100644 ... core.naextents = 0 core.forkoff = 15 core.aformat = 1 (local) ... a.sfattr.hdr.totsize = 24 a.sfattr.hdr.count = 2 a.sfattr.list[0].namelen = 5 a.sfattr.list[0].valuelen = 0 a.sfattr.list[0].root = 0 a.sfattr.list[0].secure = 0 a.sfattr.list[0].name = "empty" a.sfattr.list[1].namelen = 5 a.sfattr.list[1].valuelen = 4 a.sfattr.list[1].root = 1 a.sfattr.list[1].secure = 0 a.sfattr.list[1].name = "trust" a.sfattr.list[1].value = "val1"
実際の inode オフセットは、220 (15 x 8 + 100) あるいは 0xdc であることがわかります。
生のダンプを見ます。2つめの属性がハイライトされています。
attr1 環境で、もうひとつ属性を追加しました。エクステント形式になり、 di_forkoff は変わりません。(ダンプで見えるゼロの部分は使われていません。)
xfs_db> inode <inode#> xfs_db> p ... core.naextents = 1 core.forkoff = 15 core.aformat = 2 (extents) ... a.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,37534,1,0]
attr2 環境で同じ事をします。属性が加えられるたびに、di_forkoff が変わっていくのがわかるでしょう。
xfs_db> inode <inode#> xfs_db> p ... core.naextents = 0 core.forkoff = 15 core.aformat = 1 (local) ... a.sfattr.hdr.totsize = 17 a.sfattr.hdr.count = 1 a.sfattr.list[0].namelen = 10 a.sfattr.list[0].valuelen = 0 a.sfattr.list[0].root = 0 a.sfattr.list[0].secure = 0 a.sfattr.list[0].name = "empty_attr"
属性が追加されました。
xfs_db> p ... core.naextents = 0 core.forkoff = 15 core.aformat = 1 (local) ... a.sfattr.hdr.totsize = 31 a.sfattr.hdr.count = 2 a.sfattr.list[0].namelen = 10 a.sfattr.list[0].valuelen = 0 a.sfattr.list[0].root = 0 a.sfattr.list[0].secure = 0 a.sfattr.list[0].name = "empty_attr" a.sfattr.list[1].namelen = 7 a.sfattr.list[1].valuelen = 4 a.sfattr.list[1].root = 1 a.sfattr.list[1].secure = 0 a.sfattr.list[1].name = "trust_a" a.sfattr.list[1].value = "val1"
もうひとつ属性が追加されました。
さらにもうひとつ。
xfs_db> p core.naextents = 0 core.forkoff = 10 core.aformat = 1 (local) ... a.sfattr.hdr.totsize = 69 a.sfattr.hdr.count = 4 a.sfattr.list[0].namelen = 10 a.sfattr.list[0].valuelen = 0 a.sfattr.list[0].root = 0 a.sfattr.list[0].secure = 0 a.sfattr.list[0].name = "empty_attr" a.sfattr.list[1].namelen = 7 a.sfattr.list[1].valuelen = 4 a.sfattr.list[1].root = 1 a.sfattr.list[1].secure = 0 a.sfattr.list[1].name = "trust_a" a.sfattr.list[1].value = "val1" a.sfattr.list[2].namelen = 6 a.sfattr.list[2].valuelen = 12 a.sfattr.list[2].root = 0 a.sfattr.list[2].secure = 0 a.sfattr.list[2].name = "second" a.sfattr.list[2].value = "second_value" a.sfattr.list[3].namelen = 6 a.sfattr.list[3].valuelen = 8 a.sfattr.list[3].root = 0 a.sfattr.list[3].secure = 1 a.sfattr.list[3].name = "policy" a.sfattr.list[3].value = "contents"
前のページの attr1 ダンプと比べるために、生のダンプを表示します。ヘッダーがハイライトされています。
attr2 の場合、属性が他のファイルシステムブロックを必要とする前に、より多くの属性が inode の中に格納されることができることがわかるでしょう。
inode の属性フォークの領域が短形式の属性でうまって、さらに属性が追加されようとすると、属性は「エクステント」形式に変換されます。
エクステント形式の属性は、属性の検索を高速化するために、ハッシュとインデックスの組を使います。「リーフ」の最初の部分はフラグと、固定長のハッシュとインデックスの組の配列を持ちます。リーフブロックの残りの部分は、名前と値の組の配列で、それぞれは可変長です。
それぞれのリーフは、リーフディレクトリのところで述べた、 xfs_da_blkinfo_t ブロックヘッダーを持ちます。その構造体は、 xfs_attr_leafblock_t の他のすべての構造体を含みます。
関連する構造体は:
typedef struct xfs_attr_leaf_map { __be16 base; __be16 size; } xfs_attr_leaf_map_t; typedef struct xfs_attr_leaf_hdr { xfs_da_blkinfo_t info; __be16 count; __be16 usedbytes; __be16 firstused; __u8 holes; __u8 pad1; xfs_attr_leaf_map_t freemap[3]; } xfs_attr_leaf_hdr_t; typedef struct xfs_attr_leaf_entry { __be32 hashval; __be16 nameidx; __u8 flags; __u8 pad2; } xfs_attr_leaf_entry_t; typedef struct xfs_attr_leaf_name_local { __be16 valuelen; __u8 namelen; __u8 nameval[1]; } xfs_attr_leaf_name_local_t; typedef struct xfs_attr_leaf_name_remote { __be32 valueblk; __be32 valuelen; __u8 namelen; __u8 name[1]; } xfs_attr_leaf_name_remote_t; typedef struct xfs_attr_leafblock { xfs_attr_leaf_hdr_t hdr; xfs_attr_leaf_entry_t entries[1]; xfs_attr_leaf_name_local_t namelist; xfs_attr_leaf_name_remote_t valuelist; } xfs_attr_leafblock_t;
リーフヘッダーは以下のマジック番号を持ちます。
#define XFS_ATTR_LEAF_MAGIC 0xfbee
entries[] 配列のハッシュとインデックスの組は、ブロックの先頭から詰められます。名前と値の組はブロック終端から上に伸びますが、きっちり詰まっているわけではありません。 freemap は、entries[] 配列の後の空き領域を、長さも含めて管理しています。しかし、3つの最も大きい領域を記録しているだけで、短い領域は覚えていません。freemap に、必要な長さの領域がない場合は、名前と値の組が、詰め直され、領域検索がもう一度試みられます。それでも領域がない場合は、ブロックはスプリットされます。名前と値の組は、(ローカルもリモートも)32ビット境界になっています。
値が短い属性(つまり、値がリーフに収まる)の場合、属性は XFS_ATTR_LOCAL がセットされます。エントリーの詳細は、 xfs_attr_leaf_name_local_t 構造体を使って格納されます。リーフに入らない長い値の場合、それを格納するために別のファイルシステムブロックが確保されます。このときは、xfs_attr_leaf_name_remote_t 構造体が使われます。
ローカルとリモートのエントリーは、混じり合っていてもかまいません。ハッシュとインデックスのエントリーを介してだけ、アクセスされるからです。ハッシュとインデックスの組にはフラグがあって、どちらの構造体を使うべきかわかります。
ハッシュキーが重複することはあり得るので、検索でマッチしたハッシュについて、実際の名前の文字列の比較が必要です。
属性フラグには、「incomplete」ビットもあります。それは、属性が作成される途中であって、もしそのビットが立っている間にクラッシュした場合はユーザーに見えてはいけないということです。属性のセットアップが完了したら、ビットはオフにされます。これは、長い属性の場合、単一のトランザクション内で作成が完結しないことがあるためです。
inode に、1つの30KBの拡張属性が加えられました。
xfs_db> inode <inode#> xfs_db> p ... core.nblocks = 9 core.nextents = 0 core.naextents = 1 core.forkoff = 15 core.aformat = 2 (extents) ... a.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,37535,9,0] xfs_db> ablock 0 xfs_db> p hdr.info.forw = 0 hdr.info.back = 0 hdr.info.magic = 0xfbee hdr.count = 1 hdr.usedbytes = 20 hdr.firstused = 4076 hdr.holes = 0 hdr.freemap[0-2] = [base,size] 0:[40,4036] 1:[0,0] 2:[0,0] entries[0] = [hashval,nameidx,incomplete,root,secure,local] 0:[0xfcf89d4f,4076,0,0,0,0] nvlist[0].valueblk = 0x1 nvlist[0].valuelen = 30692 nvlist[0].namelen = 8 nvlist[0].name = "big_attr"
属性ブロック1から8(ファイルシステムブロック 37536 から 37543)は、その属性の生のバイナリーデータを持ちます。
インデックス 4076 (0xfec) は、名前と値の情報があるブロック内オフセットです。値からわかるように、それはブロックの終端に有ります。
xfs_db> type text xfs_db> p 000: 00 00 00 00 00 00 00 00 fb ee 00 00 00 01 00 14 ................ 010: 0f ec 00 00 00 28 0f c4 00 00 00 00 00 00 00 00 ................ 020: fc f8 9d 4f 0f ec 00 00 00 00 00 00 00 00 00 00 ...O............ 030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ... fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................ ff0: 00 00 77 e4 08 62 69 67 5f 61 74 74 72 00 00 00 ..w..big.attr...
30KBの属性と、いくつかの小さい属性がファイルに加えられました。
xfs_db> inode <inode#> xfs_db> p ... core.nblocks = 10 core.extsize = 0 core.nextents = 1 core.naextents = 2 core.forkoff = 15 core.aformat = 2 (extents) ... u.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,81857,1,0] a.bmx[0-1] = [startoff,startblock,blockcount,extentflag] 0:[0,81858,1,0] 1:[1,182398,8,0] xfs_db> ablock 0 xfs_db> p hdr.info.forw = 0 hdr.info.back = 0 hdr.info.magic = 0xfbee hdr.count = 3 hdr.usedbytes = 52 hdr.firstused = 4044 hdr.holes = 0 hdr.freemap[0-2] = [base,size] 0:[56,3988] 1:[0,0] 2:[0,0] entries[0-2] = [hashval,nameidx,incomplete,root,secure,local] 0:[0x1e9d3934,4044,0,0,0,1] 1:[0x1e9d3937,4060,0,0,0,1] 2:[0xfcf89d4f,4076,0,0,0,0] nvlist[0].valuelen = 6 nvlist[0].namelen = 5 nvlist[0].name = "attr2" nvlist[0].value = "value2" nvlist[1].valuelen = 6 nvlist[1].namelen = 5 nvlist[1].name = "attr1" nvlist[1].value = "value1" nvlist[2].valueblk = 0x1 nvlist[2].valuelen = 30692 nvlist[2].namelen = 8 nvlist[2].name = "big_attr"
エントリー配列でわかるように、2つの小さい属性はローカルフラグが立っていて、値が表示されています。
属性を示す生のディスクダンプです。最後に加わった属性がハイライトされています。 (オフセット 4044 もしくは 0xfcc)
属性の数が、1つのファイルシステムブロックに入る量を超えると、(つまり、ハッシュ、フラグ、名前とローカル値)最初の属性ブロックはB+ツリーのルートに変換されます。そしてリーフが、単一のリーフブロックに入っていた、ハッシュ、名前、そして値の情報を持ちます。 inode の属性形式は、エクステントのままです。ノードは、ノードディレクトリのところで述べた xfs_da_intnode_t 構造体を使います。
属性リーフブロックの位置は、どの順番でもかまいません。位置の検索は、ノードブロックの、ハッシュと before の値を使う手段しかないためです。目的のハッシュを計算したら、ノードの btree 配列を読んで、目的のハッシュ値を超える最初の hashval エントリーを探せば、 before 値が指すブロックにそれはあります。
1000の「attribute_n」、「n」は数字、という名前の小さい属性を持つ inode 。
xfs_db> inode <inode#> xfs_db> p ... core.nblocks = 15 core.nextents = 0 core.naextents = 1 core.forkoff = 15 core.aformat = 2 (extents) ... a.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,525144,15,0] xfs_db> ablock 0 xfs_db> p hdr.info.forw = 0 hdr.info.back = 0 hdr.info.magic = 0xfebe hdr.count = 14 hdr.level = 1 btree[0-13] = [hashval,before] 0:[0x3435122d,1] 1:[0x343550a9,14] 2:[0x343553a6,13] 3:[0x3436122d,12] 4:[0x343650a9,8] 5:[0x343653a6,7] 6:[0x343691af,6] 7:[0x3436d0ab,11] 8:[0x3436d3a7,10] 9:[0x3437122d,9] 10:[0x3437922e,3] 11:[0x3437d22a,5] 12:[0x3e686c25,4] 13:[0x3e686fad,2]
btree 配列で、ハッシュは昇順に並んでいます。探している属性のハッシュが、あるエントリーより小さければ、その指す属性ブロックに行きます。
例えば、「attribute_267」を探してみましょう。
xfs_db> hash attribute_267 0x3437d1a8
ルートBツリーノードで、これは 0x3437922e と 0x3437d22a の間になりますから、リーフ11つまり属性ブロック5が、目的エントリーを含みます。
ハッシュエントリーはみな、 XFS_ATTR_LOCAL フラグがオンで、属性の値は名前の後にすぐに続くという事です。名前と値の組の生のディスクダンプのオフセット 2864 (0xb30) に、ハイライトされた名前と、「value_267\d」があります。
エントリーは32ビット境界で始まるので、ハイライトされたエントリーは、2バイト、未使用バイトがあります。
inode 内の属性のエクステントマップが、いっぱいになると、 inode の属性は「btree」形式に変換されます。 inode はエクステントB+ツリーのルートノードを持ち、それが属性データのエクステント配列を持つリーフを指します。別のファイルシステムブロックにある属性データそのものは、ノード属性で述べたのと同じレイアウトと構造体を使います。
XFS B+ツリーエクステントについて詳しくは、以前のB+ツリーデータエクステントの節を参照下さい。
729バイトの値を持つ2000の属性をファイルに追加しました。
xfs_db> inode <inode#> xfs_db> p ... core.nblocks = 640 core.extsize = 0 core.nextents = 1 core.naextents = 274 core.forkoff = 15 core.aformat = 3 (btree) ... a.bmbt.level = 1 a.bmbt.numrecs = 2 a.bmbt.keys[1-2] = [startoff] 1:[0] 2:[219] a.bmbt.ptrs[1-2] = 1:83162 2:109968 xfs_db> fsblock 83162 xfs_db> type bmapbtd xfs_db> p magic = 0x424d4150 level = 0 numrecs = 127 leftsib = null rightsib = 109968 recs[1-127] = [startoff,startblock,blockcount,extentflag] 1:[0,81870,1,0] ... xfs_db> fsblock 109968 xfs_db> type bmapbtd xfs_db> p magic = 0x424d4150 level = 0 numrecs = 147 leftsib = 83162 rightsib = null recs[1-147] = [startoff,startblock,blockcount,extentflag] ... (which is fsblock 81870) xfs_db> ablock 0 xfs_db> p hdr.info.forw = 0 hdr.info.back = 0 hdr.info.magic = 0xfebe hdr.count = 2 hdr.level = 2 btree[0-1] = [hashval,before] 0:[0x343612a6,513] 1:[0x3e686fad,512]
エクステントB+ツリーは2つのリーフを持ち、274エクステントが属性のために使われています。最初のブロックを見ると、属性B+ツリーは2レベルの深さです。513と512(ablock コマンドでアクセスします)にある2つのブロックは、中間の xfs_da_intnode_t ノードであり、すべての属性リーフをインデックスしています。
XFS は、ファイルシステムが作成された時に、いくつかの inode を割り当てます。これらは内部的なものであって、通常のディレクトリ構造からはアクセスできません。これらは、スーパーブロックからだけ、アクセスできます。
クオータが有効なとき、ユーザーとグループのクオータ管理のために、2つの inode が確保されます。プロジェクトクオータが有効なときは、それはグループクオータに取って代わるため、グループクオータ inode を使います。
プロジェクトクオータの主な用途は、ディレクトリごとのディスク使用量を追跡、監視することです。このために、ディレクトリ inode は XFS_DIFLAG_PROJINHERIT フラグがセットされており、そのディレクトリで作られるすべての inode がプロジェクトID を継承する必要があります。
IDゼロが所有する inode とブロックは、クオータの制限(enforce)は受けず、統計(accounting)の対象となるだけです。
拡張属性は、ID のクオータに寄与しません。
ファイルについて、それぞれの ID のクオータ情報をアクセスするには、ID かける xfs_dqblk_tの長さ (136 bytes)のオフセットにシークします。
2つの inode (のデータエクステント)に格納されているクオータ情報は、xfs_dqblk_t 構造体の配列で、システムでの ID ごとに、1つの構造体があります。
typedef struct xfs_disk_dquot { __be16 d_magic; __u8 d_version; __u8 d_flags; __be32 d_id; __be64 d_blk_hardlimit; __be64 d_blk_softlimit; __be64 d_ino_hardlimit; __be64 d_ino_softlimit; __be64 d_bcount; __be64 d_icount; __be32 d_itimer; __be32 d_btimer; __be16 d_iwarns; __be16 d_bwarns; __be32 d_pad0; __be64 d_rtb_hardlimit; __be64 d_rtb_softlimit; __be64 d_rtbcount; __be32 d_rtbtimer; __be16 d_rtbwarns; __be16 d_pad; } xfs_disk_dquot_t; typedef struct xfs_dqblk { xfs_disk_dquot_t dd_diskdq; char dd_fill[32]; } xfs_dqblk_t;
d_magic
シグネチャ。この2バイトは、0x4451 (XFS_DQUOT_MAGIC), であり、アスキーの 「DQ」。
d_version
構造体のバージョンで、現在、1(XFS_DQUOT_VERSION)。
d_flags
ID のタイプを示します。
#define XFS_DQ_USER 0x0001 #define XFS_DQ_PROJ 0x0002 #define XFS_DQ_GROUP 0x0004
d_id
クオータ構造体の ID。d_flags の値により、uid, gid あるいは projid。
d_blk_hardlimit
その ID が所有できるファイルシステムブロック数のハードリミット。ID はこのリミット以上の領域を使うことは出来ず、それを超えると ENOSPC が返ります。
d_blk_softlimit
その ID が所有できるファイルシステムブロック数のソフトリミット。ID は、一時的には、 d_blk_hardlimit までの範囲で、 d_blk_softlimit 以上の領域を使うことができます。ID ゼロの d_btimer 値で示されるタイムリミットまでに領域が解放されない場合は、ID は領域の総量が d_blk_softlimit 以下になるまで、それ以上の領域確保が許されません。
d_ino_hardlimit
その ID が所有できる inode のハードリミット。 d_icount がこの値になると、ID はそれ以上の inode を作成したり所有したりすることができなくなります。
d_ino_softlimit
その ID が所有できる inode のソフトリミット。ID は、一時的には、 d_ino_hardlimit までの範囲で、 d_ino_softlimit 以上の inode を使うことができます。ID ゼロの d_btimer 値で示されるタイムリミットまでに inode 数が減らない場合は、ID は inode 数が d_ino_softlimit 以下になるまで、それ以上の inode を作成したり所有したりすることが許されません。
d_bcount
この ID が所有するファイルシステムブロック数
d_icount
この ID が所有する inode 数
d_itimer
ID の d_icount が d_ino_softlimit を超えた時刻。経過時間が、ID ゼロの d_itimer 値を超えると、ソフトリミットはハードリミットになります。d_icount が d_ino_softlimit を下回ると、d_itimer はゼロにリセットされます。
d_btimer
ID の d_bcount が d_blk_softlimit を超えた時刻。経過時間が、ID ゼロの d_itimer 値を超えると、ソフトリミットはハードリミットになります。d_bcount が d_blk_softlimit を下回ると、d_btimer はゼロにリセットされます。
d_iwarns d_bwarns d_rtbwarns
警告がされた回数。今は使われていません。
d_rtb_hardlimit
ID が所有できるリアルタイムブロックの数のハードリミット。ID はこれを超えてリアルタイムサブボリュームに領域を確保することはできません。
d_rtb_softlimit
ID が所有できるリアルタイムブロックの数のソフトリミット。ID は、一時的には、 d_rtb_hardlimit までの範囲で、 d_rtb_softlimit 以上の 領域を使うことができます。ID ゼロの d_rtbtimer 値で示されるタイムリミットまでに d_rtbcount が減らない場合は、ID は領域が d_rtb_softlimit 以下になるまで、それ以上の領域を所有することが許されません。
d_rtbcount
その ID が現在使っているリアルタイムブロック数。
d_rtbtimer
ID の d_rtbcount が d_rtb_softlimit を超えた時刻。経過時間が、ID ゼロの d_rtbtimer 値を超えると、ソフトリミットはハードリミットになります。d_rtbcount が d_rtb_softlimit を下回ると、d_rtbtimer はゼロにリセットされます。
リアルタイムデバイスの領域管理のために、2つの inode が確保されます。ビットマップ inode と、サマリー inode です。
ビットマップ inode は、リアルタイムデバイスの使用/空き領域を、昔からのビットマップ形式で管理します。リアルタイムエクステントごとに1ビットが使われます。エクステントの長さは、スーパーブロックの sb_rextsize にあります。
ビットマップ inode に使われるブロック数は、リアルタイムエクステント数(sb_rextents) をブロック長 (sb_blocksize) と、バイトあたりのビット数で割ったものです。この値は、 sb_rbmblocks に格納されています。nblocks と、 inode のエクステント配列はこれと一致するはずです。
xfs_ino_t sb_rbmino;
xfs_ino_t sb_rsumino;
TODO:
改訂履歴
改訂 1.0
Fri Jul 03 2009
Lerch Ryan [FAMILY Given]
Inital