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

2026-08-07

東大版Palo Alto Tiny BASICでTiny Trekが動いたけど、遊び方が分からない

simhのAltair 8800シミュレータを使って東大版Palo Alto Tiny BASICが動いています。またAutoHotkeyを使って、長いBASICソースを入力させることが出来るようになりました。そしてTiny Trekを動かしてみたところ、あっさり動作しました。それは嬉しいことなのですが、遊び方が分かりません。


>RUN
DO YOU WANT A DIFFICULT GAME?  (Y OR N):N
STARDATE 3200:  YOUR MISSION IS TO DESTROY 8 KLINGONS IN 30 STARDATES.
THERE ARE 2 STARBASES.
ENTERPRISE IN Q-65 S-83
CAPTAIN:G
ENTERPRISE IN Q-65 S-83
COMPUTER DISPLAY OF GALAXY MAP

1:   0   0   0   0   0   0   0   0

2:   0   0   0   0   0   0   0   0

3:   0   0   0   0   0   0   0   0

4:   0   0   0   0   0   0   0   0

5:   0   0   0   0   0   0   0   0

6:   0   0   0   0   0   0   0   0

7:   0   0   0   0   0   0   0   0

8:   0   0   0   0   0   0   0   0
    ..  ..  ..  ..  ..  ..  ..  ..
     1   2   3   4   5   6   7   8

CAPTAIN:R
STATUS REPORT:
STARDATE      3200
TIME LEFT     30
CONDITION     GREEN
POSITION      Q-65 S-83
ENERGY        4000
TORPEDOES     10
KLINGONS LEFT  8
STARBASES     2
CAPTAIN: 

クリップボードの限界とAutoHotkey

simh上のAltair 8800シミュレータ上で東大版Palo Alto Tiny BASICが動作するようになりました。「Hello World」程度の簡単なプログラムを動かすなら何とも思いませんが、Tiny Trekのような(比較すれば)大きなプログラムを動かそうとすると、いちいちプログラムを入力するのは大変です。そもそもTiny BASICにはsaveやloadコマンドが存在しません。

 

本来のPalo Alto Tiny BASICでは、ASR-33の紙テープを使って、事前に作成済みの紙テープを使うことで、大きなプログラムを動かすことができたようです。simh上のAltair 8800シミュレータでは、紙テープのためのPTR/PTPデバイスは備えられていますが、本家も東大版も対応していません。対応できるように改造するという方法は考えられますが、まずは別の方法で対処しようと思います。

 

まず考えたのはクリップボードを利用する方法です。プログラムを事前にクリップボードに入れておけば、Altair 8800シミュレータ上で動作中の東大版Palo Alto Tiny BASICにクリップボードからコピーすれば、うまくいくと思いました。ちょっと試してみたところ、うまくいくようです。ところが、Tiny Trekのような大きなプログラムでは、クリップボードからのコピーが早すぎて、途中で文字の取りこぼしが発生してしまいました。

 

解決策を探していたら、AutoHotkeyというものが使えそうという感触を得ました。ちょっとしたスクリプトを作成することで、指定したファイルの内容を、あたかもキー入力したかのように、アプリケーションに送ることができるようです。以下に示すようなスクリプトをGeminiに作ってもらいました。試行してみたところ、うまくいったようです。

 

 #Requires AutoHotkey v2.0

waitMs := 1000
selectedFile := FileSelect(16, A_MyDocuments . "\PaloAltoTinyBASIC\Altair8800", , "BASファイル (*.bas)")
if (selectedFile == "")
    return

try {
    Loop read, selectedFile, "UTF-8"
    {
        currentLine := A_LoopReadLine
        SendText(currentLine)
        Send("{Enter}")
        Sleep(waitMs)
    }
} catch as err {
    MsgBox("処理中にエラーが発生しました: " . err.Message, "エラー", "Icon!")
}

2026-07-27

紙テープとクリップボード

DDJで発表されたPalo Alto Tiny BASICは、ASR-33のようなテレタイプを入出力装置として想定しています。その派生である東大版Palo Alto Tiny BASICをsimhのAltair 8800シミュレータで動作するようになりました。さて、Tiny BASICが一世を風靡した頃には「Hello World」というプログラムを書いてみるという習慣はありませんでした。テスト的に簡単なプログラムを入力し、実行できるようにはなりましたが、Palo Alto Tiny BASICには、saveやloadというコマンドはありません。しかし、入力したプログラムを保存できないのは、困ります。さらに、過去に作成したプログラムを読み込めないのは、もっと困ります。

 

