FENIX Technical Manual


FENIX Ver0.2 Copyright (C) 1991-1998 S.Uchida
fsh Ver0.2 Copyright (C) 1991-1998 S.Uchida

★ FENIXでプログラムしたい人へ

FENIXは従来のIOCSを使用でき、またOSとしてメモリ管理機能などを備えてマシン語プログラムの実行環境を提供しています。

従来のBASIC上では困難だったマシン語による処理ができるわけですが、 いささか今までの環境とは異なるためにプログラムする際にとまどうことがあります。

そこで、本マニュアルではFENIXでアプリケーションを作ってみたいという人のために、各種のノウハウについて書いてみたいと思います。

Programmer's manual 及び System call manual を読んでからお読み下さい。


★ オブジェクト編

☆ まずはウォーミングアップ

オブジェクトと言っても今までのプログラムと大した違いはありません。

4000H 〜 FFFFH で動き、RET で終了するプログラムを作ればいいのです。 ただ、FENIXにおいてはメモリブロックの考えが強くなりますから、その点には注意して下さい。

例えば 5FFFH 〜 6000H の2バイトから成るプログラムの場合、これは二つのメモリブロックにまたがっていますから、なんとオブジェクトとしては16KBのメモリを消費してしまいます。

こんなプログラムを作る人はいないでしょうが、FENIXで重要なことは、複数のプログラムがオンメモリで幾つも存在できるということです。

メモリは各プログラムの共有資源だということを忘れずに、無駄なメモリを使わないように心がけましょう。

しかし、どんな小さなプログラムでも1ブロック、8KBも消費します。 と、すると下手に小さなツールを作るのもメモリの無駄ということになります。

ですから、何かツールを作ったら、8KBを使い切るまではできるだけ豪華な機能をつけてやりましょう。(笑)

☆ メモリを追加してみよう

FENIXの最大の特徴は、スワップも可能なメモリ管理能力です。 これを利用しない手はありません。 従来のOSなどでテンポラリファイルを作ることで行っていた作業が、ひょっとしたらオンメモリで処理できるかもしれません。

では、どのようにメモリを使えばいいのでしょうか?

答:FXC .GETMB を使う。

…これでは答になっていませんね。 問題は、確保したメモリをどのように使っていくかというところにあります。

これには2つの方法があって、ひとつはマッピングして使う方法、もうひとつは統一アクセスで使う方法です。

統一アクセスは後で扱うことにして、ここではマッピングして使う方法をやってみましょう。

マッピングは、スワップが生じる関係上必ず FXC を使わなければなりません。 FXC .MAPMEM もしくは FXC .MAPMB1 ですね。

しかし .MAPMB1 の方は1ブロックずつマッピングするので使いやすいけれど、.MAPMEM は6ブロックを一度にマッピングしてしまうので使いにくいと思う人もいるのではないでしょうか。

6ブロックを一度に変更するとプログラムの部分のマッピングが変更される危険があるので、プログラム部分がどのLMBにあるのかいちいち調べておかないと .MAPMEM は使えないので確かに面倒臭いです。

ですが心配無用。 6バイトのマッピングデータを用意する必要は全くなく、必要な部分のデータは既に設定済みである6バイトのデータがカーネル内に存在しています。

オブジェクトは起動時に A レジスタにオブジェクトIDが入っていますから、起動時にそのまま

    FXC   .OBJTAD
    PUSH  IX
    POP   HL
    LD    DE,26
    ADD   HL,DE
    LD    (MAPAD),HL
とでもして下さい。 何をしているかと言うと、オブジェクト管理テーブルから自分自身の情報を取り出し、そのマッピングデータの位置を求めているのです。

管理テーブル内のマッピングデータの情報は、オブジェクトを起動する際の初期マッピングデータとして使用されますから、プログラムがあるブロックのマッピングのデータは既に設定済みです。 ですからこのデータを用いて変更部分だけを書き換えれば、プログラムのあるブロックには影響を与えずにマッピングを行うことが可能です。

例えば

    LD  IX,(MAPAD)
    LD  (IX+4),3    ← C000H 〜 DFFFH にLMB3
    LD  (IX+5),4    ← E000H 〜 FFFFH にLMB4

    LD  HL,(MAPAD)
    FXC .MAPMEM
とでもするだけです。簡単でしょう?

