2009年12月9日水曜日

Lions' Commentary (8) - lshift -

なんかまた色々と忙しくなってきちゃって進みが悪いんだけど、read / write 周りのsystem callが一通り追いかけられた感じなのでブログにエントリしてみようかと。

readi / writei は今までに読んだbio.c周りのバッファを使ってブロック単位に読み書きしてます。inodeから論理ブロック番号に変換して、最終的に物理ブロック番号を求める当たりもこの辺に。ファイルのオフセットからブロック番号とブロック内オフセット位置を求めるところでlshiftというアセンブラルーチンを呼んでまして、普通に書けば簡単な関数なんですが、アセンブラで良くわからん命令があって、ちょっと読解に苦戦しました。これは後述します。

ファイルの読み書きは基本512バイトのブロック単位で行うので、書き出しの際に1ブロック丸々上書きにならないような場合にはread modify writeの処理が入ります。また書き出し時には512バイト境界をまたぐごとに非同期書き込み要求を出し、境界に達しない半端な書き込みはバッファブロックにDIRTYと印を付けておき、バッファがLRU制御で追い出される際に遅延書き込みで吐き出されるようになってます。ブロック番号が直前に読み出したブロック番号+1になっていると、シーケンシャルプリフェッチが働くようになっており、古典的とは言えI/O処理の基礎は一通り完成している感じです。

プロセス管理やメモリ管理はLions'のみで一通り理解できてきた感じですが、I/O周りとなるとソース読解だけではなかなか理解が進みませんね。この辺はBach本の併読が遠回りなようで近道かも。

で、問題のlshift。例によってconf/m40.sです。

.global _lshift
_lshift:
    mov    2(sp),r1
    mov    (r1)+,r0
    mov    (r1),r1
    ashc   4(sp),r0
    mov    r1,r0
    rts    pc
「ashc」の使い方が良くわからなくて悩んだんですが、右オペランドがデスティネーションで、偶数レジスタを上位、奇数レジスタを下位としたペアレジスタに対して算術シフトを施す、という事ですね。この場合、r0が指定されているので、(r0 << 16) | r1 が操作対象というわけです。で、左オペランドがまた4(sp)なんて書かれているもんだから意味がわからなかったんですが、これがシフト量ですね。すっかりRISCが染み込んでしまっていたので、シフト演算の入力がオフセット付きレジスタ間接アドレッシングというのは、ちょっと斜め上をいく感じでした。結局、2(sp)が第一引数、4(sp)が第二引数なので、C言語で書けば

u16 lshift(u16 *vptr, s16 n)
{
    u32 v = (vptr[0] << 16) | vptr[1];
    if (n >= 0) v <<= n;
    else v >>= -n;
    return v & 0xffff;
}

これだけの処理。というか、やりたい事はu32値をブロックサイズ512で割りたいだけなんですが、なんとも面倒な話です。同様にdpcmpなんて関数もありまして、こいつも長々と書かれてますが32bitでcompareとるだけのアセンブラ関数でした。実際には減算結果を返し、結果は-512〜512に丸められます(という概要だけでも知ってればだいぶ読みやすいはず)。

PDP-11の命令フォーマットはオペランドに関しては基本的に6bitがアサインされており、3bitでレジスタ番号、残り3bitでアドレッシングモードが指定できるんですね。 CISCという意味では直行性があり洗練されてたのかもしれません。
#レジスタ間接アドレッシングってPDP-1のINDビット以前は存在したんだろうか。。。ディレイスロットもそうだけど、プロセッサの歴史の中で誤った方向へ進んでしまった最初の一歩の1つのような気がする。