WonderWitchプログラミングtips

更新日: 2001年9月27日

目次


割り込み時のSRAMバンク不整合問題を回避する

SRAM バンク不整合問題の概要

FreyaOS では、4 バンクある SRAM バンクをプロセスのデータエリア、OS のワークエリア、 および、ソフトファイルシステムのデータ領域として使用しています。

ユーザプログラムを実行する場合、SRAM バンクには通常プロセスに割り当てられたデータエリアが マッピングされ、この領域にプログラムのデータセグメントが配置されます。

しかし、ファイルシステムのアクセスなど、一部の機能を実行する際に SRAM バンクの切替えが行なわれ、 一時的に OS のワークエリアやソフトファイルシステムのデータ領域がマッピングされる場合があります。

このようなタイミングでハードウェア割り込みが発生すると、割り込みハンドラに制御が 移った時点ではユーザプログラムのデータセグメントがマッピングされていない状態となります。 この状態で割り込みハンドラ内でユーザプログラムのデータセグメントに配置されている変数などを アクセスすると意図せぬ領域がアクセスされてしまい、システムのデータを破壊してしまったり、 暴走を招く結果となってしまいます。

問題の回避方針

この問題の回避方針としては、次の二つが考えられます。 FreyaOS では、複数プロセスで共用する割り込みハンドラの許容の点から、後者の方針を選択しました。

割り込みハンドラでの SRAM バンク切替え状態の設定と復帰

今回、この問題を回避するために、ライブラリ関数が新たに一つ追加されました。
呼び出し形式 int near pcb_get_srambank(void)
機能説明 現在実行中のプロセスのデータセグメントが配置されている SRAM バンク番号を取得する。
ヘッダファイル sys/process.h
この関数とバンク制御 BIOS の機能、bank_set_map()、bank_get_map() を用いて、 割り込みハンドラでおこなう処理の前後で SRAM バンク状態を保存/設定/復帰します。 以下に割り込みハンドラの具体的な記述方法を示します。
#include <sys/process.h>
#include <sys/bank.h>

void far callback()
{
    /* 現在の SRAM バンク状態を取得 */
    int oldbank = bank_get_map(BANK_SRAM);

    /* 実行中のプロセスのデータセグメントが配置されている SRAM バンクを取得 */
    int proc_bank = pcb_get_srambank();

    /* プロセスのデータセグメントをマッピング */
    bank_set_map(BANK_SRAM, proc_bank);
    {
        /* 割り込みハンドラの処理本体をここに記述 */
    }
    /* SRAM バンク状態を元の状態に復帰 */
    bank_set_map(BANK_SRAM, oldbank);
}   

ディスプレイモード毎の定義可能キャラクタ数

スタートアップルーチンを交換することでプログラムのディスプレイモードを変更できますが、 このとき本体内蔵RAMの利用状況が変化します。 このためディスプレイモードに応じてプログラム中で利用可能な最大キャラクタ数が異なります。

具体的には、ASCII1、ASCII2の各ディスプレイモードを利用時には最大128キャラクタ(キャラクタ番号0〜127)、 日本語1、日本語2を利用時には最大512キャラクタ(キャラクタ番号0〜511)となります。


KEY_PRESS_CHECKとKEY_HIT_CHECKを連続して呼んではいけない

キー制御BIOSの機能、KEY_PRESS_CHECKを呼び出すと、 FreyaBIOSの内部ワークエリアに保持してあるキー押下状態が更新されます。 このワークエリアはKEY_HIT_CHECKなど、他の機能でも参照するため、 KEY_PRESS_CHECKを呼び出した後にKEY_HIT_CHECKを呼び出すと、正しい結果が得られません。

これは、本来、KEY_PRESS_CHECKがワークエリアを更新すべきではないのに、 更新してしまっていることに由来する問題ですが、 動作の一意性保証の観点から将来的な対応については現在のところ予定していません。

一般的な回避策としては、KEY_PRESS_CHECKのみを用いてKEY_HIT_CHECK相当のものは 自前で処理するということになります。 例えば、垂直帰線期間割り込み(VBLANK割り込み)のたびに一度だけKEY_PRESS_CHECKを呼び出し、 結果を自前のワークエリアに保存しておいて、押下状態の取得はこのワークエリアを参照します。


RunnableILにはメソッドを追加できない

init.rcに記述することで、FreyaOS起動時にRunnableILを実行することができます。

このとき、実行対象となるRunnableILが提供する関数郡のアドレス解決は、 FreyaOS内部でスタック上に動的に確保されるRunnableIL型の変数を用いて行なわれています。

インダイレクトライブラリの性質上、 アドレス解決をする関数の数が増えれば確保すべき変数のサイズも変わるため、 init.rcで実行すべきインダイレクトライブラリが、 システムが想定しているより多くの関数のアドレスを解決を必要とする場合、 FreyaOS内部で確保されている変数領域では不足するため、 スタックを破壊し、システムが暴走してしまいます。