ちなみに細かい話をしますと、複数のブロックをマッピングする場合は .MAPMB1.MAPMEM よりも遅いです。

☆ メモリはどのように確保するか

答:FXC .GETMB を使う。

さっきと同じ答ですね。しかし今度は勝手が違います。 どのマッピングモードを使うか、ということが大事なのです。

.GETMB を呼び出すときにどのモードかを指定するようになっていますが、この際の選択は以下のように行ってください。

プログラムを載せて走らせる ・・・ モード0
マッピングして使うデータ  ・・・ モード1/2
統一アクセスでしか使わない ・・・ モード3

間違ってもただのデータを入れるためのメモリをモード0で確保し、そればかりかそれをスワップ不可にする、などということはやめましょう。 他のオブジェクトに対して迷惑です。

☆ 引数処理をやってみよう

呼び出されると所定の動作を行い、戻る。 最初の内はこんなプログラムもいいですが、だんだん色々な機能を選択させたりしたくなってきます。 そこでそういう選択をコマンドラインから行えるようにしてやりましょう。 つまり、コマンドに引数を渡し、コマンド側でそれを解釈して処理を実行するのです。

例えば fsh の ls では、

    ls [-ahF1Cls]
と多彩なオプション指定が可能です。 しかもこれは、
    ls -a -h -F
と書いても
    ls -ahF
と書いても同じ動作をします。

いつの日か(ぉぃぉぃ)UNIXのcshなどに見られるエイリアス(別名置換)機能を使えるようにする予定です。 これをうまく活用するためには、コマンド側で上のような柔軟なオプション処理を行えるようになっているのが望ましいのです。

ひとつのオプションが複数の文字から成っている、という場合もあるでしょうが、まぁそれはそれ。オプションが一文字の場合は柔軟なものにしてやりましょう。

それではどうやってこんなオプション処理を行うか? 論より証拠。ls のオプション処理部分のソースを抜き出してみましょう。

LSOPLP:   LD     A,(DE)
          OR     A
          JR     Z,LSNODIR
          CP     '-'
          JR     NZ,LSFN
          INC    DE
LSOPLS1:  LD     A,(DE)
          INC    DE
          OR     A
          JR     Z,LSOPLP
          CALL   LSOPTS         ; interpret option
          JR     LSOPLS1
                :
                :

LSOPTS:   CP     'a'            ; check option
          JR     NZ,LSOF
          LD     A,(INWORK+1)
          OR     1
          LD     (INWORK+1),A
          RET
LSOF:     CP     'F'
          JR     NZ,LSOH
          LD     A,(INWORK+1)
          OR     2
          LD     (INWORK+1),A
          RET
LSOH:     CP     'h'
          JR     NZ,LSOC
          LD     A,(INWORK+1)
          OR     4
          LD     (INWORK+1),A
          RET
                :
                :
コマンドが起動されたとき、(DE)〜にコマンド名や引数のデータが入っています。 この処理では、まず引数の最初が "-" であるかどうか調べています。

"-" であれば、その引数はオプション指定であると見なし、以降の各文字をオプションとしてチェックしていきます。 文字がオプションであればワークエリアのフラグを立てます。 "-" でなければそれはファイル名として、実際の処理に移ります。

これはオプション処理のひとつのパターンを示しています。 実際、fsh 中の複数のオプションを許すコマンドは全てこのパターンで作っています。 このパターンを基本にして、ぜひ複数オプション処理を極めましょう。

☆ ワイルドカード処理をやってみよう

fsh は、ファイル名を示す部分にはいつでもワイルドカードを使用可能です。 正確に言うと、ワイルドカードの "*","?" を使うとファイル名だと思って変換しようとするのですが。 つまりファイル名にワイルドカードが使用できると言うよりは、ワイルドカードを使用するとファイル名と見なすわけです。

しかし、Programmer's manual でも書きましたが、実際にはワイルドカードは常に実際のファイル名に変換されるわけではありません。

そのワイルドカードにマッチするファイルがひとつしかない場合のみ変換が行われます。 そこで、コマンドラインからワイルドカードが指定され、しかもそれがシェルによって変換されなかった場合は、アプリケーション側で対処してやる必要があります。

もっとも、ワイルドカードが指定されたということは複数のファイルを指定した、ということですから、そのコマンドが複数のファイル名を受け付けないというならエラーと見なせばいいでしょう。