ここで当時の状況を思い起こすと、ASR-33のようなテレタイプには、紙テープのリーダーやパンチャーがついていたという事実です。ASR-33の操作方法は分かりませんが、あたかもキーボードから入力しているかのように紙テープを入力装置としたり、あたかも印字するかのように紙テープを出力したりすることが出来たようです。つまりPalo Alto Tiny BASICから見ると、入力がキーボードなのか紙テープなのか区別できませんし、出力がプリンタなのか紙テープなのか区別できません。こういう機構があれば、Palo Alto Tiny BASICにsaveやloadコマンドがなくても、紙テープを記録メディアとして利用できることになります。

 

それでは、Altair 8800シミュレータでPalo Alto Tiny BASICの場合、「紙テープ」の代替は何でしょうか。いろいろな手法が考えられるかもしれませんが、「クリップボード」が使えそうです。あらかじめエディタか何かで、プログラムを編集しておき、その全てをクリップボードにコピーしておきます。次にAltair 8800シミュレータで動作しているPalo Alto Tiny BASICにペーストすれば、いかにもキーボードから入力したかのように、プログラムが一気に入力される(はず)です。つまり現代のクリップボードは、当時の紙テープを再現していると言えるでしょう。

 

Palo Alto Tiny BASICが流行した当時は、何故かStar Trekゲームも流行していました。Palo Alto Tiny BASICを開発したLi-Chen Wangは、Tiny Trekも発表しています。Tinyなので130行ほどしかありませんが、ゲームを遊ぶたびに、いちいちキーボードから入力しなければならないのは嬉しくありません。当時は紙テープから一括入力したようですが、現代ならクリップボードの出番です。これで一括入力できるはずです。

2026-07-22

東大版Palo Alto Tiny BASICのメモリマップ

共立出版の『マイクロコンピュータのプログラミング』に掲載されている「東大版Palo Alto Tiny BASIC」は、DDJに掲載されたオリジナルのPalo Alto Tiny BASICを拡張したものです。しかし記事に書かれているように、「Palo Alto版のオブジェクト・プログラムを、独自に逆アセンブル、変更を加えたもの」です。いろいろな変更が加えられていますが、メモリマップも違っているようです。

 

記事では「図3 メモリ・マップ」として開設されています。プログラム本体は、0000番地から始まるメモリに配置されていて、0786番地まで続くので約2Kバイトです。作業領域、BASICプログラム格納領域、スタックなどは、1000番地から1400番地までなので、たかだか1Kバイトです。

 

一方でDDJに掲載されているオリジナル Palo Alto Tiny BASICは、プログラム本体が、0000番地から074E番地までの約2Kバイトで、作業領域などが、0800番地から2000番地までの6Kバイトです。東大版に比べると、作業領域などに割り当てているサイズが大きく違います。

 

これらが発表された当時のハードウェア状況を考えれば、 メモリが1Kバイトしかないとしても、やむを得ないでしょう。しかし割り当てが少なすぎて、例えばStar Trekなどを動かすのは、おそらく無理でしょう。

 

SimHのAltair 8800シミュレータを使うなら、64Kバイトのフル実装するのも簡単なことです。そこまで大きく拡げないとしても、東大版Palo Alto Tiny BASICのソースプログラムで指定されている値を変更しておきたいと思います。

2026-07-19

Altair 8800対応「東大版Palo Alto Tiny BASIC」が動いた

共立出版の『マイクロコンピュータのプログラミング』に掲載されている「東大版Palo Alto Tiny BASIC」をSimHのAltair 8800シミュレータで動くように変更を加えてみました。共立本に掲載されているプログラムの実行環境が不明ですが、SDK-80だろうと思います。本文にはテレタイプと書かれていますが、その入出力はi8251を使っています。一方で、Altair 8800では、シリアルインターフェイスはMC6850が使われています。

 

