ラベル PDP11 の投稿を表示しています。 すべての投稿を表示
ラベル PDP11 の投稿を表示しています。 すべての投稿を表示

2026-04-30

SIMHのPDP-11エミュレータのメモリ参照は、16bitアドレス? 18bitアドレス? 22bitアドレス?

SIMHのPDP-11エミュレータを利用し、PDP-11/40上で動作するUNIXv6について学ぼうと考えています。まずは「m40.s」のラベル「start」から起動するプロセスを見ていこうと思います。ここではMMUが無効になっていますが、管理レジスタを設定し、MMUを有効にするための準備をおこないます。

 

「mov    $77406,(r1)+」のような箇所でMMUのレジスタに値を設定しています。ここでR1には「KISD0」が入っており、具体的には「172300」です。これは16bitアドレスとしての表現です。PDP-11/40のメモリ管理装置である「KT11-D」では、マニュアルの「Table 3-1 PAR/PDR Address Assignments」において「772300」のように定義されています。これは18bitアドレスです。ところがSIMHのPDP-11エミュレータでは、「17772300」のような22bitアドレスで参照しなければならないようなのです。

 

デバッガ機能を利用してメモリを参照した例を以下に示します。このように、16bitや18bitアドレスではMMUレジスタを参照できず、22bitアドレスで参照すると、格納されている値が参照できます。