しかし複数のファイルを指定可能な場合、その指定の中でワイルドカードを用いていても正しく動いてほしいものです。

例えば fsh の chmod は、

    chmod {+|-} filename・・・
という書式です。これは例えば
    chmod - a*
とすると a で始まる名前の全てのファイルのプロテクトを解除します。
    chmod + abc def ghi
とすると abc,def,ghi の3つのファイルをプロテクトしますし、
    chmod + a* def g*
などと指定することもできます。

このような指定を許すのは一見ややこしそうな処理に見えますが、実は一定の形式にしてしまえば簡単になります。

以下は、chmod のワイルドカード処理部分です。

CHMLP0:   LD     A,C
          LD     (CHMA),A
CHMLP:    PUSH   BC             ; 引数処理ループ
          CALL   NXTWORD
          PUSH   DE
          XOR    A
          FXC    .DIRLIST
          OR     A
          JR     NZ,CHMLP1
          CALL   CHMERR2
          JR     CHMLPE
CHMLP1:   CP     1
          JR     NZ,CHMWILD      ; multi file --- wild card
          CALL   CHMSUB          ; chmod 1 file
CHMLPE:   POP    DE
          POP    BC
          DJNZ   CHMLP
          RET

; ワイルドカード
CHMWILD:  LD     B,A            ; B...number of matched file
          LD     C,0
CHMWLP:   PUSH   BC
          PUSH   DE
          LD     A,C
          FXC    .SETWILD
          CALL   CHMSUB
          POP    DE
          POP    BC
          INC    C
          DJNZ   CHMWLP
          JR     CHMLPE
                :
                :
その他、ワイルドカードを許すコマンドは、全部同じ形式を取っています。

簡単に説明すると、引数がひとつのファイル名を表しているなら直接処理ルーチンを呼び、ワイルドカードならマッチするファイル名を順次生成し、処理ルーチンを呼んでいる、というだけです。

このパターンはワイルドカード処理の基本となりますので、これを元にしてワイルドカード処理も極めましょう。

☆ 常駐オブジェクトを作ってみよう

オブジェクトがシェルによってどのように処理されるか、ということについては、Programmer's manual で詳しく説明しています。

ユーザーが load したものは実行した後もオブジェクトとして残るわけですが、直接ファイルから起動すると実行後には消されてしまいます。

しかしアプリケーションによっては、勝手に消去されると困るということがあります。 主に後述のTSRの場合なのですが、こういう場合に基本となるのが常駐オブジェクトの考え方です。

作り方は簡単です。

    LD   A,自分のオブジェクトID
    FXC  .OBJTAD
    SET  1,(IX+18)
とすればできあがりです。

ここでのビットセットは自分自身の書き込み許可を取り消してしています。 これだけで、ファイルから起動した場合でもシェルに消されずに済みます。 ファイルから起動した場合は、シェルは実行終了後にそのオブジェクトを消そうとしますが、書き込み許可がないと消せませんのでそのままにするからです。

ではこれで終わりです。 と言いたいところですが、よく考えるとこのオブジェクトを消すにはどうしたらいいんだ、という問題にぶつかります。

これは、勝手に常駐するわけですから、そのオブジェクト側で勝手に消えてもらうのが妥当なところでしょう。 従って、常駐を解除するオプションなどを用意してオブジェクトが自分自身を消去するようにしましょう。 それは以下のような処理になります。

    LD   A,自分のオブジェクトID
    FXC  .OBJTAD
    RES  1,(IX+18)
    FXC  .KILLOBJ
    RET
自分自身の書き込み許可を戻し、自分自身を殺して RET しています。

この、FXC .KILLOBJ と RET の間が、ゾンビオブジェクトという危険な状態です。 オブジェクトとしては死んでいるのに、プログラムとしては動いています。 不測の事態を避けるため、この例のように自分を殺したらすぐさま元のシステムに戻るようにして下さい。

☆ TSRオブジェクトに挑戦!

TSRとは何でしょう? これは Terminate but Stay Resident の略で、実行後も消去されずにメモリ上に居座って何らかの動作をするプロセスのことです。

しかしFENIXにはプロセスの概念がなく、常に常駐するオブジェクトの概念しかありません。 ですから、本当はTSRと呼ぶのは間違っているのかもしれません。 しかし、一般にTSRと言うと、常駐してシステムに作用するものを指すようですので、ここではそういうものをTSRという呼び方をします。

