2012/10/20
2013/03/14 更新
2013/03/28 更新
2013/04/02 更新
2013/04/10 更新
2013/04/24 更新
2012年の夏休みは、9front の cwfs を試していた。cwfs は fscache と fsworm で構成されているが、fsworm の方から調べることとした。fsworm の方に関心がある理由は、ファイルの履歴等の情報は fsworm に置かれ、また fscache がクラッシュした時のリカバリーも fsworm に基づいて行われるからである。
もちろん、いかなるデバイスもやがては死を迎える。fsworm に対してもバックアップが必要である。cwfs 自体を RAID に置くことも考えられようが、僕には大げさすぎる。もう一つ HDD を追加して、そこに fsworm のコピーを持ちたい。それでは、どのようにコピーすれば、短時間でやれるのか? この問題を解明したかったのである。
9front 付属の cwfs は cwfs64x である。そこで、ここでは cwfs64x に基づいて解説する。
この節は未完成である。暇な時に書く事とする。
Sean Quinlan (Ph.D)
Ken Thompson (Ken's fs)
Plan9 2nd edition (1995)
Geoff Collyer (2007/03/27 mail)
9front (2011)
過去の履歴
WORM (write once read many)
消えない(消せない)
dump
Mac/OSX の time machine との比較
pdumpfs
以前はサーバのために1台を割り当てていた。
memory: ファイルサーバ専用機の中のメモリー
disk: disk cache
WORM: 光ディスク
現在は Plan9 の基で動くユーザープログラム cwfs
ユーザプログラムである事の利点: 仕組みを調べやすい。
memory: cwfs daemon 中のメモリー
disk: ファイルサーバの fscache パーティション
WORM: ファイルサーバの fsworm パーティション
cwfs (or cwfs32)
cwfs64
cwfs64x
cwfs に関する僕のパーティションのサイズは次の通りである。
term% ls -l /dev/sdC0/fs* --rw-r----- S 0 arisawa arisawa 22848146432 Sep 13 09:50 /dev/sdC0/fscache --rw-r----- S 0 arisawa arisawa 114240734208 Sep 13 09:50 /dev/sdC0/fsworm term%
cwfs コンソールはコマンド
con -C /srv/cwfs.cmd
cat /srv/cwfs.cmd
echo statw >> /srv/cwfs.cmd
cwfs コンソールで statw
を実行すると、fsworm の利用統計が出力される。次にその出力例を示す。
> statw cwstats main filesys main maddr = 3 msize = 10313 caddr = 1035 csize = 1392255 sbaddr = 1755217 craddr = 1755389 1755389 roaddr = 1755392 1755392 fsize = 1755394 1755394 0+25% slast = 1754642 snext = 1755393 wmax = 1755392 0+25% wsize = 6972701 1+ 0% 8600 none 230005 dirty 0 dump 1153555 read 95 write 0 dump1 cache 17% full >
maddr
から wsize
までの、等号の右の数字(1列目と2列目)は、サイズを表している。1列目の数字の情報は、fscache の Tcache
block (block address 2) から得られる。2列目は fsworm から得られる注1。これらの単位は msize
を除けば block である。msize
だけは bucket 数である。
fsize
を除いて、2列目の情報は、最後の super block から得られる情報と一致している。fsize
は statw
コマンドを実行した時点での値であり、これは fscache の Tcache
block から得られる fsize
と一致している。(2013/04/24)
fsworm には、fsworm のパーティションサイズに関する情報は含まれない事に注目しよう。fsworm が満杯になった時に、もっと大きなパーティションに置き換えられる可能性があるので、その場合には、単に満杯になったパーティションの内容を新しいパーティションにコピーすればよいように設計されているのであろう。
fscache も fsworm も block を単位として管理されている注2。cwfs64x の場合には 1 block は 16KB である。以下、block をアドレスで表すこととする。ba[0]
は 最初の block を ba[1]
は第2 block を意味する。
wsize
の 6972701
は /dev/sdC0/fsworm
の大きさを block 数で表している。
6972701*16*1024 == 114240733184
/dev/sdC0/fsworm
の大きさ 114240734208
よりも 1024
だけ少ないが、block を単位として使用されている為に、使用できない領域が発生したのである。
fsworm にはフォーマットの概念が存在しない事に注意すべきである。なぜなら Write Once だから、フォーマットしたらデータを書き込めない!注3
fsworm には先頭 block から順に書き込まれる。snext
(next super block) は、次に書き込まれる block を示している。dump を行うと、最初に super block と呼ばれる block が書き込まれ、それに続いてデータの本体が書き込まれる。Super block は、dump 毎の境界として、fsworm においては基本的な役割を果たしている。
sbaddr
(super block address) は、最後に書き込んだ super block のアドレスである。また、slast
は、その1つ前の super block のアドレスである。
fsworm は(その仕組み上)非常に堅牢ではあるが、それでもハードウェアクラッシュによるデータ消失のリスクを負っている。バックアップを行うには、fsworm のコピー(例えば fsworm-bak)を持てばよい。
dump 毎に、fsworm の中に新たに生成された block だけをコピーして行けばよいと考えるかも知れないが、話はそんなに簡単ではない。なぜなら、書き込まれていない block が最後に dump された super block の下に存在するからである。筆者の観察ではそれらには2種類ある。
(a) reserved block注1
(b) free block
である。
(a)は、fscache の中で使用されているが、まだ dump が完了していない fsworm の block である。
(b)は、今後 fscache が使用可能な fsworm の block である。
fscache も fsworm も block を単位として管理されている。cwfs64x の場合には 1 block は 16KB である。以下、block をアドレスで表すこととする。ba[0]
は 最初の block を ba[1]
は第2プロックを意味する。共に、先頭の 2 block は特殊である。block のアドレスは 8 B のデータで指定される。プログラムの中ではデータ型が Off
で示されている。
fscache の場合には、ba[0]
には config 情報が置かれている。ba[1]
は使われていないようである。
fsworm の場合にも先頭の 2 block が使用されている気配はない。
どの block も Tag
を持っている。もっとも、全ての block がフォーマットされているのではない注1。
Tag の大きさは、cwfs64x の場合には 12B であり、block の末尾に置かれる。その構造は次のようなものである。
struct Tag { short pad; /* make tag end at a long boundary */ short tag; Off path; };
pad
の値は 0 である。tag
が block の種類を表している。 path
の値に関する考え方は tag
毎に異なる。
16KB から Tag
の 12B を除く領域がデータ領域(以下、Data で表す)であり、block の種類毎の構造を持っている。纏めると
block = Data + Tag
dump を繰り返すと、fsworm には、ダンプしたデータが積み重ねられる。データは決して上書きされることはない。その様子を図fig:dumpblock>(左)に示す。
以下、block address
dump 毎に block が積み上げられる。1回の dump で積み上げられる block を単に dump と呼ぶ事にする。1つの dump の中には必ず3つの block, “super block”, “cfs root block”, “dump root block” が含まれる。図fig:dumpblock>(右)には、dump の内部構造が示されている。
super block と cfs root block の間の block には、fscache の内容が(前回の dump から更新された差分だけ)保存される。
cfs root block と dump root block の間の block には、dump された年月日の情報が含まれる。必要な block 数は dump の回数に依存する。
fsworm の構造を捉える上で、もっとも基本的なのは super block である。super block の
super block は、fscache をダンプした時に 1 つ作られる。まず super block が fsworm に書き込まれ、続いてデータの本体が block 単位に書き込まれる(図fig:dumpblock>右)。
super block の構造は
を持つ。
図
ここで分かるように、各々の super block は、次の super block のアドレス(
最後の
これらの情報の一部は cwfs のコンソールからも得られる。
次に基本的なのは directory entry block である。この block の
directory entry block には次の directory entry (
図
ここにはファイルやディレクトリの名前(
dump root block は directory entry block の 1 つである。筆者の家庭でのシステムの場合には次のように dump した日付が見える。
最初の行は初めて dump を行った時に(2012年8月1日)生成されている。 図
dump した年月日情報 (
このケースでは次の図fig:dirent>に示すように block が繋がっている。
長方形が1つのブロックを表している。この図ではどれも directory entry block である。dump の回数がまだ少ないので、2013年の月日の情報は1個の directory entry block で間に合っているが、そのうちに複数の block が要求されるようになるだろう。
Plan9 では図fig:dentry>の
であるが、この
cwfs64x の場合、1つの
cwfs64x の場合には
6 個の direct block には、データが直接書かれる。1つの direct block には 16*1024 - 12 B のデータが書き込めるので、6 個の direct block には合計 6*(16*1024 - 12) B のデータを書き込める。ディレクトリの場合には 372(=62*6)個までの
directory entry block の
同様に、
indirect block のタグは
ファイルの内容を含む block は
1つの file block には、最大
ファイルの内容は block 単位で管理されている。ファイルの内容が更新された時には全ての block が書き換えられるのだろうか? 筆者の実験によると否である。1ブロックよりも大きなファイルの末尾にデータを追加すると、最後の block だけが更新される。他の block は、そのまま利用される。もっとも、この実験は
16KB を超える大きなファイルをテキストエディタで開いて、末尾にデータを追加した場合には、完全に書き換えられると思う。(実験はしていないけど...)
書き換えられた block だけが新たに生成される cwfs の特性は、特にサーバで重要である。筆者のサーバではウェブのサーバのログファイルは 1.7GB にも上る。
fsworm の全ての情報は dump stack のトップにある dump root block から辿る事ができる。このアドレスは cwfs console の
実は
ダンプは現在の fscache に基づいて行われる。そして、まず最初に super block が fsworm に書き込まれる。これに続いて、ディレクトリツリーの末端の情報から順に書き込まれる。従って、fsworm の中に、例えば
ユーザからは ls command に q option を添えてファイルやディレクトリの qid を見ることができる。例えば
ユニークであるならば、qid が管理されなくてはならない。super block の中の qidgen が、そのためにあると思える(図fig:super1>)。
実験をして見れば分かるが qid はファイル名の変更によっては変わらない。内容が変わると version が変わる。
fsworm や fscache の中を除くと、qid とその version は directory entry の中に含まれ(図fig:dentry>)、その contents に関する block が同じ qid となっていることが分かる。つまり block の所属を確認するために使われていると思われる。
cwfs64x を見る限り、筆者の config block (
さらに、この block は次のように tag 付けられている。
ソースコードには、次の構造化データがある。
この中のデータは、初期化過程の中で、cwfs の種類毎に(ソースコードの中で)与えられている。
Tcache block は cwfs に関する基本情報を管理している。この内容は cwfs コンソールで見ることができる。
fscache の各ブロックはメモリーにキャッシュされている。10秒毎にメモリーのキャッシュは、(更新があれば) fscache に書き込まれる。
fsworm の各 block は、図fig:fscache>の cache area の cache block に mapping される。
fscache の cache block のアドレスを
cache block の総数は(cwfs コンソールでは)
筆者のシステムの例では fsworm の block 数は 6972701 であるのに対して、fscache の cache block 数は 1392255 である。従って 1/5 程度のキャッシュ能力を持っている。また、fscache には 1394540 個の block が採れるが、実際に使われているのは、1035+1392255(=1393290) に過ぎない。未使用領域は 0.1% 程度である。
単純に考えると表tab:mapping>に示すような mapping を思い浮かべるかも知れない。
ここに、cache と書いたのは fscache の cache area である。示されている address は
しかし、この mapping は問題を孕んでいる。ある fsworm block が cache されていると、同じ cache block に map される他の fsworm block が cache に入り込めない場合がある。
n
の block を ba[n]
で表す。ba[0]
と ba[1]
は使われていない。cwfs が実行されると、最初に 3 つの block
ba[2]: super block
ba[3]: cfs root block
ba[4]: dump root block
Super Block
tag
は Tsuper
(=1) である。cwfs のコードを読むと、super block の Tag
の中の path
は、QPSUPER
(=2) となっている。
struct Superb
{
Fbuf fbuf;
Super1;
};
Fbuf
は free block のアドレスの配列を内部に保有している。free block に関する解説は後回しにする。
Super1
が重要である。
struct Super1
{
Off fstart;
Off fsize;
Off tfree;
Off qidgen; /* generator for unique ids */
/*
* Stuff for WWC device
*/
Off cwraddr; /* cfs root addr */
Off roraddr; /* dump root addr */
Off last; /* last super block addr */
Off next; /* next super block addr */
};
next
)を保有している。そして 最初の super block は ba[2]
から始まる。従って ba[2]
から順に super block を辿れば、次にダンプされるアドレスが分かることになる。次に、その出力結果例を示す。(このツールは後に紹介する)
super blocks:
2
5
69908
85793
104695
222009
...
1751346
1754278
1754381
1754569
1754642
1755217
1755393
1755393
は、次に作られる予定の super block アドレスである。ba[1755217]
の中の Super1
の内容は(例えば)次のようなものである。
super1 fstart: 2
super1 fsize: 1755394
super1 tfree: 92
super1 qidgen: 6d76e
super1 cwraddr: 1755389
super1 roraddr: 1755392
super1 last: 1754642
super1 next: 1755393
sbaddr 1755217
: 現在の super block (最後に書き込まれた super block)
snext 1755393
: 次のダンプ予定のアドレス(つまり、次に作られる予定の super block アドレス)
slast 1754642
: sbaddr
より一つ手前の super block アドレス
Directory Entry Block
2013/03/02 更新
Tag.tag
は Tdir
である。また、Tag.path
は、親ディレクトリの qid に一致する。
Dentry
) が1個以上(cwfs64x の場合、最大62個)含まれている。
struct Dentry
{
char name[NAMELEN];
Userid uid;
Userid gid;
ushort mode;
#define DALLOC 0x8000
#define DDIR 0x4000
#define DAPND 0x2000
#define DLOCK 0x1000
#define DTMP 0x0800
#define DREAD 0x4
#define DWRITE 0x2
#define DEXEC 0x1
Userid muid;
Qid9p1 qid;
Off size;
Off dblock[NDBLOCK];
Off iblocks[NIBLOCK];
long atime;
long mtime;
};
name
)が含まれているので、Dentry
のサイズは許容する名前の長さ(NAMELEN-1
)に依存する。
term% ls /n/dump
/n/dump/2012/0801
/n/dump/2012/0802
/n/dump/2012/0804
/n/dump/2012/0813
....
/n/dump/2013/0121
/n/dump/2013/0127
/n/dump/2013/0128
/n/dump/2013/0205
....
ls -l /n/dump/2012/0801
maia% ls -l /n/dump/2012/0801
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/386
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/68000
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/68020
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/acme
d-rwxrwxr-x M 495 adm adm 0 Jul 31 2012 /n/dump/2012/0801/adm
....
d-rwxrwxr-x M 495 sys sys 0 Jan 18 2012 /n/dump/2012/0801/mnt
d-rwxrwxr-x M 495 sys sys 0 Jan 18 2012 /n/dump/2012/0801/n
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/power
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/power64
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/rc
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/sparc
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/sparc64
d-rwxrwxr-x M 495 sys sys 0 Jul 31 2012 /n/dump/2012/0801/sys
d-r-xr-xr-x M 495 sys sys 0 Jan 18 2012 /n/dump/2012/0801/tmp
d-rwxrwxr-x M 495 sys sys 0 Aug 1 2012 /n/dump/2012/0801/usr
maia%
YYYY/MMDD
) は dump root address と cfs root address の間にある。(図fig:dumpblock>)
Dentry
構造体を見れば分かるように、directory の名前と、mode などの情報が同じ block に同居している。他方 UNIX では mode などの情報は、inode に置かれ、名前の一覧とは別の block になっている(図fig:inode>)。この違いの起源は UNIX では hard link のサポートのために、link counter を名前とは別の block (具体的には inode) に持たなくてはならないからだと思える。
Dentry
の Qid9p1
構造体は
struct Qid9p1
{
Off path; /* was long */
ulong version; /* should be Off */
};
path
は、mode
がディレクトリの場合(つまり mode&DDIR != 0
の場合)には先頭ビットに 1 が立てられている。(このように設計した理由に関しては僕は今のところ解らない。) 正式な qid、すなわち、コマンド
ls -ql
qid.path
の先頭ビットを落としたもの、すなわち qid.path&~DDIR
である。
Dentry
は 260 B である。従って、1つの block には最大 62 個の Dentry
を保持できる。
name
には、ファイル名やディレクトリ名が入る。NAMELEN
は cwfs64x の場合には 144 である。名前は '\0'
で終わるので、名前の最大長は 143 文字である。名前の他に、ディレクトリやファイルにとって基本的な情報がこの中に含まれている。
Dentry
がファイルを表している場合(mode&DDIR == 0
)、ファイルコンテンツの置かれている block は direct block (dblock[NDBLOCK]
) と indirect block (iblocks[NIBLOCK]
) を基に辿る事ができる。ファイルコンテンツを含む block は Tfile
でタグ付けられている。
Dentry
がディレクトリの場合(mode&DDIR != 0
)には、そのディレクトリコンテンツ(中に含まれているファイルやディレクトリの情報)の置かれている block は、ファイルの場合と同様に、direct block (dblock[NDBLOCK]
) と indirect block (iblocks[NIBLOCK]
) を基に辿る事ができる。ディレクトリコンテンツを含む block は Tdir
でタグ付けられている。
NDBLOCK
の値は 6、NIBLOCK
の値は 4 である。
Dentry
が扱える事となる。
Indirect Block
Dentry
構造体に含まれる iblocks[0]
によって示される block には、
(16*1024 - 12)/8 = 2046
2046 * (16*1024 - 12) = 33497112 B
iblocks[1]
には、2 次の indirect block が書かれる。つまり、この block には block アドレスが書かれているのであるが、それらのアドレスは(データの場所ではなく) 1 次の indirect block アドレスである。従って
2046 * 2046 * (16*1024 - 12) = 68535091152 B
iblocks[2]
には
2046 * 2046 * 2046 * (16*1024 - 12) = 140222796496992 B
iblocks[3]
には
2046 * 2046 * 2046 *2046 * (16*1024 - 12) = 286895841632845632 B
iblocks[0] Tind1
iblocks[1] Tind2
iblocks[2] Tind3
iblocks[3] Tind4
Tag.path
はどれも親ディレクトリの qid に一致する。
File Block
Tfile
のタグが付けられている。この block の Tag.path
は、このファイルが属するディレクトリの qid に一致する。
16*1024 - 12 B
(a) ファイルに append 属性を指定している
(b) ファイルの末尾に seek して書き込んでいる
のいずれかの条件の下で実験している。
1757143424 Oct 18 17:13 http
Fsworm Root
2013/03/09
roaddr
から知ることができる。図fig:ls_/n/dump>および図fig:dirent>は、ここから辿って見えるパスの最初の部分である。
roaddr
は fscache が管理している root block であるが、fsworm の dump root block と一致している。
ダンプの順序
/2012/0925/....
2012
、さらにその前に 0925
が...
qid
maia% ls -ql
(000000000009baa2 6 00) --rw-rw-r-- M 326 web web 33597 Mar 8 15:01 bucket.png
(000000000009baa3 3 00) --rw-rw-r-- M 326 web web 13693 Mar 8 15:02 bucket.svg
(0000000000089b8c 2 00) --rw-rw-r-- M 326 arisawa web 782 Sep 28 10:11 console.txt
(0000000000089b8d 2 00) --rw-rw-r-- M 326 arisawa web 2401 Oct 15 21:21 cwfs.svg
...
maia%
のように表示される。先頭の ( ) の中の 16 進数表示の部分が qid であり、その次の数字が qid version である。
マニュアルによると qid はfile system の中でユニークであると言う。
そうであるから、エディタを作る場合には、保存時に他の何かによって変更を受けたか否かを知るために使えるのであるが、time stamp の方が手軽なので僕はこれまでに qid を利用した事は無い。(なお unix の qid は、違うものらしい)
fscache の構造
ba[0] config
ba[1] -
ba[2] Tcache
ba[maddr] map
...
ba[caddr] cache
...
Config Block
2013/03/05
ba[0]
) には、テキスト形式で次のようなデータが先頭から書き込まれていた。(この内容は cwfs console の printconf コマンドでも見える)
service cwfs
filsys main c(/dev/sdC0/fscache)(/dev/sdC0/fsworm)
filsys dump o
filsys other (/dev/sdC0/other)
noauth
newcache
blocksize 16384
daddrbits 64
indirblks 4
dirblks 6
namelen 144
noauth
は、認証をしないで cwfs へのアクセスを許す事を意味している。noauth
は安全な環境での実験レベルでしか許されない特殊な設定である事に注意すべきである。
大学で使っているのは、今年の2月の版であり、これは noauth
にはなっていない。(2013/04/10)
pad: 0000
tag: 10 (Tconfig)
path: 0
struct Conf
{
ulong nmach; /* processors */
ulong nuid; /* distinct uids */
ulong nserve; /* server processes */
ulong nfile; /* number of fid -- system wide */
ulong nwpath; /* number of active paths, derived from nfile */
ulong gidspace; /* space for gid names -- derived from nuid */
ulong nlgmsg; /* number of large message buffers */
ulong nsmmsg; /* number of small message buffers */
Off recovcw; /* recover addresses */
Off recovro;
Off firstsb;
Off recovsb;
ulong configfirst; /* configure before starting normal operation */
char *confdev;
char *devmap; /* name of config->file device mapping file */
uchar nodump; /* no periodic dumps */
uchar dumpreread; /* read and compare in dump copy */
uchar newcache;
};
Tcache Block
struct Cache
{
Off maddr; /* cache map addr */
Off msize; /* cache map size in buckets */
Off caddr; /* cache addr */
Off csize; /* cache size */
Off fsize; /* current size of worm */
Off wsize; /* max size of the worm */
Off wmax; /* highwater write */
Off sbaddr; /* super block addr */
Off cwraddr; /* cw root addr */
Off roraddr; /* dump root addr */
Timet toytime; /* somewhere convienent */
Timet time;
};
Mapping
2013/03/08 更新
cba
とすると cba
は
caddr <= cba < caddr + csize
wsize
の block が fscache のこの領域に map される。
csize
で表示されている。caddr
から始まる、残りの全ての block が cache block ではない事に注意する。
caddr
から数えている。