Step expired, PC: 003370 (MOV #77406,(R1)+)
sim> s
Step expired, PC: 003374 (ADD R4,R2)
sim> e R1
R1:     172302
sim> e 172300
172300: 000000
sim> e 772300
772300: 000000
sim> e 17772300
17772300:       077406 

 

ちなみに、CPUは次のように定義しています。

sim> sh cpu
CPU, 11/40, NOFIS, idle enabled, stability wait = 20s, autoconfiguration enabled, 256KB 


エミュレーションするモデルを11/70としているのであれば、22bitアドレスでも納得するところです。しかし11/40として設定しているはずなの、22bitアドレスなのは、ちょっと不可解です。ただしSIMHが、そのような挙動なのは、そうなのですから、それを受け入れるしかありません。

2026-04-20

PDP-11のハードウェアTRAPにおけるモデル差異

PDP-11/40で動作するUNIXv6を、Lions本を頼りに学んでみようと考えています。まず手始めに「low.s」を調べています。この中でトラップベクタが定義されていますが、PDP-11のモデルによって違いがあるのではないかと気になりました。カーネルの動作環境は、SIMHのPDP-11エミュレータを使用するつもりですが、PDP-11/40を想定しています。しかし、PDP-11/45とかPDP-11/70という環境を考えることも、できなくはありません。PDP-11のモデルは他にもありますが、まずは11/40、11/45、11/70を考えると、モデルによる差異はどうなっているのでしょうか。

 

調査した資料は、PDP-11/40、11/45、11/70の『Processor Handbook』です。

 

まず、4番地から34番地にある7つのTRAPは、どのモデルにも存在します。また240番地と244番地の2つのTRAPも、どのモデルにもあります。しかし250番地のTRAPは、PDP-11/45、11/70にしかないようですし、114番地のTRAPは、PDP-11/70だけにしか存在しません。「low.s」では、これらのTRAPが全て定義されていますが、定義されていても別に無駄にはならないので、問題ではないのでしょう。

 

また各トラップベクタでは「trap; br7+5」のように記述されています。PDP-11のトラップベクタは、2ワード(4バイト)で構成されており、第1ワードが飛び先アドレス、第2ワードがPSWです。そして「br7」というのは割り込みのプライオリティを示していますが「+5」は何でしょうか。「low.s」の中では「br7+0」から「br7+9」まであります。

 

「trap」とは、「m40.s」の中にあるラベル「trap」のことで、Lions本の0755行にあります。この中で更に制御が移り、「trap.c」の「trap()」が呼ばれます。これはLions本の2693行にあります。ここで「+5」などを処理するために、switch-caseがあります。ただし「br7+4」と「br7+7」に相当するcase文はありません。これらはdefault文で処理されることになります。

 

しかも「low.s」には「br7+7」となっているトラップベクタが2つあります。Lions本の0538行と0547行です。これらは、「trap()」でも対応するロジックがありませんから、UNIXv6としては、未対応で十分という判断なのだと思います。今回UNIXv6のカーネルを学ぶに際しても、それ以上の調査は行いません。ただし将来的には、これらのTRAPの扱いがどうなっているのか、調べてみたい気がします。UNIXv7とかBSD2.11などでは、改良されているのでしょうか。それとも手付かずのままなのでしょうか。 

「4」とは?

PDP-11/40で動作するUNIXv6を学び始めました。カーネルを学ぶ入口は様々かと思いますが、まずは起動する過程を追いかけていこうと考えています。参考書はLions本で、オンラインで参照できる様々な資料も使います。実行環境は、実機があればよかったのですが(よくない?)、SIMHのPDP11エミュレータを使います。

 

カーネルの0番地に関連するのは、「low.s」です。Lions本では500行から599行までです。508行の0番地には「br 1f」があり、522行には「1: jmp start」があるので、カーネルの起動処理は、実質的には「start」から始まっているようです。

 

ここで509行目に目を移すと「4」とあります。これは何でしょうか?PDP-11の命令ではないし、疑似命令でもありません。何かの定数宣言でもなさそうですし、コメントが入っていないので、意図も分かりません。

 

『PDP-11/40 processor handbook』の「APPENDIX B MEMORY MAP」 には「INTERRUPT VECTORS」として、0番地から300番地あたりまでに定義されている割り込みベクターが記載されています。これらは、2ワード(4バイト)を組みとして、第1ワードが飛び先アドレス、第2ワードがPSWの設定内容となっています。ただし0番地は「RESERVED」となっているため、ここが割り込みベクターとはなっていないし、実際に「br 1f」という命令が置かれているわけです。仮に割り込みベクターと考えたとしたら、PSWが入るはずの場所に「4」があるわけですが、そもそも割り込みベクターではないので、PSWに設定する値ではないはずです。

 

「low.s」では、512行から「trap; br7+0」のように、PDP-11/40が定義した割り込みベクターが並んでいます。これらは4番地から始まるので、0番地の「br 1f」だけでは番地がずれてしまうので、何か埋め草が必要です。それが「4」のように見えるのです。

 

この「4」には、何か意味があるのでしょうか。「0」でも構わないのではないでしょうか。もしくは「4」を記述せずに、「. = 4^.」を明示した方が良かったのではないでしょうか。この疑問には答えがありません。強いて言えば開発者本人に聞くしかありませんが、もう60年くらい前のことですし、強いて思い出してもらわなければならないほど重要な問題でもありません。 

「klin: jsr r0,call; _klrint」とは何をしているのか

Lions本や、オンライン上にある情報を参考に、PDP-11/40で動作するUNIXv6について学んでいます。まずはカーネルが起動する過程から調べていこうと思い、「low.s」を見ています。これは機械的に生成されたものである点に注意しておかなければなりません。実行環境は、当然ならが実機を所有していないので、SIMHのPDP11エミュレータを用いて、「Installing UNIX v6 (PDP-11) on SIMH」の手順で作成された環境を利用します。 

 

Lions本に掲載されている「low.s」は、実行環境とは構成されているデバイスが違います。しかし「klin: jsr r0,call; _klrint」のようなエントリが並んでいるのは同様です。このようなロジックを考え付いた経緯はわかりません。他の実装も考えられるかもしれません。僕が疑問を感じる(と言っても、この実装が駄目だと言っているわけではないのですが)のは「JSR(Jump to Subroutine)命令を使っているのに、この場所に戻ってくることは想定していない」からです。もし本当にサブルーチンコールを意図しているなら、何らかの処理をしたら、この場所に戻ってくるわけですが、「low.s」では各デバイスのためのJSR命令が並んでいるだけですから、それを順番に処理しても意味がありません。このJSR命令は、戻ってこないのが前提であり、むしろJMP(Jump)命令のような動作を期待しているはずです。

 

さらにJSR命令の後に続く「_klrint」は何でしょうか。これは、Lions本であれば8078行にある「klrint()」のことです。そしてJSR命令で呼び出される「call」(Lions本の776行)には「jsr pc,*(r0)+」があります。PDP-11のJSR命令は、x86のCALL命令とは異なり、戻り番地がスタックに積まれるとは限りません。特に今回のように「jsr r0,call」となっている場合、戻り番地は、レジスタR0にあります。と言うことは、R0が指している場所には「_klrint」があります。つまり「jsr r0,call; _klrint」では、形式的にサブルーチンコールとして「call」が呼ばれ、その中で「jsr pc,*(r0)+」として、「_klrinit」に制御が移ります。最終的に呼び出し元に戻ってくることはありません。

 

なお「jsr pc,*(r0)+」において、R0の内容が変化しますが、これは副作用です。PDP-11に「jsr pc,*(r0)」のような指定が存在しないだけであって、カーネルとしてR0が変化するのを期待しているわけではありません。 

2026-04-16

Lions本とUnix-v6-Ken-Wellsch.tapとではデバイス構成が異なる

SIMH上のPDP-11エミュレータでUNIXv6カーネルを学ぼうと考えています。まずは、「low.s」と「m40.s」から手を付けていきます。参考書は、オンライン上の資料もありますが、やはり定番のLions本です。UNIXv6環境は、「Installing UNIX v6 (PDP-11) on SIMH」に従い、「Unix-v6-Ken-Wellsch.tap」を利用するつもりです。

 

「low.s」は「mkconf.c」により生成されるものなので、UNIXv6のソースツリーには含まれていません。Lions本には「low.s」が掲載されていますが、これがSIMH上の環境と合っているとは限りません。まずSIMHのデバッガ―を利用して、メモリ上に置かれたカーネル「rkunix」を調べてみました。

 

確認したところ、カーネル「rkunix」は、以下のデバイスを有効にしていると見られます。これは「run」の内部で「rkunix」を生成する箇所と一致します。

  1. RK11
  2. TM11
  3. TC11 

 

一方で、Lions本は、0500番台の行番号がついている「low.s」によれば、以下のデバイスが有効になっています。

  1. RK11
  2. LP11
  3. PC11

 

ディスク操作は、どちらもRK11ですが、端末関係が違います。このような違いも含めて、UNIXv6のカーネルを学んでいこうと思います。 

2026-04-11

SIMHを使用してUNIXv6カーネルを学ぶための準備

SETTING UP UNIX - Sixth Edition」に記述されている「Making a Disk From Tape」と「Booting UNIX」の手順について、内部処理を確認してきました。これをおこなうことで、UNIXv6のソースファイルにも、PDP-11の動作についても、分かってきました。さらにSIMHのPDP-11エミュレータ内蔵デバッガについても、理解が進みましたので、いよいよカーネル本体を学ぶ練習としては、ちょうど良かったと思います。

 

これからカーネル本体について学んでいきますが、Web上の様々な情報を参照するつもりですが、主たる参考書はもちろんLions本です。ソースファイルを参照するには「The Unix Heritage Society」のサイトにあるものを使おうと考えています。

 

カーネル本体は、これまで見てきたブートローダに比べると大きいし、アセンブラではなくC言語が使われています。SIMHのデバッガは、UNIXv6カーネルのシンボル情報を参照できません。闇雲にデバッガを操作しても、深みにはまるだけで、何も得られないと思います。なんとならないかとGeminiに相談したら、コマンド「nm」を使って、シンボル情報リストを入手しておくことを勧められました。「nm -n rkunix」を実行して得られたリストを手元に置いて参照することにしました。今の時代なら、シンボリックデバッグを使えばローカル変数でも何でも参照できますが、UNIXv6時代では、そこまで求めることはできません。でも、関数のエントリポイントのアドレスが分かるので、最低限の参考にはなりそうです。

 

カーネルの調査を進めていくため、調べたこと、分かったこと、分からないこと等々を記録していく必要がありますが、TW5を利用するつもりです。取得したシンボルテーブルもTW5に記録しておくつもりです。そのままでもシンボルを検索できないわけではないと思いますが、もう少しなんとかならないかと思います。Geminiに相談したら、簡単なスクリプトを生成してくれました。これを使えば、検索ボックスでシンボル名かアドレスを入力すると、対応するアドレス(またはシンボル名)を見つけてくれます。ミニツールではありますが、便利に利用できそうです。 

2026-04-09

rkubootのbufは、もうひとつ欲しい

SIMHのPDP-11エミュレータを使ってUNIXv6について調べています。RK05にインストールされたカーネルを起動するには、ディスクの先頭にあるブートローダ「rkuboot」が使われています。これの主な処理は「fsboot.s」にあります。これはファイルシステムを解釈して、指定されたカーネルのiノードを取得し、その中で参照されているディスクブロックをメモリ上にロードします。UNIXv6のカーネルは、今の目から見れば小さいですが、さすがに直接参照だけでは足らないので、間接参照が使われています。

 

処理の概要は、間接参照のディスクブロックをひとつずつ見ていき、その指し示しているディスクブロックを順番にメモリ上に持ってきます。ここで気になるのが、間接参照のディスクブロックを見る際も、カーネルに相当するディスクブロックを得る際も、512バイト分として確保されている領域「buf」を使っていることです。

 

カーネルに対応するiノードは、メモリ上に確保された「inod」に置かれますから、特に問題はありません。そのiノードの中から間接参照によるディスクブロック番号を得るために、「buf」を使用します。そしてカーネルの一部があるディスクブロック番号が判明すると、その内容を「buf」に持ってくるのですが、こうすることで、先ほどの間接参照として使っていた内容は上書きされてしまいます。「buf」にあるのはカーネルの一部ですから、それをメモリのしかるべき位置にコピーしています。カーネルの次のディスクブロックを得るには、再びiノードを参照して間接参照によるディスクブロック番号を取得する必要がありますが、「buf」には情報がありませんから、またしてもrmblkで読み込む必要があります。この繰り返しが、カーネル全体を読み込むまで続きます。

 

ひとつの「buf」を使いまわしているから、こうなってしまうのではないかと思います。UNIXv6当時のCPUやメモリリソースが限られていたとしても、「buf」ひとつだけで処理しないで、もうひとつあれば良いのにと思うところです。 

rkubootにおける直接参照も間接参照も、カーネルをブートできれば良いと割り切っている

SIMHのPDP-11エミュレータを使いUNIXv6について学んでみようと思っています。マシンが起動され、RK05にインストールされたカーネルがメモリ上にロードされるには、ディスクの先頭にあるブートローダ「rkuboot」が使われます。この主処理は「fsboot.s」にあります。このローダがファイルシステムを解釈して、カーネルを探して、メモリ上に配置し、制御を移すことで、カーネルが動き出します。

 

ファイルシステムを解釈するには、iノード、ディスクブロックの直接参照や間接参照などを取り扱う必要があります。その処理はルーチン「rmblk」にあるようです。UNIXv6当時のファイルシステムは、今日とは異なり、「LARGEフラグ」をみて、直接参照と間接参照を切り替えていたようです。さらにコメントには「huge algorithm is not implemented」とあり、二重間接参照は実装していません。

 

ブートローダの目的は、UNIXのファイルシステムを正確に取り扱うことではなく、カーネルさえ読み込めれば十分であるという割り切りがあるようです。そうであるなら、当時のカーネルは小さなファイルだったので、さすがに直接参照では扱えませんが、間接参照だけで読み込める程度の大きさだったのでしょう。二重間接参照が必要となるほど大きなファイルにはならなかったので、それを実装したところで絶対要らないのが明らかですから、余計なことはしていないのでしょう。

 

しかも、直接参照にしろ間接参照にしろ、ディスブロックとして得られた値が「0」になっていれば、そこで読込みを終えることになります。そのためのロジックは入っていますし、「bno = buf+514.」のように、何故か512+2が指定されていることからわかるように、それなりに考慮はされているようです。しかし、直接参照にしろ間接参照にしろ、いかなる場合も絶対に問題が起きないように念入りに対処するというよりは、「カーネルを読むだけだから、細かいところは気にしなくても良いだろう」と割り切っているのではないかと思われるロジックに見えます。

 

ブートローダは512バイト以下におさめる必要がありますから、エラーチェックをしっかりとか、ファイルシステムを完璧に実装しようなどという理想に走ると、容量をオーバーしてしまうでしょう。問題があることはわかっているけど、仕方ないよねという割り切りを感じるのです。 

リッチーとトンプソンによる「The UNIX Time-Sharing System」は2つある

PDP-11で動作したUNIXv6について学ぼうとすると、真っ先に挙げられるのは「Lions本」と呼ばれる『Lions' Commentary on UNIX』です。僕自身も、アスキー出版局から出た和訳を参考にしています。その中には、次のような箇所があります。

  • 『The UNIX Time-sharing System』はオリジナルの『Communications of the ACM』の論文の改訂版である。1か月に少なくとも一度は再読すべきである。

  

この一文の前半にある「論文の改訂版である」というのは、一読しても何のことか分かりませんが、後半の「再読すべきである」は、よく分かります。暗記してしまうくらい熟読せよという意味でしょう。これはネットを検索すれば見つかるので、手元に置いておき、仰せの通りに何度も読み直そうと思います。

 

UNIXv6のブートローダを調べていると、ファイルシステムの構造として「LARGEフラグ」というものがあり、それに応じて、直接参照と間接参照を切り替えていることを知りました。今日でもファイルシステムでは直接参照と間接参照がありますが、「LARGEフラグ」というものは存在しません。何時ごろ無くなったのだろうかとGeminiに尋ねてみたら、UNIXv7で変わったとの回答を得ました。ということは、LARGEフラグがあるのはUNIXv6が最後ということになります。

 

しかもGeminiがいうには、「The UNIX Time-Sharing System」には、1974年のCACM版と、1978年のBSTJ版があるそうです。その「4 Implementation of the file system」の記述内容が大きく変わっているとのことでした。あらためてネットから入手し直してみると、タイトルも著者も同じですが、内容が異なる2つの論文があることを確認しました。

 

UNIXの系統樹などを見ると、BSD版はUNIXv6から派生したように書かれています。直接的にはそうなのかもしれませんが、UNIXv7で加えられた変更をBSDが取り込んだタイミングがあると思います。それは何時だったのでしょうか。BSDの旧いソースを追いかけると、わかるかもしれません。 

rmblkはリターンアドレスを操作する

SIMHのPDP-11エミュレータを利用し、RK05にインストールされたUNIXv6のブートローダ「rkuboot」について調べています。これは「run」により作られますが、主たる処理は「fsboot.s」にあります。この中にあるルーチン「rmblk」は、サブルーチン呼び出しのリターンアドレスを操作しているようです。

 

rmblkが呼び出されると、まず「add $2,(sp)」があります。このままサブルーチンを終える場合もありますが、場合によっては、戻る直前に「sub $2,(sp)」しています。一方でrmblkの呼び出し元では「jsr pc,rmblk」の直後に「br callout」と「mov $buf,r1」が置かれています。普通なら、JSR命令で呼び出したサブルーチンから戻ってくると、その次の命令(ここであれば、BR命令)が実行されることになります。しかしrmblk側でリターンアドレスを操作しているので、戻り方に依っては命令を飛ばし(BR命令は実行されず) 、MOV命令から実行されます。かなり職人芸と言える技だと思います。

 

PDP-11上でUNIXv6が動作していた時代は、現在から比べると、CPU性能もメモリ容量も極めて限られていたので、このような芸当を駆使していたのだと説明されることがあります。確かにその通りかもしれません。そうであったとしても、このような芸当に走るのは、ちょっとやりすぎではないかと思わないでもありません。rmblkが正常終了したか異常終了したかに依って処理を分岐させたいのであれば、他の方法もあるだろうし、そうすることが困難だとも思えません。 

rkubootにはmbootのような行入力の特殊文字がない

SIMHのPDP-11エミュレータを利用し、「SETTING UP UNIX - Sixth Edition」における「Making a Disk From Tape」から「Booting UNIX」の動作を追体験しています。配布されたテープイメージをディスクにコピーする段階では、テープの先頭にあるmbootにより、ディスクからブートする段階では、ディスクの先頭にあるrkubootにより、キー入力された名前のプログラムを探し、それに制御を移しているようです。mbootもrkubootも、Geminiに助けてもらいながら、ロジックの詳細は理解できました。

 

mbootもrkubootもスクリプト「run」で作られます。mbootの中心となるのは「tpboot.s」で、rkubootの中心となるのは「fsboot.s」で、どちらも同じような作りになっています。ただしよく見ると、tpboot.sでは、キー入力を間違った場合の簡易編集機能として「@」や「#」を扱う処理が入っています。しかしfsboot.sにはありません。

 

mbootにしろrkubootにしろ、キー入力時に簡易編集機能が必須というわけではないと思います。あればあったで便利かもしれませんが、なければなくても別に困らないと思います。また簡易編集機能を処理に組み込んだとしても、些細なロジックですから、ブートローダの512バイト制限を気にするほどのことではないと思います。

 

おそらく簡易編集機能は、あってもなくても構わない程度の、おまけ機能なのでしょう。 

2026-04-08

rkubootの動作

SIMHのPDP-11エミュレータを使用してUNIXv6について学んでいます。同様の試みはオンラインでも書籍でも数多く存在していますが、カーネル本体がメモリ上にロードされた以降を対象にしているようです。もちろんそれで何の問題もないのですが、いきなりカーネルを学ぶよりも、助走のつもりで、ディスク上のブートローダがカーネルをメモリに読み込むところから調べてみることにしました。UNIXv6のブートローダは、ディスク毎に分かれているようですが、RK05用のrkubootを対象にします。

 

rkubootはスクリプト「run」で作られています。主要部分は「fsboot.s」です。ブート手順については「SETTING UP UNIX - Sixth Edition」の「Booting UNIX」に書かれているとおりです。まずPDP-11のフロントパネルからブートローダをメモリ上に置くプログラムを入力し、実行します。そして読み込まれたブートローダを実行すると、プロンプト「@」が出力されるので、カーネル名称として(例えば)「rkunix」を入力します。こうするとブートローダがカーネルを読込み、制御を移し、カーネルが動き出します。

 

ブートローダ「rkuboot」の概要は上述したとおりですが、「fsboot.s」を追いかけてみると、次のような処理をしていました。

  1. 0番地に置かれている自分自身を137000番地に移す。こうしておかないと、カーネルを読み込もうとした際に、ブートローダ自身が上書きされて壊れてしまいます。
  2. プロンプト「@」を出力し、カーネルのパス名を入力させまる。ここで、mbootにあったような特殊文字「#」や「@」のハンドリングはありません。またカーネルが階層ディレクトリに置かれている場合でも対処できる処理になっています。
  3. 入力されたカーネル名称をファイルシステムから探します。FILE SYSTEM(V)にも明記されているとおり、i番号の「1」がルートディレクトリなのが決まりですから、それを前提に探します。そしてルートディレクトリ(またはサブディレクトリ)に指定されたカーネルが見つかれば、そのi番号をからi-nodeを参照してディスク上のカーネルをメモリに読み込みます。

 

ブートローダですから、上記した内容が全てですが、これらの処理を512バイト以内に納める必要があります。あまり複雑な処理とか、エラーチェックとかをしている余裕がないようで、ある種の「割り切り」があるように思います。例えば、fsboot.sのコメントには「huge algorithm is not implemented」とありますが、これは今日のUNIXにもある「二重間接参照」は実装されていないということのようです。実際問題として、二重間接参照が必要となるほどカーネルは大きくないので、実装しなくても実用上問題ないという「 割り切り」があるのでしょう。他にもエラーチェックなどをしない箇所がありますが、それも「普通そういう事態にはならないはず」という「割り切り」があるのだと思います。

2026-03-28

「Making a Disk From Tape」と「Booting UNIX」の対称性

SETTING UP UNIX - Sixth Edition」の「Making a Disk From Tape」と「Booting UNIX」は、手順が対称的になっていることに気づきました。

 

「Making a Disk From Tape」では、細部を省略すると、次のような手順です。

  1. TU10コードをフロントパネルから入力して実行する。
  2. mbootがロードされるので、実行されると、プロンプト「=」が出力される。
  3. tmrkなどを入力することで、テープからディスクにコピーが行われる。

 

これに対して「Booting UNIX」では、次のような手順です。

  1. RK05コードをフロントパネルから入力して実行する。
  2. rkubootがロードされ、実行されると、プロンプト「@」が出力される。
  3. rkunixなどを入力することで、ディスクからカーネルが起動する。

 

「Making a Disk From Tape」の処理を追いかけてくることで、UNIXv6が動作する以前の世界が理解できました。

RK05は2種類ある

「RK05 disk drive maintenance manual」(DEC-00-HRK05-C-D)に記載されている「Table 1-1 Performance Specifications」には「Sectors(records)」として「4872 (12 per revolution)/6496 (16 per revolution)」とあります。これは何でしょうか。

 

RK05は、片面203シリンダ(含予備)なので、両面ならば406トラックあります。406×12=4872ですし、406×16=6496です。計算は合っていますが、どういう意味でしょうか。Geminiに相談したら、RK05は、1シリンダが12セクタの製品と16セクタの製品が存在したのだと回答しました。そうなのかもしれません。しかし裏付け資料が見つからないので、真偽が定かではありません。

 

UNIXv6の「rk.s」には、「div $12.,r0」という処理があります。この結果がRKDAレジスタ(777412番地)に入ることになるので、12セクタのための処理なのでしょう。しかし、もし16セクタのRK05があるとするならば、その容量を活かせていないことになります。

 

RK05に12セクタ版と16セクタ版が存在すると仮定して、UNIXv6が自動判別する処理を組み込むのは、当時の歴史的事情としては許されなかったのではないかと推測します。どちらかい決め打ちするのであれば、12セクタ版にしておいた方が無難と判断したのかもしれません。 

 

 

tm.sのtreadとrk.sのwblk

スクリプト「run」で作られる「tmrk」の処理の中心は「mcopy.s」にあります。最も重要なのは、「tread」と「wblk」を呼び出して、512バイト(256ワード)のコピーを指定回数分だけ繰り返しているところです。

 

「tread」は「tm.s」にあります。「wblk」は「rk.s」にあります。両者を見比べると、テープを操作するかディスクを操作するかという事だけではなく、大きく違うのですが、論理的には対称になっているはずです。「wblk」の方がシンプルで、「tread」は複雑です。その複雑さに目を奪われてしまうことなく、対称性に注意すれば、「tread」が何をしようとしているのか見えてくると思います。 

mcopy.sにおいて「illegal digit」が出力された場合の挙動

SETTING UP UNIX - Sixth Edition」の「Making a Disk From Tape」の手順に従って「tmrk」を呼び出すと、「disk offset」、「tape offset」、「count」の入力が求められます。これは「mcopy.s」のルーチン「numb」が呼ばれることで、キーボードから入力された数値がR0に入ります。ここで数字以外を入力すると「illegal digit」というエラーメッセージを出力して、「rts pc」が実行されます。ここで気になるのは、一体何処にもどるのでしょうか。

 

「tmrk」の中で「jsr pc,numb」として呼び出されている訳ですが、「tmrk」の中に戻るわけではないようです。エラー発生時に処理を追いかけていくと「tst (sp)+」をした後で「rts pc」となっています。この「tst (sp)+」が重要です。オペコード「TST」というのは、オペランドを評価してフラグをセットする命令です。普通は直後に条件分岐命令が続きます。しかしここでは「rts pc」が続いていて、フラグの状態には影響されません。

 

要するにここでは、TST命令である必要はなく、「(sp)+」として、SPが変化する事が重要なのです。SPを変化させることで、「tmrk」から呼び出された際の戻り番地を捨て、その上位にある「mboot」に戻るためのテクニックだと思います。そうなら、それが分かりやすいプログラムを書けば良いではないかと思うところですが、こういう職人芸的なプログラムを書くところが当時の時代背景としてあったのでしょう。 

「SETTING UP UNIX - Sixth Edition」の「Making a Disk From Tape」におけるmbootとtmrkの関係

SIMHのPDP11エミュレータを利用し、UNIXv6について学ぼうと考えています。同様の志を持つ方は少なくなく、オンラインでも書籍でも数多くの情報があります。しかしながら、それらはUNIXv6がディスクにインストールされていることを前提としており、「SETTING UP UNIX - Sixth Edition」の「Making a Disk From Tape」について言及されていないように思います。それでも別に構わないのですが、いきなりカーネル本体に取り組むより、例え多くの情報が蓄積されているとしても、まずはPDP-11のCPU本体や周辺機器の扱いに慣れる意味で、「Making a Disk From Tape」の手順や、その処理に精通しておくことにしました。

 

「Making a Disk From Tape」では、TU10の先頭ブロックをロードするコードが提示されています。PDP-11実機であれば、これをフロントパネルから入力し実行することで、配布テープの先頭ブロックがメモリにロードされます。これは、スクリプト「run」で作成される「mboot」であることを突き止めました。これが実行されると、プロンプト「=」が出力され、入力待ちになります。導入手順では「tmrk」と入力することになっていて、これをおこなうことで、配布テープからディスクにUNIXv6がコピーされます。

 

「mboot」も「tmrk」もスクリプト「run」によって作成されるa.out形式ファイルです。しかし両者は独立している訳ではないようで、「mboot」が主、「tmrk」が従の立場で動いているようです。このような主従関係と考えるのは、以下のような挙動だからです。

 

まず「mboot」が0番地からロードされ、実行されると、まず最初に自分自身を137000番地に移して、以降はそちらで実行を続けます。これは、「tmrk」などが0番地からロードされた際に、「mboot」が壊されないためと思われます。この挙動から、「mboot」が主で、「tmrk」が従と見做せます。 

 

さらに「mcopy.s」のでは、文字列を出力するため「jsr pc,4(r5)」のようなサブルーチン呼び出しが存在します。ここでR5は、「tmrk」では設定されていません。「mboot」の方で設定されていることが前提となっています。このような前提で「tmrk」が動作している事実が、主従関係があると判断する理由です。

 

また「tmrk」が実行されると、「disk offset」、「tape offset」、「count」の入力を求め、その入力に応じてテープからディスクにコピーすると、実行を終了してしまいます。実行終了後にどうなるかと言うと、「mboot」に戻るのです。この挙動を見ても、「mboot」が主で、「tmrk」が従だとわかります。

 

「Making a Disk From Tape」の手順6では、「tmrk」の代わりに「htrk」、「tmrp」、「htrp」などを使う場合の記述があります。これらのプログラムは調査していませんが、おそらく「tmrk」と同様だろうと思います。 

2026-03-22

mbootとUnix-v6-Ken-Wellsch.tap

SETTING UP UNIX - Sixth Edition」の「Making a Disk From Tape」で行われた処理について調べています。ここで書かれている手順の概略は次の通りです。

  1. PDP-11のフロントパネルを操作し、100000番地からTU10コードを入力し、実行する。
  2. テープから先頭ブロックが0番地に読み込まれるので、実行する。これは「mboot」です。
  3. 「mboot」の中でプロンプト「=」が出力されるので、テープの内容をディスクに書き込むプログラム名を指定する。ドキュメントでは「tmrk」が指定されています。「mboot」は、指定されたプログラム「tmrk」をテープから探し、メモリにロードし、制御を移します。
  4. 「tmrk」が実行されると、指定位置から指定長をテープから読み込み、ディスクに書き出します。

 

UNIXv6が出た当時は、これらの手順を実機PDP-11/40などでおこなったのでしょうけれども、21世紀に生きる我々はSIMHのPDP11エミュレータを使います。そしてテープイメージには、ファイル「Unix-v6-Ken-Wellsch.tap」を使います。

 

「mboot」は、スクリプト「run」によると、「tpboot.s」、「tty.s」、「tm.s」からできています。これらのソースを参照し、SIMHのPDP11エミュレータのデバッグ機能を駆使し、時々Geminiとの対話を繰り返しながら、解析しました。なんとか「mboot」の動作は理解できた気がします。動作の概略は、以下のようになっています。

  1. TU10コードにより、「mboot」はメモリ番地0からロードされます。「mboot」の目的は、ユーザが指定した「tmrk」などのプログラムをメモリ番地0からロードして制御を渡すことなので、このままではよくありません。そこで自分自身を上位に移し、プログラムがロードされる場所を空けておきます。上位のメモリ番地は137000のようです。
  2. プロンプト「=」を出力し、ユーザからプログラム名を入力してもらい、それをテープから検索します。
    1. プログラムを入力する際、「@」が入力されると、それまでの入力が全て取り消されます。また「#」が入力されると、その直前の入力が取り消されます。
    2. 入力された文字列(「tmrk」など)がテープに存在するか検索します。テープファイル「Unix-v6-Ken-Wellsch.tap」を調べたところ、先頭ブロックには「mboot」が格納されていますが、その次には「hboot」が入っていることがわかりました。その次からTP(V)形式のディレクトリが続きます。PDP11のデバッグ機能を使って動作を確認すると、「hboot」が格納されているブロックから検索を始めていることが分かりました。TP(V)形式のディレクトリではないし、そもそも何故「hboot」がテープに置かれているのか分かりません。 
  3. テープ上の場所が分かったら、それをメモリ番地0からロードして、制御を渡します。ただしロードする前に、メモリを全て0クリアしています。未初期化の変数を参照してしまった場合、再現不能なバグが出ないための事前準備でしょうか。

 

テープファイル「Unix-v6-Ken-Wellsch.tap」を解析すると、次のようになっていました。

  1. Block#0は、a.out形式の「mboot」が格納されている。
  2. Block#1は、a.out形式の「hboot」が格納されている。
  3. Block#2からは、TP(V)形式のディレクトリ情報が格納されている。格納されているプログラムは、次の通りです。
    1. 「dldr」(size:000000344,tape:000031)
    2. 「dtf」(size:000004546,tape:000032)
    3. 「hboot」(size:000000724,tape:000037)
    4. 「hpuboot」(size:000000766,tape:000040)
    5. 「hthp」(size:000000624,tape:000041)
    6. 「htrk」(size:000000554,tape:000042)
    7. 「htrp」(size:000000566,tape:000043)
    8. 「mboot」(size:000000674,tape:000044)
    9. 「mcopy」(size:000000660,tape:000045)
    10. 「mem」(size:000012756,tape:000046)
    11. 「reset」(size:000000024,tape:000061)
    12. 「rkf」(size:000000200,tape:000062)
    13. 「rkuboot」(size:000000716,tape:000063)
    14. 「rpuboot」(size:000000730,tape:000064)
    15. 「tboot」(size:000000626,tape:000065)
    16. 「tcf」(size:000004516,tape:000066)
    17. 「tmhp」(size:000000574,tape:000073)
    18. 「tmrk」(size:000000524,tape:000074)
    19. 「tmrp」(size:000000536,tape:000075)
    20. 「uboot」(size:000001000,tape:000076)

2026-03-19

Unix-v6-Ken-Wellsch.tapの構造

SIMHのPDP11エミュレータを使ってUNIXv6を学ぼうと考えています。同様の志を持つ人たちが、ネットでも書籍でも多くの情報を提供しています。ただしUNIXがインストールされた環境を前提にしていることが多いように見受けられます。カーネルを学ぶには、インストールが済んだことを前提にする方が良いのだと思いますが、PDP-11について学ぶ練習のため、「SETTING UP UNIX - Sixth Edition」の手順でインストールするところから調べてみようと思います。

 

 SIMHで利用できるテープイメージのファイルが「Unix-v6-Ken-Wellsch.tap」として入手できます。これはSIMHで扱える形式になっているようです。その構造は「SIMH Magtape Representation and Handling」という資料があります。この構造を確認するため、Pythonでスクリプトを組んでみました。

#!/usr/bin/python3

def readblock(f):
    global TapeMark
    global Block
    Block += 1
    len0 = int.from_bytes(f.read(4),'little')
    if len0 == 0:
        TapeMark += 1
        if TapeMark >= 2: raise EOFError
        return 0
    TapeMark = 0
    data = f.read(len0)
    len1 = int.from_bytes(f.read(4),'little')
    print("[%6d]:" % Block, TapeMark, len0, data[:4])
    return 0 if len0 != len1 else len0

TapeMark = 0
Block = 0

with open("Unix-v6-Ken-Wellsch.tap","rb") as f:
    while True:
        try:
            readblock(f)
        except EOFError:
            break
#[EOF]

 これで確認すると、512バイトのブロックが12,100あります。ドキュメントに書かれているとおりです。

 

また先頭ブロックには「mboot」があります。次のブロックには「hboot」が置かれています。その後には「TP(V)」形式のディレクトリが続きます。ブートプログラムとして「mboot」と「hboot」の二つが用意されている理由はなんでしょうか。ドキュメントでは、フロントパネルから入力する「TU16」のプログラムが「(To be added)」となっています。このテープの配布を受けた側がTU10でもTU16でも対応できるようにしておいたのかもしれません。

2026-03-14

TU10コードからmbootを経由してtmrkが呼ばれるまで

SETTING UP UNIX - Sixth Edition」の「Making a Disk From Tape」において、手順3で入力されたコードを実行すると、テープからプログラムがロードされます。simhのPDP11エミュレータを使用し、テープイメージとして「Unix-v6-Ken-Wellsch.tap」を使用する場合、このロードされたプログラムは「mboot」だと思われます。これは「run」の中で、以下のようにして作られています。

as tpboot.s tty.s tm.s
strip a.out
ls -l a.out
cp a.out /usr/mdec/mboot 

 

そして手順5において、プロンプト「=」が表示され、「tmrk」を入力するように指示されています。

 

この一連の処理を追いかけているのですが、「tpboot.s」では、プロンプト「=」を出し、「TP(V)」形式のテープから指定されたプログラム(例えば「tmrk」)を読みだして、制御を移しているだけです。 

 

つまり、「Making a Disk From Tap」では、次のような段階を踏んで、テープにあるUNIXv6をディスクにコピーしていることになります。

  1. フロントパネルからTU10コードを入力し、実行する。
  2. テープの先頭ブロックにmbootが入っており、それを実行する。
  3. mbootがプロンプト「=」を出し、コマンド「tmrk」などを入力すると、mbootがテープから探し出し、メモリ上にロードする。
  4. メモリ上にロードされたプログラムが、テープからUNIXv6本体をディスクにコピーする。

 

 処理の流れが見えてきました。これだけわかれば十分とも言えますが、mbootに含まれるtpboot.s、tty.s、tm.sは、PDP-11のアセンブラで書かれていますが、短いので、今後UNIXv6の本体を追いかけていく練習として、ロジックを細部まで見ていこうと思います。