ではFENIXにおけるTSRとはどんなものでしょう?

これは割り込みを用いたもので、大きく分けて以下の3種類があります。

では順番に説明しましょう。

[1]ソフトウェア割り込み

他のオブジェクトがエントリアドレスをコールすることで、処理を起動します。 SVCやFXCのようなものをユーザーが作れるのだと思って下さい。

結局オブジェクトを呼び出しているのですが、FXC .EXECOBJ と異なる点は、

などです。

では実際にどのように使うか、について説明しましょう。

まず、ソフトウェア割り込みを登録するオブジェクトが必要です。 そしてその機能を利用するオブジェクトが必要です。 今、仮に前者をオブジェクトA、後者をオブジェクトBとします。

Aの方はまずソフトウェア割り込みを登録します。 これには FXC .TSRON を使用します。

    LD   A,8                ←ソフト割り込みはタイプ8
    LD   HL,ADDRESS         ←割り込みで呼び出されるルーチンのアドレス
    FXC  .TSRON
これで登録終了です。

この登録で何が行われるかといいますと、

  1. Aの全LMBがスワップ不可になる。(実MB番号で直接マッピングするため)
  2. 内部テーブルに、呼び出しアドレスとマッピングに関するデータが登録される。
  3. Aの書き込み許可が無くなる。

さて、今度はこれをBから使ってみましょう。 まず、AのオブジェクトIDが分かっていればいいのですが、普通は分かりません。 しかしAの名前さえ分かっていればIDを取得することはできます。

    LD   DE,A_NAME
    FXC  .CHKOBJ
          :
          :

A_NAME: DEFB 'A',00H
といった具合です。FXC .CHKOBJ でIDが分かれば、次に
    LD   C,A      ←オブジェクトID
    LD   B,0      ←Aが持っている番号0の割り込み
    LD   A,8      ←ソフト割り込みはタイプ8
    FXC  .TSRENT
これで HL に呼び出しアドレスが返ってきます。 このアドレスをコールすれば、Aが登録した機能を使うことができます。

そこで、このアドレスはどこにあるのか、マッピングを変更すると暴走の危険があるのではないか、と気にする方がいらっしゃるかもしれませんが、心配無用です。

TSR処理ルーチンは、全てコモンエリア内のスタック領域にあります。 ここならマッピングが変更される恐れはありません。 スタック領域は2KBもありますので、少々使っても大丈夫です。

そこでこの呼び出しの際ですが、まず、呼び出された時のレジスタの値を全て保存したまま実際の処理が呼び出されます。 そして、実際の処理が終了した時のレジスタの値を全て保存したままもとのルーチンに帰ります。 従って入出力パラメータにレジスタを自由に割り当てることができます。

ではこれで説明を終わり…おっとっと。 ソフト割り込みを登録したAは、もはや書き込み許可を与えても消去することはできません。 このままではオブジェクトが増える一方ですね。

じゃあ登録を取り消してみましょう。Aから

    LD   A,8      ←ソフト割り込みはタイプ8
    LD   B,0      ←Aが持っている番号0の割り込み
    FXC  .TSROFF
これで登録が解除されます。 しかし書き込み許可は復活しませんので、自分で復活させます。 常駐オブジェクトの説明で行った通りです。

[2]0.1秒タイマ割り込み

ご存知の方も多いでしょうが、SuperMZのIOCSには0.1秒ごとに処理を起動することのできる機能があります。 これをFENIXでサポートしています。

さっそく使ってみましょう。 今度はオブジェクトをひとつしか使いませんので楽です。

まずは登録。

    LD   A,9                ←タイマ割り込みはタイプ9
    LD   HL,ADDRESS         ←割り込みで呼び出されるルーチンのアドレス
    FXC  .TSRON
ソフト割り込みと変わりませんね。これで、
HL ... 呼び出しアドレス
DE ... コモンエリアのタイマ管理テーブルのアドレス
が返ってきます。 しかし HL の値は全く意味がありません。 呼び出すためのアドレス設定は既に済ませてあるからです。

ここで重要なのは DE です。 このままま放っておいても、全く処理ルーチンは呼び出されません。 タイマテーブルを設定してやらなければならないのです。

コモンエリアのタイマテーブルは以下のような構成になっています。(計8バイト)

