UNIX 6th code reading - unix/rdwri.c

はじめに

今回はunix/rdwri.cを見ていきます。

ファイルの読み書き処理が中心です。

readi( )

readi( )(6221行目)は、inodeに対応するデータの内容をディスクから読み出します。

readi( )を呼び出す前に、u.u_base, u.u_offset, u.u_count, u.u_segflgを設定しておく必要があります。

ディスクへのアクセスは1block(512bytes)単位で行われ、一度にblockをまたぐアクセスは行われないように制御されます。

複数blockアクセスする場合は、while( )ループで全データを読み出すまでblock読み出しを繰り返します。

先読みblockがある場合は、breada( )を使って、先読みblockも同時に読み出しておきます。非同期で先読みblock読み出し処理が行われ、block bufferにデータが格納されることが期待できます。そのため、実際にそのblockの読み出し処理を行う際はキャッシュが効いて、ディスクにアクセスすることなくデータを取得できるので性能が上がります。

writei( )

writei( )(6276行目)は、inodeに対応するデータの内容をディスクに書き込みます。

基本的な流れはreadi( )と同様です。

block単位で書き込むため、block中の一部を書き込む場合はbread( )でblockを読み出し、その後更新部分だけデータを更新した後にbawrite( ), bdwrite( )で書き込みます。blockを丸ごと書き込む場合は、blockを読み出すことはしません。

よって、block単位でのみ書き込みを行うようにプログラムを書けば、読み出し処理がなくなる分性能が上がります。

max( ), min( )

1word(2bytes)の引数を2つ受け取り、max( )(6326行目)は大きいほうの値、min( )(6339行目)は小さいほうの値を返します。

iomove( )

iomove( )(6364行目)は、block bufferとu.u_baseで指定したアドレスとでデータのやり取りを行います。

ディスクからデータを読み出す場合、読み出し結果がblock bufferに格納されるので、iomove( )を使ってユーザ空間(プログラム中の変数など)にデータをコピーする必要があります。

書き込む場合はその逆で、iomove( )を実行してblock bufferにデータを書き込んでから、ディスクへの書き込み処理を実行する必要があります。

segflgが0(カーネル、ユーザ空間でデータをコピー)、かつ、1word(2bytes)単位の処理ならばcopyin( ), copyout( )関数を使用しデータをコピーします。

そうでない場合は、cpass( ), passc( )を使用します。

iomove( )が正しく実行されると、読み書きが行われた分だけu.u_base, u.u_offset, u.u_countが更新されます。

コードメモ

readi( )
  • arguments
    • aip : inode pointer
  • 6230-6231 : u.u_countが0ならばreturn
  • 6232 : inodeの更新フラグを立てる
  • 6233-6236 : inodeがIFCHR型ならば、デバイスのread( )ハンドラを実行してreturn
  • 6238 : ここから読み出し処理のdo whileループ。全データを読み終わるか、途中でエラーが起こるまでblock単位で処理を繰り返す
    • 6239 : lshift( )を使ってu.u_offset[ ]を9bit右シフトする。1blockは512bytesなので、9bit右シフトすることで仮想block Noが得られる。>>演算子ではなくlshift( )を使用している理由は、u.u_offsetはu.u_offset[0]とu.u_offset[1]の16bits x 2で32bitsを表現しており、さらに、PDP11はミドルエンディアンのため、単純に>>を使用することができないので、lshift( )という関数を作ってそこでシフト演算を処理している。PDP11は16bitsマシンのため、16bits以上のデータを扱いたいときはこのように配列を使って実現している。ミドルエンディアンについてはこちらを参考 http://d.hatena.ne.jp/oraccha/20090921/1253549177
    • 6240 : u.u_offsetの下位9bitsを取得して、block中のオフセットを取得する
    • 6241 : min( )を使って、1block中から読み出すデータサイズ(512bytes-offset)とu.u_count(読み出し処理の総byte数)の大きいほうをnに格納する。この部分で、読み出しがblock単位の処理になるように調整している
    • 6242 : inodeがIFBLK型でないならば
      • 6243-6244 : dbcmp( )を使用して、inodeのi_size(=ファイルサイズ)と、u.u_offsetの差分をdnに格納する。dbcmp( )は32bits用の単なる引き算処理。上記のように、16bits以上のデータは配列で表現されているため、単純な引き算処理では行えないため専用の関数を用意している
      • 6245-6246 : dnが0、つまりファイルオフセットがファイルサイズより大きくなっていたらreturn
      • 6247 : nとdnで値の小さいほうをnに格納する。これが実際に読み出されるbytes数になる
      • 6248-6249 : bmap( )を使って仮想的なblock Noから物理的なblock Noを得る。これが0ならばエラーなのでreturn
      • 6250 : dnにinodeのi_dev(デバイス種)を格納
    • 6251-6253 : inodeがIFBLK型ならばdnにinodeのi_addr[0]を格納する。IFBLKK型はi_addr[0]にデバイス種類が格納されている?先読みblock Noを示すrablockには(仮想?)block No+1を格納しておく
    • 6255-6256 : do whileループで次々と連続blockを読み出している状態ならば、breada( )を使って、先読みblockの読み出し処理も非同期で行っておく。実際にそのblockを読むときにはデータがblockに読み出されているはずであり、キャッシュが効いて性能が上がる。inodeのi_lastrは6259行目で、読み出しを行った仮想block Noを設定している。次のループでi_lastr+1と、今から読もうとしている仮想block Noが等しければ、ループで次々と連続したblockを読み出しているところだと判断ができる。(i_lastrはreadi( )実行後にも残っている?もしそうならば、readi( )自体を連続して呼び出す際にもそれが連続したblockならば、連続block読み出しを行っている最中だと判断される?)
    • 6257-6258 : 連続block読み出しを行っていると判断できなければ、bread( )を使って読み出し対象blockのみを読み出す
    • 6259 : inodeのi_lastrに、読み出し処理を行った仮想block Noを格納しておく
    • 6260 : この時点で、ディスクから読み出したデータがblock bufferに格納されている。iomove( )を使って、block bufferのデータをユーザ領域にコピーする。iomove( )が終わった時点で、u.u_base, u.u_count, u.u_offsetが更新されていることに注意
    • 6261 : brelse( )を呼んで、block bufferの解放
  • 6262 : エラーが起きたか(u.u_errorに値が入っている)、まだ読み出すデータがある(u.u_countが0ではない)ならば、block読み出し処理を繰り返す
