ユーザプログラムを実行する場合、SRAM バンクには通常プロセスに割り当てられたデータエリアが マッピングされ、この領域にプログラムのデータセグメントが配置されます。
しかし、ファイルシステムのアクセスなど、一部の機能を実行する際に SRAM バンクの切替えが行なわれ、 一時的に OS のワークエリアやソフトファイルシステムのデータ領域がマッピングされる場合があります。
このようなタイミングでハードウェア割り込みが発生すると、割り込みハンドラに制御が 移った時点ではユーザプログラムのデータセグメントがマッピングされていない状態となります。 この状態で割り込みハンドラ内でユーザプログラムのデータセグメントに配置されている変数などを アクセスすると意図せぬ領域がアクセスされてしまい、システムのデータを破壊してしまったり、 暴走を招く結果となってしまいます。
この関数とバンク制御 BIOS の機能、bank_set_map()、bank_get_map() を用いて、 割り込みハンドラでおこなう処理の前後で SRAM バンク状態を保存/設定/復帰します。 以下に割り込みハンドラの具体的な記述方法を示します。
呼び出し形式 int near pcb_get_srambank(void)
機能説明 現在実行中のプロセスのデータセグメントが配置されている SRAM バンク番号を取得する。 ヘッダファイル sys/process.h
#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); }
具体的には、ASCII1、ASCII2の各ディスプレイモードを利用時には最大128キャラクタ(キャラクタ番号0〜127)、 日本語1、日本語2を利用時には最大512キャラクタ(キャラクタ番号0〜511)となります。
これは、本来、KEY_PRESS_CHECKがワークエリアを更新すべきではないのに、 更新してしまっていることに由来する問題ですが、 動作の一意性保証の観点から将来的な対応については現在のところ予定していません。
一般的な回避策としては、KEY_PRESS_CHECKのみを用いてKEY_HIT_CHECK相当のものは 自前で処理するということになります。 例えば、垂直帰線期間割り込み(VBLANK割り込み)のたびに一度だけKEY_PRESS_CHECKを呼び出し、 結果を自前のワークエリアに保存しておいて、押下状態の取得はこのワークエリアを参照します。
このとき、実行対象となるRunnableILが提供する関数郡のアドレス解決は、 FreyaOS内部でスタック上に動的に確保されるRunnableIL型の変数を用いて行なわれています。
インダイレクトライブラリの性質上、 アドレス解決をする関数の数が増えれば確保すべき変数のサイズも変わるため、 init.rcで実行すべきインダイレクトライブラリが、 システムが想定しているより多くの関数のアドレスを解決を必要とする場合、 FreyaOS内部で確保されている変数領域では不足するため、 スタックを破壊し、システムが暴走してしまいます。
このため、init.rcから呼び出すインダイレクトライブラリは、 厳密にRunnableIL型に合致させておく必要があります。
この問題は一般的にはアドレス解決すべき関数の数を動的に取得して、 それに応じたアドレス解決用のバッファを取得することで回避できますが、 init.rcからの呼び出しに関してはシステムの仕様と位置付けられています。
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での提供については未定です。
csは、割りこみハンドラのセグメントアドレスで、callbackメンバーとともに 関数のfarアドレスを構成します。通常、割りこみを設定する際の CS レジスタ の値をそのまま設定します。
また、dsは、割りこみハンドラを実行する際のデータセグメントで、 通常、割りこみを設定するときの DS レジスタの値をそのまま設定します。
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