マッパメモリ

「マッパメモリ」の編集履歴(バックアップ)一覧に戻る

マッパメモリ - (2019/06/18 (火) 00:45:24) のソース

-マッパRAM

マッパRAMは、メモリ拡張のもう一つの仕組み。
マッパーと呼ばれる拡張はオプションのメモリRAM拡張の仕組み。マッパはRAMを拡張する。

ページセグメントの単位はスロットの仕組みと同じ16KB単位で似ているが、スロットとは異なる
実装である。
RAMの16KBページセグメント単位を自由自在に64KB物理アドレスに割り当てることができ、
メモリー空間を拡張する。
このマッパと呼ばれるメモリ拡張はRAMにのみ適用される(スロットはROM/RAM混載が可能)
ページングの制御の仕方によってはリニアなメモリ拡張のように扱う事も可能。


マッパのページング単位はスロットと同じ16KB単位で64KB物理アドレス空間を4分割した
領域に対して外部拡張メモリをアドレスセレクトして割り当てる。

#asciiart(){
物理アドレス(16K単位)
0             FFFF
---------------
|A   |B   |C   |D   |E
---------------
  |    |    |    |
---------------------------
|p0  |p1  |p2  |p3  |p4  |p5  |....|p255 |
---------------------------
拡張ページアドレス
}

A=0x0
B=0x4000
C=0x8000
D=0xC000
E=0xFFFF

p0-p255=4MByte

論理メモリアドレスの拡張はI/Oポートの0xFC-0xFFを使う。
最大ページング数は256*16KB=4MBとなる。

日本国内で販売されたMSX2では64KB搭載のハードウエアしか販売されずメモリマッパは搭載されなかったが、
海外製MSX2では512KB拡張されており拡張メモリはマッパセグメントでアクセスすることができた。
しかし2/2+のDOS1やROMBASICにはマッパ拡張RAMをサポートするAPI(BIOS)やファンクションコールは搭載されていない。
(直接ハードウエア制御するルーチンを自前で用意する必要があった)
そのほかメモリマッパはDOS2環境において標準搭載されている。

ROMBASICは整数が2byteのintしかなく、言語上Longサイズの整数を扱えるように設計さえているわけ
でもないので使う必要性もない。(ROMBASICでは拡張メモリの広大なアドレスがポイントできない)
ROMBASIC動作時は物理アドレスにページングの際の空きがないのでいずれにしても拡張メモリを
利用できない。

本格的な拡張メモリの利用はDOS環境が必要となる。このためDOS2が必要になる。DOS1環境で拡張
メモリを使う際には独自のルーチンやAPIを用意する必要がある。DOS2はBIOSROMに
メモリアロケーションのAPI(BIOS)を持っている。
これらAPIはC言語のmalloc/freeと似た機能で16KBごとのページング単位で管理される。
ページセグメントはmalloc/freeのようにアロケーションしたり物理アドレスにマッピングすることができ、
メモリのアロケーション管理はDOS2システム側が行なう。

大きな拡張メモリを利用する場合、拡張RAMを使ってヒープを確保するアプリはDOS環境以外には
ないと思うので使う必要性はないだろう。

このマッパと呼ばれるRAM拡張の仕組みはリニアなアドレス拡張ではないが、ページングセグメント単位の
仕組みを逆に利用してマルチタスク(マルチプロセス)を実現することができる。
実際にこれらの仕組みを利用しているのがUzix/Symbos/MNBIOSなどである(DOS2とは別のOSなので
当然DOS2のファンクションコールは使っていない)。



-sdccのmalloc/freeについて

malloc/freeはC標準関数(stdlib.h)の動的メモリ確保のための関数だが、
メモリの動的な確保はデータ領域のメモリが割り当てられる。
具体的にいうとmalloc/freeで確保されたヒープ領域は、コンパイラコマンドオプションの
コード領域とデータ領域指定アドレスの、データ領域のRAM上に置かれる。

Cのmalloc/freeは拡張メモリの利用を前提としていないのでそのままでは拡張RAMを使う事は
できない。
C標準ライブラリのmalloc/freeはメモリのデータ領域に確保されるものなので、
元々小さなZ80の物理メモリアドレス空間では大きなサイズを確保することは難しい。


-特定アドレス領域のポインタ参照とマッパセグメント切替え