このため、init.rcから呼び出すインダイレクトライブラリは、 厳密にRunnableIL型に合致させておく必要があります。

この問題は一般的にはアドレス解決すべき関数の数を動的に取得して、 それに応じたアドレス解決用のバッファを取得することで回避できますが、 init.rcからの呼び出しに関してはシステムの仕様と位置付けられています。


LCD描画ライン関連機能

WonderSwanには、現在描画中のLCDラインを取得する機能と、 特定ラインを描画するタイミングを検知して割り込みを発生させる機能があります。

WonderWitchでもこの割り込みに対するハンドラの登録機能は提供されていましたが、 検知ラインを指定する機能が提供されていませんでした。

WonderWitch用にこれらの機能を実現するライブラリ関数を提供しました。

void lcd_set_interrupt_line(unsigned char line)
指定したラインを描画するタイミングで割り込みを発生させます。
割り込みハンドラは、SYS_INTERRUPT_SET_HOOK を用いて登録します。

unsigned char lcd_get_display_line(void)
現在描画中のラインを取得します。
以下に示すのは、この機能を利用して特定ラインを描画する際に割り込みを発生させるサンプルプログラムです。
#include <sys/bios.h>

#ifdef LSI_C_WW
#define _CS	_asm_inline("mov\tax, cs")
#define _DS	_asm_inline("mov\tax, ds")
#endif

void far callback()
{
	/* This function will be called when line # 10 is being drawn */
}

int main(int argc, char *argv[])
{
	intvector_t v;

	lcd_set_interrupt_line(10);

	v.callback = (void (near *)())FP_OFF(callback);
	v.cs = _CS;
	v.ds = _DS;
	sys_interrupt_set_hook(SYS_INT_DISPLINE, &v, &v);

	text_screen_init();
	text_put_string(0, 17, "HIT ANY KEY");
	key_wait();

	sys_interrupt_reset_hook(SYS_INT_DISPLINE, &v);
}

なお、これらの機能は、本来、画面制御BIOSあるいはシステム制御BIOSの機能として提供されるべきですが、 現時点ではライブラリ関数のみでの提供となっており、今後のBIOSでの提供については未定です。


intvector_t の cs、ds の設定

SYS_INTERRUPUT_SET_HOOK に渡す intvector_t のメンバー、cs、dsには、 BIOSが割りこみハンドラを呼び出す時に用いる値を設定します。

csは、割りこみハンドラのセグメントアドレスで、callbackメンバーとともに 関数のfarアドレスを構成します。通常、割りこみを設定する際の CS レジスタ の値をそのまま設定します。

また、dsは、割りこみハンドラを実行する際のデータセグメントで、 通常、割りこみを設定するときの DS レジスタの値をそのまま設定します。

CS、DSの値を得る方法は、お使いのコンパイラによって異なります。

Turbo Cの場合

レジスタ擬似変数 _CS, _DS を参照すれば、その時点での cs, ds レジスタの 値を得ることができます。この値を intvector_t 構造体の各メンバーに 設定してください。
※レジスタ擬似変数につきましては、Turbo C関連の書籍等をご参照下さい。

LSI C-86 for WonderWitchの場合

インラインアセンブラを用いて、cs, ds レジスタの値を取得することが できます。具体的には、以下のように、インラインアセンブラ関数と、 それを利用するマクロを定義しておけば、Turbo C をお使いの場合と同様に _CS、_DS として参照することができます。
extern unsigned _asm_inline(char *asm);
#define _CS	_asm_inline("mov ax, cs")
#define _DS	_asm_inline("mov ax, ds")
※LSI C のインラインアセンブラについては、LSI C-86 for WonderWitch のマニュアルをご参照下さい。
WWitch\lsic86ww\man\wonderwitch.man

どちらのコンパイラでも利用できるようにするには、あらかじめ定義されて いるマクロを用いて、定義を切りかえる方法が有効です。 Turbo C の場合 __TURBOC__ が定義されています。また、LSI C-86 for Wonder Witch の場合、初期設定では LSI_C_WW と LSI_C が定義されていますので、 以下のように記述しておけば、Turbo C、LSI C-86 for WonderWitch のいずれを 使ってもコンパイルできるようになります。

#ifdef LSI_C_WW
extern unsigned _asm_inline();
#define _CS	_asm_inline("mov ax, cs")
#define _DS	_asm_inline("mov ax, ds")
#endif /* LSI_C_WW */

CS != DS != SS