Altair 8800に対応させるには、i8251を意識している箇所を、MC6850に対応させる必要があります。具体的には、次のようにします。

  1. 入出力データは、0FAHとなっているのを、011Hに変更します。 
  2. ポーリングのために参照するレジスタは、0FBHとなっているのを、010に変更します。
  3. i8251の「TxRDY」は、D0です。MC6850の「Transimit Data Register」は、D1です。このため、OMSKの定義を、001Hから002Hに変更します。
  4. i8251の「RxRDY」は、D1です。MC6850の「Receive Data Register」は、D0です。このため、IMSKの定義を、002Hから001Hに変更します。

 

定義を変更するだけで、とりあえず動いてくれました。

Altair 8800 simulator V3.9-0
sim> load KYORITSU-ALTAIR8800.rom
4118 Bytes loaded.
sim> run

OK
>10 INPUT A,B
>20 PRINT #3,'A+B=',A+B,' A-B=',A-B,' A*B=',A*B,' A/B=',A/B
>30 STOP
>RUN
A:10
B:2
A+B= 12 A-B=  8 A*B= 20 A/B=  5

OK
>

 

 

2026-07-18

SimH Altair 8800で「Hello World」

K&Rの『プログラミング言語C』以来、「Hello World」とは「最初の一歩」という意味を持つようになりました。SimHのAltair 8800シミュレータを使い、「Hello World」を出力するプログラムを動かしてみようと思います。ここで重視しておくのは、CP/MとかDOSのような実行環境を一切使わないということです。

 

まずプログラムはアセンブラで書きます。入出力は、Altair 8800のSIOポートを使います。「Hello World」という文字列の各文字をSIOポートに出力し、HALTするだけ、というプログラムです。Geminiに作成してもらいました。アセンブラはzasmです。オプションとして「-uw --asm8080」を指定しました。 アセンブルした結果は、Rawバイナリ形式のファイルになります。オプションを指定すれば、インテルHEX形式や、モトローラS形式にも出来るようです。

 

SimHのAltair 8800シミュレータを起動し、プロンプトが出たところで「load hello.rom」のようにして、Rawバイナリ形式のファイルを読み込みます。そして「go 0」したら、文字列「Hello World」が出力されました。文字出力できました。

 

さらに、文字入力も確認しておきます。文字入力ができることを確認するだけなので、数字キーをおしたら、その回数だけ「Hello World」を出力するプログラムとしました。これもGeminiに作ってもらいました。アセンブルし、実行する手順は、同じです。これもうまくいったので、文字入力も大丈夫です。

 

Altair 8800が想定している入出力装置は、ASR-33のようなものなので、とても原始的です。使いやすくはありません。当時の時代の息吹が感じられるとも言えるかもしれません。

共立版「東大版Palo Alto Tiny BASIC」のソースコード

「東大版Palo Alto Tiny BASIC」と呼ばれるものは、共立出版の『マイクロコンピュータのプログラミング』に掲載されているものと、雑誌アスキーに掲載されているものがあります。両者の違いを詳しく確認したわけではありませんが、ちょっと見ただけでも違いがあります。

 

共立版の方には何も情報がないのですが、アスキー版には「8080 MACRO ASSEMBLER, VER 2.0」という文字があり、また「FOR SDK-80 VERSION」とも書かれています。「東大版Palo Alto Tiny BASIC」を学ぶため、どちらを使うか迷うところです。共立版の方が内部構造の説明が詳しいので、共立版を使おうと思います。

 

ひとまず書籍に掲載されているソースコードを入力したファイルを作成しました。これをアセンブルしようと思いますが、Windows11で利用可能なzasmを使おうと考えています。アスキー版にある「8080 MACRO ASSEMBLER」というのが、どういうものなのか分かりませんが、共立版も同じなのでしょう。zasmを使うなら、変更しておく箇所があります。

  1. 文字列をくくるのに「シングルクォート」が使われています。文字列中にシングルクォートが現れると、文字を重ねて対応するようですが、シングルクォートが多すぎて見にくいところがあるので、直そうと思います。
  2. マクロの構文がzasmとは違うようなので、対処が必要です。
  3. 「\」という文字が使われていますが、「$」のことのようなので、変更します。 

 

もう一つ考えておく必要があるのは、実行環境です。おそらくSDK-80を想定していると思われます。私は、SimHのAltair 8800エミュレータを使おうと考えています。文字入力や文字出力に関わる部分を変更する必要があるでしょう。

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

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セクタ版にしておいた方が無難と判断したのかもしれません。