0x1000-0x2000などの領域を指定してメモリアクセスする際はポインタ経由のアドレス参照
を使う事が出来る。

DOSが起動しているときZ80の64KBの全ての物理アドレス空間はRAMが割り当てられている。
このRAMはスロットの仕組みを経由している。RAM領域をセグメント切替を行なうものが
マッパーと呼ばれる拡張メモリ方式なので、このセグメントを切替えてページングすれば
多くのメモリを扱う事が出来る。

例えばchar型の配列として0x1000-0x2000を使うのであれば、

 //charのポインタ型
 char *p;
 
 //アドレス設定
 p=(char *)0x1000;
 
 //配列参照
 p[0]=0;

などのように書く事が出来るだろう。
これらの手法を用いて拡張メモリ領域をアクセスする事ができる。

拡張メモリを扱う際の注意点は、物理メモリマップを直接切り替える処理を伴うので慎重に
おこなうこと。
通常WindowsなどのOSでは同様のメモリアクセスはカーネルがシステム(スーパーバイザ)
コールで処理する。ユーザープログラムが直接関与することはないのだが、
MSDOSに代表されるDOSはそうした高度な管理機構を持たないのでユーザー側でコードを書く
必要がある。

拡張RAMのページング切替えはI/Oポートの0xFC-0xFFを使う。DOSが起動している際はZ80の
64KBの物理アドレス空間のうちページスロット0,3はシステム領域なので、ページスロット1,2
などを使ってメモリ空間を切り替える。

単純な例を示すと、拡張メモリが4MByte搭載されているとき、
0x8000-0xBFFF(ページスロット2)の物理アドレス空間に拡張メモリのページセグメント255
のメモリ区画のページング切替を行なう場合、

 outp(0xfe,255);

とする。
特定のメモリ領域を使いたい場合は以下のようなコードでアクセス可能だ。

 #include <stdlib.h>
 
 extern unsigned char inp(unsigned char);
 extern outp(unsigned char,unsigned char);
 
 void main(void){
 	char *p;
 	int i;
 
 	p=(char *)0x8000;
 
 	//allocating segment
 	outp(0xfe,255);
 
 	for(i=0; i<100; i++){
 		p[i]=128;
 	}
 
 	//freeing segment
 	outp(0xfe,1);
 }

この例では外部拡張メモリの16KByte(1 Segment)を参照している。
連続したメモリ領域にアクセスする場合はこのコードを拡張すれば良いだろう。

注意が必要な点は、使った領域は必ず元に戻す事と、割り込みは禁止しておく
こと。
(利用している物理アドレス空間が0x8000-0xBFFF(ページスロット2)で、システム領域では
ないので割り込み禁止しなくとも一応動作する)
マッパーセグメントの切替は物理アドレスが切り替わっているので注意が必要。

そのほか初期状態のセグメント番号が判らないので問題となる。
4MB搭載を前提としているのであれば直接セグメント初期値が判るが、基本は初期化時に値を求める。
最初に容量をチェックし、そこからI/Oポート初期値を求めて、グローバル変数として保持
しておき、参照時は変数を読み、書き換え時はI/Oレジスタと変数を書き換える仕組み。

もう一つの注意点は拡張メモリであるマッパセグメントはI/Oを使っているがレジスタではないので値が読み出せない。
セグメント番号を読むことが可能なハードもあるがそれは後期のマシンであり
基本はWriteOnlyだということ。
(そもそもセグメント番号を書き込むI/Oポートの仕組みはレジスタではないので参照できない)

海外製と日本国内製のマッパアドレッシングの実装にも若干違いがあると指摘されている。
これらの点から、セグメント番号参照はソフトやシステムアプリケーション側で管理しておき、I/Oポートは書き込み
だけを行なうという管理方法が必要だろうということ。

-拡張メモリサイズの調査

拡張RAMの物理サイズは利用状況によって違いがあり一定ではないので実際のメモリ量は
プログラムによりチェックする必要がある。
これはPC(Windowsなどが動くコンピュータ)の起動時のBIOSのメモリチェックと全く同じ。

拡張メモリの搭載容量チェックはメモリセグメントの先頭の1byteを読み書きしてRAMが
存在するらしい事をチェックしながら、255このセグメントをスキャンし、
最大搭載容量を把握する。