+0 1でテーブル有効
+1〜+3呼び出しインターバル。0.1秒ごとに1加えられ、0になると処理ルーチンが呼び出される。+1 が下位バイト。
+4〜+7呼び出すルーチンのマッピングとアドレスのデータ。.TSRON で既に設定済み。
+4〜+7 は書き換える必要はないのですが、問題は +0〜+3 です。 最初に起動するときに設定しなければならないのはもちろんのこと、なんとこの部分のデータは処理を起動する度に0になってしまい、再設定しないとその処理ルーチンを起動することはできなくなってしまうのです。

ですから、処理部分では

  1. テーブルを有効にする
  2. インターバルを再設定する
ことを忘れないようにして下さい。

以下のような感じですね。

ADDRESS:CALL 実際の処理
        LD   HL,(TABLE_ADDRESS)  ←.TSRON で DE に渡された値
        LD   (HL),1              ←テーブルを有効にする
        INC  HL
        LD   (HL),0FBH           ←インターバル再設定
        INC  HL                    この場合は 0FFFFFBH = 0.5秒
        LD   (HL),0FFH
        INC  HL
        LD   (HL),0FFH
        RET
従来のタイマ管理では1ブロックのマッピングしか行えませんでしたが、FENIXでは6ブロック全てマッピングが可能です。

登録解除はソフト割り込みとほぼ同じです。 なお、この割り込みの処理ルーチン側で、レジスタを保存する必要はありません。 また、SVC・FNC・FXCは基本的に使えないはずです。(暴走の危険あり)

[3]ハードウェア割り込み

いよいよ本来のTSRの姿とも言えるハードウェア割り込みです。 ハードによる割り込みを登録することで、どんな処理を実行中でも別の処理を起動し、システムに何らかの作用を及ぼすことができます。

しかし、多くのハードウェア割り込みは既にIOCSで利用されていて、うかつに使うとシステムが暴走する危険があります。 従って、注意して使う必要があります。

登録する際に注意すべきことは、.TSRON は内部テーブルに登録するだけで実際に割り込みのための設定などは行わないということです。 .TSROFF も同じく、割り込みを解除するような処理は行いません。 従って、アプリケーション側で割り込みの設定・解除を行う必要があるのです。

そして、もうひとつ注意すべきなのはレジスタです。 ハードウェア割り込みはその性質上全てのレジスタを保存する必要がありますが、呼び出しルーチン側では AF,BC,HL しか保存しません。 従ってそれ以外のレジスタを使う場合は、保存しておいて終了時に復帰させる必要があります。

更に言うと、SVC・FNC・FXCも使えません。(暴走の危険あり)

ハードウェア割り込みは割り込み源によって6種類のものがサポートされています。 順に使い方を説明していきましょう。

[3.1]PIO割り込み

これは面倒な処理はある程度軽減されます。なぜならこの割り込みはIOCSでは 使用していないからです。

登録は例によって

    LD   A,10               ←PIO割り込みはタイプ10
    LD   HL,PTR_ADDRESS     ←割り込みルーチンアドレス格納位置へのポインタ
    FXC  .TSRON
あれ?何か違いますね。 実はPIOはAポートとBポートの2種類の割り込み源から割り込みをかけることができるので、割り込みルーチンのアドレスが2つ必要なのです。 つまり
    (HL),(HL+1)    ....  Aポート割り込み時の処理アドレス
    (HL+2),(HL+3)  ....  Bポート割り込み時の処理アドレス
と、ワークエリアをセットしておいてから登録しないといけないのです。

すると HL と DE に処理アドレスへのポインタが入って返されますが、PIOの場合は HL=DE です。

(HL),(HL+1)  .... Aポート割り込み時に呼び出されるアドレス
(HL+2),(HL+3) .... Bポート割り込み時に呼び出されるアドレス
となっていますので、実際にPIOをプログラムして割り込みをセットする場合は、Aポートのベクタを HL に、Bポートのベクタを HL+2 に合わせて下さい。

[3.2]SIO割り込み

ハードウェア割り込みの中でも一番複雑なものでしょう。 Z80SIOには、通常の割り込みベクトル発生機能の他に、ステータス・アフェクツ・ベクトルという機能があります。 一応どちらでも使えるようにはなっていますが、どちらかと言えばステータス・アフェクツ・ベクトル向きに作られています。