writei( )

大きな流れはreadi( )とほとんど一緒

  • arguments
    • aip : inode pointer
  • 6285 : inodeの参照フラグと更新フラグを立てる
  • 6286-6289 : inodeがIFCHR型ならば、デバイスのwrite( )ハンドラを実行しreturn
  • 6290-6291 : u.u_countが0ならばreturn
  • 6293 : readi( )と同じように、block単位でデータの書き込みが行われる。データ全てを書き終わるまで、もしくは、エラーが起きるまで処理が繰り返し行われる
    • 6294-6296 : ここはreadi( )と同じ。block単位の処理になるように調整
    • 6297 : IFBLK型ではないならば
      • 6298-6299 : bmap( )を呼んで、仮想block Noから物理block Noに変換する。それが0ならばエラーなのでreturn
      • 6300 : dnにinodeのi_dev(デバイス種類)を格納する
    • 6301-6302 : IFBLK型ならばdnにi_addr[0]の値を格納する
    • 6303-6304 : block(512bytes)単位で書き込もうとしていたらgetblk( )を使ってblock bufferを取得する。getblk( )では、デバイスへの読み出し処理が伴わない
    • 6304-6305 : blockの中の一部だけ更新しようとしている場合、bread( )を使ってデバイスから最新データを読み出しを行い、block bufferを取得する
    • 6306 : iomove( )を使って、ユーザ空間にあるデータをblock bufferにコピーする。この時点でu.u_base, u.u_count, u.u_offsetが更新されていることに注意
    • 6307-6308 : iomove( )でエラーがあったらbrelse( )でblock bufferを解放
    • 6308-6311 : iomove( )が成功していた場合。u.u_offsetがblockの境界を指していた場bawrite( )ですぐに書き込みを行う。ただし非同期なのでIOの完了は待たない。境界を指していない場合、次のループや次にwritei( )を呼んだときに続きのデータを同blockに書き込む可能性があるので(?)、bdwrite( )を使ってすぐにはデバイスに書き込みを行わない
    • 6312-6317 : 書き込みを行った結果、ファイルサイズが大きくなったら、かつ、IFCHR型ではないならば、ファイルサイズの拡大処理を行う。inodeのi_sizeにu.u_offsetを足す
    • 6318 : inodeの更新フラグを立てる。6285行目でも更新フラグが立っているのに、なぜ再度立てている?ループの中で時間のかかる処理があって、その間にinodeの更新が行われてしまうことを危惧している?時間のかかりうる処理は6305行目のbread( )(ディスクの読み込み処理を行うとき、IO完了を待つ)だけだと思う。これを気にしている?bawrite( )もbdwrite( )(getblk( )の中で呼ばれるものも含む)もIO完了は待たないので、そんなに時間はかからないはず
max( )
  • arguments
    • a : 1word data
    • b : 1word data
  • 6330-6332 : aとbを比較し、大きいほうをreturnする
min( )
  • arguments
    • a : 1word data
    • b : 1word data
  • 6343-6345 : aとbを比較し、小さいほうをreturnする
iomove( )
  • arguments
    • bp : buffer pointer
    • o : offset
    • an : copyするdataのサイズ(byte単位)
    • flag : block bufferに対するwriteかreadか
  • 6371 : block bufferの先頭アドレスにoffsetを足したものをcpに格納
  • 6372 : u.u_segflgが0(カーネル、ユーザ空間でのデータのコピー)で、かつ、copyするdata size, 転送元アドレス, 転送先アドレスが全てword単位ならば
    • 6373-6374 : flagがB_WRITEならばcopyin( )を使ってu.u_baseが指すユーザ領域のデータをn bytes分block bufferにコピー
    • 6375-6376 : そうでなければcopyout( )を使って、block bufferのデータをn bytes分 u.u_baseが指すユーザ領域のアドレスにコピーする
    • 6377-6380 : copyin( ), copyout( )の返り値を見て、値が0でない場合はエラーが起きたことを示すので、u.u_errorにEFAULTを設定しreturn
    • 6381-6384 : u.u_base, u.u_offsetを読み書き処理を行ったbytes分プラスし、u.u_countをマイナスし、return
  • 6386 : u.u_setflgが1か、byte単位で読み書きを行う場合
    • 6387-6391 : flagがB_WRITE(block bufferへの書き込み)の場合、cpass( )を使って、u.u_baseが指すアドレスから1bytesずつデータをblock bufferに転送する
    • 6392-6393 : block bufferから読み出す場合、passc( )を使ってブロックバッファからu.u_baseが指すアドレスへ1byteずつデータを読み出す

終わりに

今回までで、ファイルシステムの基礎的な部分をだいたいおさえることができました。

次回からはファイルシステム周りのシステムコールを見ていきます。