ユーザーによるTPAフリーエリアやマッパと呼ぶ拡張メモリの確認方法は簡単で
一般的には以下のような方法で拡張ページメモリのサイズや領域を確認することができる。
ここではCP/M互換の64KB以内のDOSコマンド実行ファイルのフリーエリア領域の大きさと、
拡張ページメモリの最大値を求める。拡張メモリはプライマリのみとする。
CPMのDOSファンクションコールの先頭アドレスが実質的なフリーエリアを示すため、
先頭のゼロページを引いた値がTPAフリー領域となる。またI/Oポートの内容を読む
ことでハードウエアに組み込まれたプライマリ拡張ページメモリの容量を知る事ができる。

 #include <stdio.h>
 
 extern unsigned char inp(unsigned char);
 
 void main(){
 	unsigned int *tpa;
 	unsigned int tpasize;
 	unsigned int psize;
 
 	//CPM互換システムコールジャンプ先&TPAフリーエリア取得アドレス
 	tpa=(unsigned int *)0x0006;
 	tpasize=*tpa-254;
 
 	//ページメモリ容量取得(プライマリ)
 	psize=inp(0xFF);
 	psize=(256-psize)*16;
 
 	printf("Top of address: %x \n",*tpa);
 	printf("TPA free: %u bytes\n",tpasize);
 	printf("Extend memory: %u kb \n",psize);
 }


-拡張メモリのセグメント番号

マッパ拡張メモリは搭載容量によってセグメント番号が変化する。
セグメント番号とアドレスの関係はメモリサイズにより変わる。ここでは説明のため最大容量
4MByteで考える。

4MBの拡張RAM領域はアドレス0x0-0x3FFFFFの空間を持つ。
以下は拡張メモリI/Oレジスタアドレスと対応する物理アドレスの関係。
I/Oポートの0xFFは、物理アドレスの0xC000-0xFFFF区画(スロットページ3)にどのメモリ
セグメントを割り当てるかを指定する。
セグメントは255個なので合計で4MBのアドレス範囲となる。

0xFC,0x0000-0x3FFF,SLOT PAGE-P0
0xFD,0x4000-0x7FFF,SLOT PAGE-P1
0xFE,0x8000-0xBFFF,SLOT PAGE-P2
0xFF,0xC000-0xFFFF,SLOT PAGE-P3

拡張メモリが4MB搭載されておりDOS1が起動しているとき、64KBの物理アドレス空間と拡張
メモリアドレスのセグメント空間のI/Oポート設定状態は以下のようになる。

物理アドレス0x0000-0x3FFF,SLOT PAGE-P0	-- SEGMENT 3
物理アドレス0x4000-0x7FFF,SLOT PAGE-P1	-- SEGMENT 2
物理アドレス0x8000-0xBFFF,SLOT PAGE-P2	-- SEGMENT 1
物理アドレス0xC000-0xFFFF,SLOT PAGE-P3	-- SEGMENT 0

拡張メモリのセグメント番号は最大で255を持つ。
物理メモリに対する拡張メモリの割り当てはやや変則的で、物理アドレス0xC000-0xFFFFの場所、
いわゆるワークエリアが使っているアドレスに拡張メモリのセグメント0(0x0から始まる16KB)
が割り当てられている。

物理アドレスの最大値0xFFFF側から小さいアドレスに向かって降順にセグメントが0から割り
当てられると考えていいだろう。

もし拡張メモリのサイズが2MBであれば、セグメント番号は128-255となる。
拡張アドレス範囲は0x0-0x1F,FFFFとなる。
物理メモリとセグメントの割り当ては以下のようになる。

物理アドレス0x0000-0x3FFF,SLOT PAGE-P0	-- SEGMENT 0x83(131)
物理アドレス0x4000-0x7FFF,SLOT PAGE-P1	-- SEGMENT 0x82(130)
物理アドレス0x8000-0xBFFF,SLOT PAGE-P2	-- SEGMENT 0x81(129)
物理アドレス0xC000-0xFFFF,SLOT PAGE-P3	-- SEGMENT 0x80(128)


1MB搭載のRAM時の有効なセグメント番号は192-255の64セグメントのみとなる。
物理メモリへの割り当ては192-195が降順にページマッピングされる。
拡張RAM容量計算が必要だが、面倒であればRAM容量決めうちでコードを書く方法もある。