登録は、PIOと同じく

    LD   A,11               ←SIO割り込みはタイプ11
    LD   HL,PTR_ADDRESS     ←割り込みルーチンアドレス格納位置へのポインタ
    FXC  .TSRON
となります。

これで HL に呼び出しアドレステーブルへのポインタ、DE にIOCSが使っている割り込みベクタの格納アドレスが入って返されます。

具体的な設定方法を各モードについて説明します。

(1)通常の割り込みベクトルを使用する場合
(HL),(HL+1)に処理アドレスを格納して .TSRON を呼んで下さい。 返ってきた HL のテーブルの内、(HL),(HL+1)しか使いません。
    DI
    LD   (ORIGADD),DE       ←元の処理ルーチンアドレスの格納位置を保存
    LD   A,(DE)             ←元の処理ルーチンのアドレスを保存
    LD   (ORIG),A
    INC  DE
    LD   A,(DE)
    LD   (ORIG+1),A
    DEC  DE
    LD   C,(HL)             ←処理ルーチンアドレスを読み出す
    INC  HL
    LD   B,(HL)
    LD   (DE),C             ←アドレスを登録
    INC  DE
    LD   (DE),B
    EI
といった具合にすればいいでしょう。 TSR管理テーブルにはステータス・アフェクツ・ベクトル用に8個のテーブルが用意されていますが、その最初のひとつを流用しているわけです。
(2)ステータス・アフェクツ・ベクトルを使用する場合
各割り込み要因に対して処理アドレスが必要なので、8個のアドレスのテーブルが必要となります。
    (HL),(HL+1)  .... ch.B トランスミッタバッファエンプティ処理アドレス
    (HL+2),(HL+3) .... ch.B 外部/ステータス割り込み処理アドレス
                  :
                  中略
                  :
    (HL+14),(HL+15).... ch.A 特殊受信状態処理アドレス
と、16バイトのワークエリアを設定してから .TSRON を呼ばなければなりません。

すると (HL)〜(HL+15) に各割り込み要因に対応した呼び出し先が登録されているテーブルが返されますので、割り込みベクトルを HL の示すアドレスに合わせます。 ちなみに HL=2D0H ですから、Iレジスタを気にする必要はありません。

[3.3]VBLANK割り込み

若干特殊です。これはグラフィック画面からのVBLANK信号によって発生する割り込みですが、IOCSによって管理されているにもかかわらず普段は起動しないようになっています。

それではいつ起動するのか、というと、スムーススクロールをする時です。 つまりスムーススクロールを実行する時だけ割り込み処理を起動するようにしてあって、スクロールが終了すると割り込みを解除してしまうのです。

ここが少々やっかいなところです。 割り込みの起動・解除までIOCSで管理されていては手が出ません。 スムーススクロールさえ生じなければIOCSは関与せず大丈夫なので、FENIXでこの割り込みを使用する場合はスムーススクロールモードでない場合に限る、ということになります。

それでも使いたいという場合は、以下のように使って下さい。

    LD   A,12               ←VBLANK割り込みはタイプ12
    LD   HL,ADDRESS         ←割り込みで呼び出されるルーチンのアドレス
    FXC  .TSRON
これで HL に呼び出しアドレス、DE に元々IOCSが使っていた割り込みベクタの格納アドレスが入って返されます。
    DI
    LD   (ORIGADD),DE       ←元の処理ルーチンアドレスの格納位置を保存
    LD   A,(DE)             ←元の処理ルーチンのアドレスを保存
    LD   (ORIG),A
    INC  DE
    LD   A,(DE)
    LD   (ORIG+1),A
    DEC  DE
    LD   A,L                ←割り込みベクタに新しいアドレスを設定
    LD   (DE),A
    INC  DE
    LD   A,H
    LD   (DE),A
    LD   A,(61EH)           ←割り込み許可
    OR   8
    LD   (61EH),A
    OUT  (0C6H),A
    EI
      :
      :
こんな具合に設定して下さい。 他の割り込みと異なり、割り込みが有効になるようにI/Oコントローラの設定をしなければなりません。

この点を除けば以降の3つの割り込みと大した違いはありません。 61EH は割り込みベクタマスクフラグを保存しているワークエリアです。 この値を用いてアクセスすれば、現在の割り込み設定を乱す恐れはありません。