FreyaOSの仕様では、ユーザプログラム実行時の各セグメントの配置とサイズは、通常、以下のようになります。
セグメント配置場所サイズ
コード(CS)ハードファイルシステム上のコード領域最大64KB
データ(DS)SRAM上のユーザプロセス用データ領域64KB
スタック(SS)本体内蔵RAM上のスタック領域ディスプレィモードによって変化
このセグメント構成は、メモリモデルでいうと、ほぼコンパクトモデルに相当します。 しかし、例えば、Turbo Cを用いてコンパクトモデルでコンパイルすると、 デフォルトのポインタが全て far で扱われるなど、効率の悪い面があります。 このため、通常はスモールモデルでコンパイルしておき、 DS == SS に基づいてコード生成されてしまう結果正常に動作しない部分のみ、 DS != SS で正常に動作するよう調整します。

例えば、関数やブロックの中でstaticではないローカル変数を定義しそのアドレスを参照する場合、 この変数のための記憶領域はスタック上に確保します。したがって、この変数のアドレスは SSベースでアクセスする必要がありますが、DS == SS が仮定されていると、DSベースでアクセスする コードを生成してしまいます。この場合、アドレスを取得する際に far ポインタを用いることで、 問題を回避できます。

void foo()
{
	char on_stack[10];		/* スタック(SS)上に確保される */
	static char on_data[10];	/* データセグメント(DS)上に確保される */

	char near *nearp;
	char far  *farp;

	nearp = on_stack;		/* NG! DS:on_stack を参照してしまう */
	farp  = on_stack;		/* OK. SS:on_stack を参照する */

	nearp = on_data;		/* OK. DS:on_data を参照する */
	farp  = on_data;		/* OK. DS:on_data を参照する */
		:
		:
}

Turbo C で longの乗除算が使えない

Turbo C で long 乗除算を使うプログラムをコンパイルする場合はタイニィモデルを指定して下さい。

タイニィモデルでコンパイルするには、コマンドラインオプションで -mt を指定します。 サンプル群をタイニィモデルでコンパイルするには、 WWitch\samples\makefile.tcc 中の CFLAGS の定義で -ms となっている部分を -mt に変更します。

Turbo C では、longの乗除算はヘルパルーチン(特殊なライブラリ関数)を用いて行ないます。 ヘルパルーチンはfar関数として設計されているため、タイニィモデル以外のメモリモデルを指定して コンパイルするとセグメント値の調整が必要になります。 しかし、WonderWitchではこの調整を行わないため、正しくヘルパールーチンを動作させることができません。

LSI C-86 for WonderWitch や Turbo C++ ではlong演算のライブラリ関数がnear関数となっているため、 この問題は発生しません。


構造体の実体コピーができない

LSI C-86 for WonderWitchでは、構造体の実体をコピーが正常に行なえません。 これは、コピー元の構造体がスタック上に確保されている場合に起こる問題で、 DS != SS であることに起因しています。

回避策としては、以下が考えられます。

また、これとは別に、Turbo C では long の乗除算と同じ理由により、 構造体のコピーを使用する場合にはタイニィモデルを指定してコンパイルする必要があります。 詳しくは「Turbo C で longの乗除算が使えない」 の説明を参照して下さい。


リンク位置はなぜ2000:0000なのか

特に理由はなく、便宜的なものです。 実際、リンク時に指定したコードの配置場所が実際に使用されることはありません。

FreyaOSでは、プログラムはファイルシステムに配置された状態のまま、 初期化済みデータのみがSRAM上のユーザプロセス用データ領域にコピーされて実行されます。 したがってコードの位置は実行時まで不定で、かつ、ファイルの配置状態によって変化することになります。


シリアル通信のエラーをクリアしたい

現在、シリアル通信制御BIOSにはエラーをクリアする機能が用意されていません。 この機能の提供は検討中ですが対応時期などについては未定です。

暫定的な対応策としては、COMM_CLOSE、COMM_OPENを行なうことでエラーをクリアすることができます。


sys_wait(1) で簡単にVBLANK一回分待つ

VBLANKを利用してタイミング合わせを行なう場合、sys_wait(1) を用いるのが簡単です。

fwrite() で2048バイト以上書き込みたい

FreyaOSでは、ファイルは作成時に最大サイズを決定し、 ファイルシステム上で占める領域の大きさを確定します。 領域の大きさはブロック数で指定し、1ブロックの大きさは128バイトです。 明示的にサイズを指定せずに、open() や fopen() でファイルを作成する場合には、 デフォルトのブロック数(sys/fcntl.hで定義されている DEFAULT_FCOUNT。現状16)となります。

このため、普通に open() や fopen() でファイルを作成すると、 128 x 16 = 2048バイト の領域が確保され、これ以上書き込むことはできません。

create() を用いると、ファイル生成時にファイルのブロック数を指定できます。 2048バイトより大きなファイルを作りたい場合には、 open() / fopen() を行なう前に、create() を用いて必要なブロック数の領域を明に確保してください。


Copyright (C) 2000,2001 Qute Corporation. All rights reserved.
Copyright (C) BANDAI 2000