-TRでの拡張メモリの扱い

2/2+では拡張メモリを搭載すると、本体では内部のメモリと外部カートリッジの拡張メモリのサイズの
比較が行なわれ、最大容量を持つ拡張メモリがシステムにより選択される。

このとき物理的にはスロットと呼ばれる拡張機能を介して、二つのメモリが見えている。
一つは元々ハードウエア内部にあった標準搭載64KBのメモリで、通常は3-0などのスロットに
割り当てられている。
もう一方の拡張メモリはスロット1,2のどちらかに外部カートリッジで接続されている。

通常2/2+では最大容量を持つメモリ区画がデフォルトとして起動時に選択される。
しかしTRでは外部カートリッジにもっとも大きな容量の拡張メモリを接続しても本体内部の
メモリが選択される。
理由は不明だがこれは処理速度やハード構成との関係で内部メモリが選択されるのかもしれない。
拡張カートリッジに拡張メモリを搭載すると、複数のメモリが繋がる事になるが、システムで
使われるメモリはどちらか一方のみとなる。

例えば本体に64KBのメモリがあり、拡張メモリ512KBが接続されている場合、2/2+では
マッパー容量の大きい512KBがプライマリとして選択されるが、64KBは未使用となる。
(起動時のメモリ量表示は512+64の数値となるが実際は一方のみ)

一方でTRでは本体256KB、拡張2MBであっても本体側の256KBのみが必ず選択される。
メインメモリは必ず本体内部の256KBからとられ、本体側のメモリがプライマリとなる。
拡張メモリは容量が大きくともプライマリには選択されない。
外部の拡張メモリを利用したい場合はスロット管理+マッパセグメントを切替えて併用する
必要があるかもしれない。

ただしTRでは本体側のメモリはプライマリとしてセレクトされるが、DOS2のRAMDISK機能は
拡張メモリを使うことができる。
DOS2のRAMDISK機能はセカンダリ拡張メモリマッパに対応しているので追加した拡張メモリを
全てRAMDISKとして利用することが可能。


TRの拡張メモリセグメント選択のI/Oポートレジスタも若干違いがあるようだ。
I/OポートはWrite Onlyなので値を読んでも正しいセグメント値が得られるとは限らないし、
また回路設計によっては誤動作を招く危険性も指摘されている。
しかしTRではセグメント切替のI/OレジスタのRead/Writeに対応しているらしい。
互換性を考えるとセグメント番号はコードで管理し、I/OはWrite Onlyにするのが無難だろう。
DOS2ではOSが管理するのでAPIを使うだけで良い。


-連続した拡張メモリのアクセス

DOS1環境で単純な16KBの拡張メモリのセグメント切替を発展させて4MBの連続した領域を使う例を示す。

 #include <stdlib.h>
 
 extern unsigned char inp(unsigned char);
 extern outp(unsigned char,unsigned char);
 
 void main(void){
 	char *p;
 	int i;
 	int seg;
 
 	//page address (P1,0x8000-0xBFFF)
 	p=(char *)0x8000;
 
 	//4MB
 	for(seg=4; seg<255; seg++){
 		//allocating segment
 		outp(0xfe,seg);
 
 		for(i=0; i<16383; i++){
 			p[i]=128;
 		}
 	}
 
 	//freeing segment
 	outp(0xfe,1);
 }

コード修正箇所はセグメント操作を追加しただけ。
処理内容は、利用している拡張セグメントは物理領域に割り当てられた64KBを除いた4MB領域の
初期化を行なうものだが、
Z80では4MBの拡張メモリ全領域を初期化するだけで1分間以上の時間を要する。

バッファ領域として用いる場合は、拡張メモリアクセス期間中は物理メモリアドレスが
ページングで変わっているので必ず他の処理は禁止すること。
拡張メモリのアクセス中は割り込みを禁止し、なおかつDOSシステムコールを呼ばない、
他の処理をしないことが必要。
高機能で配慮されたOSならばこれらの処理はスーパーバイザ(システム)やリングプロテクション
内で行なわれるという事。

他の処理を行なう場合は拡張メモリアクセスを完了し、セグメントを元に戻してから処理を行なう。
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。