UNIX 6th code reading - unix/subr.c

はじめに

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

仮想的なblock Noから物理デバイスのblock Noに変換するbmap( )を中心に、subr(サブルーチン)の名前の通り、他の関数からよく呼ばれる関数が揃っています。

bmap( )

ファイル中のアクセスしたいデータのオフセットから、仮想的なblock Noが決まります。先頭のblock Noを0とすると、例えば513bytes目にアクセスしたければ、欲しいデータのblock Noは513 / 512 = 1(小数点は省略)です。

この仮想block Noから物理デバイスのblock Noのマッピングを行うのがbmap( )(6415行目)です。

マッピングには直接参照と間接参照があり……という話は以前のエントリで解説をしたので、そちらを参照してください。

概念図を再掲しておきます。

bmap( )にはマッピングを行うだけでなく、以下の処理も行います。

  • 現在扱っているblock No数より大きい数のblock Noに対してbmap( )で変換をしようとするとき、alloc( )を呼び出し新たにデータ用のblockを取得する(例えば、ファイルサイズが513bytes(=2block)である場合に、bmap( )を呼んで3block目の物理アドレスを取得しようとしすると、alloc( )で新しいblockを確保して自分のデータblockとして登録する(直接参照ならi_addrに、間接参照ならi_addrが指す先のblockに登録する)
  • 「次」のデータblockのNoを先読みblockとしてrablockに格納しておく(ここで言う「次」は変換後の物理block No+1ではない。直接参照を例にすると、returnする物理block Noがi_addr[n]の値ならば「次」はi_addr[n+1]の値である)

passc( )

passc( )(6517行目)は、1byteのデータをユーザ空間(プログラム中の変数とか)にコピーします。ユーザ空間中のアドレスはu.u_baseで指定し、ユーザ空間へのコピーはsubyte( )を使って実現します。

実行が成功すると、u.u_countがデクリメント、u.u_baseとu.u_offsetがインクリメントされます。

返り値は、u.u_countが0なら-1, それ以外なら0になります。passc( )を呼ぶ側は、u.u_countその他を適切な値に設定することで、while( )を使って必要なデータをまとめてコピーできます。

cpass( )

cpass( )(6542行目)は、passc( )と逆で、1byteのデータをユーザ空間から読み出します。

ユーザ空間中のアドレス指定や、u領域のデータの更新の話はpassc( )と同様です。

返り値は読み出した1byteのデータです。

nodev( )

nodev( )(6566行目)は、bdevsw(4656行目), cdevsw(4669行目)で使われます。使用していないデバイスのハンドラにnodev( )を割り当てます。

nodev( )はu.u_errorにENODEV(そんなデバイスは存在しないエラー)をセットします。

nulldev( )

nulldev( )(6574行目)はbdevsw, cdevswで使われます。呼び出しても意味のないハンドラにnulldev( )を割り当てます。

例えば、rkのopenハンドラにnulldev( )が割り当てられています。rkディスクに対してアクセスを行う際に、open処理は必要ないためです。

nulldev( )内では、何も処理を行いません。

bcopy( )

bcopy( )(6585行目)は引数のcount words(byte単位ではない)分データをコピーします。

passc( )と異なり、カーネル空間からカーネル空間へのコピーです。(より正確に言うと、現モード空間から現モード空間へのコピー)

コードメモ

bmap( )
  • arguments
    • ip : inode pointer
    • bn : 仮想的なblock number. アクセスしたいオフセット(byte)を512で割った値
  • 6423-6426 : 引数のblock numberが大きすぎたらエラー
  • 6427 : inodeにILARGフラグが立っていなかったら。直接参照処理
    • 6431 : 直接参照の場合、引数で渡されたblock numberは7(i_addr[ ]の要素数)以下のはずだが、7以上だった場合の処理。直接参照から間接参照に切り替える。このケースはbmap( )がwritei( )から呼ばれたときのみ起こりうる。writei( )はファイル・ディレクトリへの書き込み処理
      • 6435-6436 : alloc( )で新たにblockを取得する。失敗したらエラー
      • 6437-6441 : i_addr[ ]の中身を先ほど取得したblockにコピーしておく。i_addr[ ]の中身は0クリアしておく
      • 6442 : ip_addr[0]に先ほど取得したblockのblock Noを格納する。このblockは間接参照に使用される
      • 6443 : bdwrite( )を呼んで、blockの書き込み。元々i_addr[ ]に格納されていた値がblockに出力される
      • 6444-6445 : inodeのILARGフラグを立ててlargeに飛び、関節参照処理を継続
    • 6447 : ここから通常の直接参照処理。引数で与えられたblock Noが差すi_addr[ ]の値を取得
    • 6448-6453 : ファイルサイズの拡大による新規block取得処理。i_addr[ ]から取得した値が0で、alloc( )による新規blockの取得に成功した場合、取得したblockをi_addr[ ]に登録し、inodeの更新フラグを立てる。
    • 6454-6456 : 先読みblockの登録処理。引数から与えられたbn(仮想block No)が7未満ならば、i_addr[bn+1]の値を先読みblock Noであるrablockに登録しておく。ファイルを先頭から読んで処理する場合など、次のblockがすぐ後に読まれる確率が非常に高いため
    • 6457 : 物理block Noをreturn
  • 6462 : ここから間接参照処理
  • 6463-6465 : iに仮想block Noを8bit右シフトしたものを格納。間接参照の場合、i_addr[ ]の1要素辺り256blocks扱うので、8bit右シフト(=256で割った商)した値がi_addr[ ]のindexになる。仮想block Noが0174000(=2,048=256x8)以上ならば二重間接参照を使用する。二重間接参照はi_addr[7]を使用するので、iに7を格納する
  • 6466-6473 : i_addr[i]の値が0ならば、inodeに更新フラグを立て、alloc( )で新たなblockを取得する。取得したblockのblock Noをi_addr[i]に格納する。alloc( )に失敗したらreturn. i_addr[i]の値が0でなければ、そのblockをbread( )で取得する
  • 6477 : ここから二重間接参照処理
    • 6478 : 一つ目の参照ブロック中の該当block Noを算出。8bit右シフトしたものから引いている"7"は、仮想block Noから1,792を引いているという意味。1,792は7x256. i_addr[0-6]は通常の(一重)間接参照で、それぞれ256個のblockを扱う。なので1,792を引くと二重間接参照(i_addr[7])の一つ目の参照ブロック中のオフセットが算出できる
    • 6479-6485 : 一つ目の参照ブロック中の該当エントリのblock Noが0だった場合、alloc( )で新たにblockを取得し、そのblock Noを該当エントリに割り当て、bdwrite( )を呼んでブロックデバイスに遅延書き込みを行う。alloc( )に失敗したらエラーでreturn
    • 6486-6489 : 該当エントリがblock Noを持っていた場合、bread( )を使って該当blockを取得する
    • 6490-6491 : bp, bapを取得したblockで上書き。もう一回間接参照blockを辿る処理は、この後の通常の間接参照処理で行われる
  • 6496 : ここから通常の間接参照処理。iに仮想block Noの下位8bitを格納。1block(512bytes)中に2bytes単位(int)でblock Noが256個格納されている。なので仮想block Noの下位8bit(0-255)が、間接参照ブロック中のオフセットになる
  • 6497-6503 : 間接参照ブロック中の該当オフセットからblock Noを取得。それが0ならalloc( )で新たなブロックの取得を試みる。取得したブロックのblock Noを該当オフセットにセットし、bdwrite( )を使って取得したブロックと間接参照ブロックを遅延書き込み。block Noが0でないか、alloc( )でのブロック取得に失敗したら間接参照ブロックを解放する。(alloc( )に失敗したときは、エラー処理じゃなくていいのか?)
  • 6504-6506 : 先読みblock Noをrablockにセット
  • 6507 : 物理block No(nb)をreturn.(Lions本の版によってはbnをreturnしているが、それは誤植。nbをreturnが正しい)
passc( )
  • arguments
    • c : コピーする1byteのデータ
  • 6521-6522 : u.u_segflgがセットされていれば、u.u_baseば指す場所にデータcをコピーする。(kernel to kernel copy)
  • 6522-6526 : u.u_segflgがセットされていなければ、subyte( )を使ってu.u_baseが指すユーザ空間のアドレスにデータcをコピーする(kernel to user copy)。subyte( )に失敗したらエラーでreturn
  • 6527 : u.u_countをデクリメント
  • 6528-6529 : u.u_offsetをインクリメント
  • 6530 : u.u_baseをインクリメント
  • 6531 : u.u_countが0なら-1, それ以外なら0を返す。passc( )の読み出し元は、返り値が-1かどうかで1連のデータのコピーが完了したかどうか判断できる
cpass( )
  • 6546-6547 : u.u_countが0ならreturn -1. 手元のLions本ではc.c_countとなっているがu.u_countが正しい
  • 6548-6549 : u.u_segflgがセットされていれば、u.u_baseが指す場所の1byteデータをcにコピーする(kernel to kernel copy)
  • 6549-6553 : u.u_segflgがセットされていなければ、u.u_baseが指すユーザ空間の1byteデータをfubyte( )を使ってcにコピーする(user to kernel copy)。fubyte( )に失敗したらエラーでreturn
  • 6554-6557 : 後処理はpassc( )の6527-6530と同じ。u.u_countをデクリメントし、u.u_offsetをインクリメントし、u.u_baseをインクリメントする
  • 6558 : 取得した1byteデータをreturnする
nodev( )
  • 6559 : u.u_errorにENODEV(そんなデバイスは存在しないエラー)をセット。つまり、この関数が呼ばれること自体がエラー
nulldev( )

何もしない

bcopy( )
  • arguments
    • from : コピー元アドレス(in kernel)
    • to : コピー先アドレス(in kernel)
    • count : コピーするデータサイズ。word(2bytes)単位
  • 6593-6595 : fromからtoへc words分データをコピーする。register変数は恐らく16bits

終わりに

以前のエントリで説明した、直接参照・間接参照がカーネルの中でどのように処理されているかが確認できました。

次回はunix/fio.cを追っていく予定です。