解放時には逆のことを行わなければなりません。 つまり

    DI
    LD   A,(61EH)           ←割り込み禁止
    AND  7
    LD   (61EH),A
    OUT  (0C6H),A
    LD   DE,(ORIGADD)       ←元の処理ルーチンのアドレスを復帰
    LD   A,(ORIG)
    LD   (DE),A
    INC  DE
    LD   A,(ORIG+1)
    LD   (DE),A
    EI
        :
        :
といった具合です。

[3.4]8253割り込み

IOCSシステムの中核を成す割り込みです。20msごとに起動されるタイマ割り込みなのですが、この処理ルーチンでキーボード入力処理、マウス処理、音楽処理、といった様々な処理を行っています。

この割り込みを止めてしまうとシステムがハングアップしますので、これを使う場合は細心の注意が必要です。

登録は例によって

    LD   A,13               ←8253割り込みはタイプ13
    LD   HL,ADDRESS         ←割り込みで呼び出されるルーチンのアドレス
    FXC  .TSRON
となります。 ここで HL に呼び出しアドレス、DE にIOCSが使っていた割り込みベクタの格納アドレスが入って返されます。 設定は以下のように行って下さい。
    DI
    LD   A,(DE)             ←元の処理ルーチンのアドレスをセット
    LD   (ORIG+1),A
    INC  DE
    LD   A,(DE)
    LD   (ORIG+2),A
    DEC  DE
    LD   A,L                ←割り込みベクタに新しいアドレスを設定
    LD   (DE),A
    INC  DE
    LD   A,H
    LD   (DE),A
    EI
        :
        :


ADDRESS:CALL 実際の処理
ORIG:   JP   0000               ←元の処理ルーチンへジャンプ

元の処理ルーチンを呼ばないとシステムがハングアップしてしまいます。 また、この割り込みはただでさえ多数の処理を行っていてかなり負担がかかるので、時間のかかる処理をさせたりしないで下さい。

[3.5]プリンタ割り込み

使ったことがないので分かりませんが(ぉぃぉぃ)基本的に8253割り込みと同じように扱っていいと思います。 元々のIOCSの処理ルーチンは呼ばなくてもいいかもしれません。

登録時のタイプは14です。

[3.6]RTC割り込み

上に同じ。VBLANKからRTCまでの4個の割り込みはI/Oコントローラの制御下にある割り込み源ですから、本来4個とも同じように扱えるはずです。

登録時のタイプは15です。

[3.7]優音割り込み

登録等は基本的に同上。 詳細は優音用シェルのマニュアルを参照して下さい。

登録時のタイプは16です。


★ ファイル編

☆ 統一アクセスの一般的な話

統一アクセスは、全てのファイルタイプを同等に扱う便利なものですが、汎用性を重視したためにかなり処理が遅くなってしまっています。

作者としては「ないよりはマシだ」という程度にしか考えていませんので、別にFENIXだから統一アクセスしないといけない、ということはありません。 しかし簡単にプログラムを作れますので、さほど速度を要求されないところでは結構使いやすいと思います。

アクセスは以下のように行います。

1.ファイル/オブジェクトの指定

.CHKFILE(ファイル名による指定)
.CHKOBJ (オブジェクト名による指定)
.CHNGOBJ(オブジェクトIDによる指定)
この3個のいずれかを用いて指定を行います。

2.オープン

.U_OPEN を用います。IOCSと違い、LU番号を指定してオープンするのではなく、FXC がLU番号を返すようになっています。

3.読み書き

.U_READ もしくは .U_WRITE です。 256バイトのバッファを用意して実行して下さい。

3’.ランダムアクセス

.OBJRND を用いてランダムアクセスを行うこともできます。 ただし OBJ,BTX のファイルかオブジェクトでないとできません。

4.クローズ

.U_CLOSE を用いて、アクセスが終わったらクローズします。

4’.キル(エラー時)

エラーなどのためアクセスを破棄したい場合はキルします。 これも .U_CLOSE で行います。

全体としては、

    指定→オープン→読み書き→クローズ

という流れになります。

☆ オブジェクトに対して統一アクセスする場合の利点

オブジェクトの内容を読み書きするのなら、何も統一アクセスなど使わなくてもLMBをマッピングして直接読み書きした方が速いじゃないかと思うでしょうね。

