ほんとうにできるのか... というと疑問ではあるが、とりあえず検討してみる。
改造するベースとする ソースは、 v7em-003.tgz とする。
トランザクションデータ とその管理
トランザクションデータ という ジャーナルに書かれるデータをまず考える。
ファイルを作ったり、書き込んだりする といった オペレーションを行うと いくつか の buf が汚れることになる。この更新イメージ を トランザクションデータとして書き出す。1 つのオペレーションが複数の トランザクションデータになることはないように制御する。
オペレーションを行おうとしたら、トランザクションデータにはいるかどうか まず計算する。入るのであれば、関係する buf を トランザクションデータを 管理するデータ (とりあえず struct trans と呼ぶ) に登録する。
struct trans は、トランザクションデータがすべて書かれても 残る。 すべての buf が 更新されるか 新たなトランザクションデータに引き継がれ 、登録された buf がなくなれば、開放される。
また、struct trans の数は、有限であるとする。空いている struct trans がなくなったり、ジャーナルがいっぱいで書けないような 状況になれば、まったく更新できなくなる。そのときは、先頭の struct trans に含まれる buf を積極的に 書き出すようにする。
struct trans の引き継ぎ
1つの buf を 複数の struct trans で管理する必要はない。
つぎの更新 が入った場合、disk に書き出されたのと同じ扱いをして、 つぎの struct trans に 登録する。
buf の状態管理
buf は、データが ないか clean か ダーティ かの区別があった。 基本的に ダーティ なものは、いつ書き出してもよかった。
ところが、メタデータの場合はそうではない。トランザクションデータが 書かれるまでは、Disk に書き出してはいけない。
struct trans へのポインタがあり、struct trans の状態が 未書き込みで あれば、Disk に書き出さない。Disk に書き出したら、 struct trans へのポインタがあれば、登録を外す処理が必要。
また、メタデータの場合、buf を、つぎに書き出す トランザクションデータ の struct trans に 登録するという 処理が必要。
また、この登録処理は、トランザクションデータを書き出している最中は またせないといけない。
ところで、普通は、1つ1つの更新プリミティブ も トランザクションと呼ぶ。 ここでは、それらを1つにまとめて ジャーナルを更新する単位を トランザクションと呼ぶことにする。
誤解を招くような気がするんで コメント。
1つ1つ更新の単位は、とりあえずオペレーションと呼ぶことにする。 これも普通の使いかたとちがうけども、--- トランザクションを 構成するもの という意味では同じか。
ここでは、
ということにする。
struct buf レベルでの 更新イメージのみを対象にする .. という 考えは、普通とは違う... たぶん。
struct buf の 変更
buf には、どういうデータが加わって、状態がどうなるのかを整理してみる。
追加するデータ
* struct trans *b_trans : トランザクションへのポインタ * struct buf *b_tforw,*b_tback : トランザクションに属するリスト * unsigned short b_chs,b_che : 変更箇所の 範囲 * char b_flags : トランザクション関係の状態(たぶん不要)
追加する状態
* B_TRANS : iodone で、trans_iodone を呼ぶかどうか および、delayed write して良いかどうか。 ( B_TRANS なものは、delay write で書き出さない )
struct trans の 構造
struct trans
* char t_stat ; 下記の状態 * struct trans *t_forw,*t_back ; 同じ状態のもののリスト * daddr_t t_gen,t_off ; ジャーナル 上の位置 * daddr_t t_size ; ジャーナル上のサイズ * struct buf *t_buf ; buf のリスト * int t_bufnum : 上のリストの個数 * struct buf *t_bufnext : 次の 状態に使う buf のリスト * int t_bufnextnum : 上のリストの個数
状態
TS_FREE 0 TS_CREATE 1 TS_START 2 TS_TRANS 3 TS_END 4 TS_ESTART 5
グローバル変数
struct trans *tlist[6];
trans 自体の状態は、
TS_FREE 使っていない TS_CRATE トランザクション作成中 TS_START トランザクションデータ書き込み中 (trans_inodone で次に) TS_TRANS トランザクション完了待ち中 (trans_inodone で次に) TS_END トランザクション完了 TS_ESTART トランザクション完了処理中 (trans_inodone で次に)
という状態を循環することにする。T_END と T_ESTART の2つに分けたのは、 完了マークを書くための buf を get してこないといけないため、 その処理は、iodone の延長(= 割り込み処理中)ではできない。
trans に属してる buf は、書き込み終ると、trans から開放 される。全部なくなれば、trans の状態遷移を行う。この部分は以下の情報 を対象にする。
t_buf の内容は、状態に応じて変わる。
TS_START の状態で トランザクションのために登録する buf は、 別のところ ... t_bufnext に置くことにする。 状態が遷移したら、t_bufnext を t_buf に移す。
T_END から T_ESTART の処理を誰が受け持つか あんまりちゃんと考えていないが。
適当にいれることにする。kernel_thread があると楽なのだが ... そういうものは、期待しない。
トランザクションデータの 登録 ...
よくわからないが ...とりあえずはこんなものを考える。
struct trans *trans_get() trans_add(struct trans *tp,struct buf *bp,int type,int from,int to) trans_put(struct trans *tp)
ひょっとしたら、リソースの予約という概念が必要かも知れない。 必要なら、trans_get のパラメータに加えることにする。
trans_add では、buf および 変更を範囲を登録。
type は、更新 と 開放(= データブロックとして使われるかも知れない) だけで良いと思うが、一応、OP 毎に名前を付けて管理することにする。
たとえば、
といった感じか。(TOP じゃなくて、TRANS_OP とか長くすべきかちょっと悩むけど とりあえず TOP )
どこから手を付けるべきか ...
について表示してみる。
まずはそこまで。れっつとらい。
とりあえず、bmap を 修正して考えてみた。
== free ===
free では、新たに block を freelist の 書くから そこで、 trans_add する。ここでは、bwrite しているが、 トランザクションデータを書くまで、disk に書いてはいけないので、 bdwrite に変更。
ここで、トランザクションに関係あるのは、bdwrite だけであることに気が付いた。 そして、grep してみると 上記の更新 でだいたい網羅してしまっている。
あとは、TOP_SUPER をどうするか ... これは実際には、T_START になる 直前で f_mod 見て 処理すれば良い。 ... なんとなく、できそうな気が ... してくる。本当か?
ちょっと試す。
まずは mkfs
./v7-mkfs file sys size: 64000 file system: rootdisk isize = 20480 m/n = 3 500
とかパラメータを入れると、32MB の rootdisk というファイルができる。 これに、v7-tar で展開。
gunzip < v7.tar.gz | ./v7-tar -xvf - 2>&1 |tee log
なんか出て来た。
: TOP_ILIST: bno = 3448 8 - 12 TOP_USED: bno = 3460 0 - 0 TOP_ILIST: bno = 3448 12 - 16 TOP_USED: bno = 3463 0 - 0 TOP_ILIST: bno = 3448 16 - 20 TOP_INODE: bno = 6 320 - 384 TOP_INODE: bno = 6 320 - 384 TOP_INODE: bno = 14 320 - 384 TOP_USED: bno = 3466 0 - 0 TOP_INODE: bno = 6 256 - 320 TOP_USED: bno = 3469 0 - 0 TOP_INODE: bno = 6 256 - 320 :
... ここで、mkdir とか rmdir での ジャーナリングをすっかり忘れていた ことに気が付く。
V7 は、ディレクトリ のファイルの更新を userland にまかせていたのであった。 これをどうするか検討しなくては ...
とりあえずここまでは、 v7jfs-000.txt に パッチ形式でおいておく。
mkdir rmdir をどうすべきか ..
mkdir は、
という一連の処理がある。
rmdir は、
これを まとめてトランザクションにできるようにする ...
... となると、trans_get を かぶせるような ことが出来れば良い。
それは、u.u_ap に入っているかどうかで判断できるから OK。
あとは、通常ファイルの更新に対してもジャーナルを作るというオプションを 作れば良い。この オプションは、writei に対するもの。
具体的には、
の2つのパスで、u にモードを設定できるようにする。
mkdir は、こんな感じか。
TOP_USED: 99 1 bno = 19594 0 - 0 TOP_INODE: 99 2 bno = 257 128 - 192 TOP_FDATA: 99 3 bno = 19594 0 - 16 TOP_INODE: 99 4 bno = 257 128 - 192 TOP_FDATA: 99 5 bno = 19594 16 - 32 TOP_INODE: 99 6 bno = 257 128 - 192 TOP_INODE: 99 7 bno = 246 256 - 320
なんかそれっぽい。mkdir というトランザクションに 7 つの オペレーションが はいっている。で、2 回 link しているから、bno = 19594 に 2 つのデータを 書き込む。... ええ感じやん。
INODE は、257 に対して 3 回変更。246 に対して、1 回変更。 USED は、あたらしく、データを取って来て USER データとして使うから、 ちょっと、Disk に書くのをまてや ... つうことだが、この場合は出さなくて良いか。
ところで、組み込みTiki で こいつの位置付けが だいぶはっきりしてきた。
ということだ。で、Host OS を通して ちゃんと Disk に書くことまで 保証できるのだろうか? そうでないと ジャーナリングしても意味がない。
という2 通りの方法が一応ある。ただし、fsync/fdatasync は、そのファイル 全体に対して.. ということになる。これしか使えないとなると、 FS とジャーナル のファイルを別々にして ... ジャーナルを書く/ FS を書く というのを交互にやることになりそうだ。
まあ、それでも良いんだが ... mmap している場合は、msync というのも 使える。これは BSD なんかの マニュアルを見ると、(ページ単位で)好きなところを 書き出せるように見える。
Linux でも fsync はちゃんと Disk に書いてくれるようなので、 Linux なら fsync によるコントロール。BSD なら msync によるコントロール という風に 使い分けすることになるかも。