確かに直接マッピングしてアクセスする方が速いのですが、統一アクセスにもそれなりの利点はあります。

  1. マッピングしない
    LMBをマッピングする場合はスワップが生じる可能性があります。 EMMに対してのスワップは1ブロック当たり約0.2秒、VRAMに対してだともう少し速いのですが、どちらにしても余計な時間が必要となります。
    統一アクセスだと、相手がVRAMでもEMMでもマッピングせずに直接内容を読みますのでほぼ同じ時間でアクセスすることができます。
    統一アクセスでしか利用しないLMBはモード3(極力メインRAMを避ける)で確保して下さいと言っているのもこういう理由からです。 メインRAMは実行形式バイナリのために空けておきましょう。

  2. 単純な読み書きには便利
    連続して書き込んだり読み込んだりする場合は .U_WRITE.U_READ を連続して呼び出すだけで済みます。
    マッピングすると、8Kバイトを越えた場合次のLMBを確保してマッピングする、という処理が必要になってしまいます。

  3. ファイルと同じインタフェイスでアクセスできる
    出力先をファイルにするかオブジェクトにするか選択できる場合、出力の処理を非常に簡単に実現できます。 .U_OPEN の入力パラメータが異なるだけですから。
    例えば大量の作業領域を必要とするツールがあった場合、メモリがある限りはオブジェクトにデータを書き込み、メモリが足りなくなったらファイルに切り換えるなんてこともかなり簡単に実現できるでしょう。
統一アクセスは、オブジェクトのファイル的側面をサポートしています。

プロセスのようでもありファイルのようでもある。 これがオブジェクトの面白いところです。 私は「ファイル以上プロセス未満」と呼んでいます。

こんな「オブジェクト」を使って、何か面白いことやってみませんか?

☆ オブジェクトアクセス時の注意

普通に読み書きする分には特に注意することはありませんが、ランダムアクセス時には気をつけてほしいことがあります。

まず、当たり前ですが存在しないブロックにアクセスしようとしないこと。 そして、.GETMB で獲得したメモリにアクセスしようとする場合は自分のサイズを必ず変更しておいて下さい。

統一アクセスは、オブジェクト管理テーブルに記録されているサイズに関する情報を参照してアクセス可能な最終ブロックを決定しています。 .GETMB はサイズの変更など行いませんから、実際はアクセスできる部分は増えていてもアクセスしようとすると拒否されます。 (正確に言うと、一回アクセスするとEOFが返ってくる)

致命的なエラーになるということはありませんが、一貫性がとれていないとバグの原因にもなりかねませんのでサイズは常に修正して下さい。


★ FXC .SHELL編

fsh Ver0.11 より FXC .SHELL がサポートされています。 これは、シェルの機能である PATH 指定やコマンド行解析&実行、変数参照などの機能をユーザに解放するものです。 詳しくは fsh manual を参照して下さい。 その中で、特に変数を参照する場合というのは姑息な(?)テクニックがありますのでここで取り上げます。

アプリから変数の値を参照するとき注意しておかなければならないのは、変数には4通りの状態があるということです。それは、

  1. 定義されていない
  2. 定義されているが値が空である
  3. 定義されていて、値が1ワードしかない
  4. 定義されていて、値が複数のワードである
というものです。特に注意すべきなのが2の状態です。 例えば変数 var というものを使うアプリがあって、FXC .SHELL で変数が定義されているかどうかを確かめたいとき、
    LD      A,0
    LD      C,FSH_VAR
    FXC     .SHELL
などとすればできますが、ここでは A=0 にしない方がいいです。 なぜかと言いますと、このやり方だと上記の2〜4全ての場合にあてはまるため、2の状態だった場合、値が空ですから、(DE) には NULL(00) が入っていることになります。

そこで、値を読むときにいちいち (DE)=0 かどうか確かめなければなりません。 値は不要と言うならそれでもいいのですが、値が必要な場合はもっと賢いやり方があります。先の例で、

    LD      A,1
にして呼び出すのです。 こうすると、値が定義されていない場合はエラーになります(指定ワードが存在しない)ので、(DE)=0 の確認が不要になるというわけです。

逆に、変数が定義されているかどうかだけ知りたい場合は最初のやり方の方がいいというわけです。


皮肉なことにまだ作者自身があまりFENIXを使いこんでいないという状況のため、 実際的なテクニックはあまり蓄積されていませんし、説明不足の部分もあるでしょう。

これから充実させていきたいと思いますので、質問、アイデアなどがありましたら聞かせて下さい。


FENIXのメインページに戻る