当初は、すぐに使わなくなるだろうと思っていたppcsimですが、建て増しを続け ながら使い続けているうちに機能が増えてきたため、機能メモ/内部仕様書ということで 本ドキュメントを作成することにしました。興味のある方は読み物として読んでみて 下さい。
初期のppcsimは単純なCPU/メモリモデルのシミュレートしか行っていませんでしたが、
OS上で実行されるプロセスとして動作させた方が、プログラミングの勝手が良いで
あろうということで、Ver0.50からプロセスの概念の導入とシステムコール
エミュレーションを行うようになりました。
プロセスの管理といっても非常に単純なもので、プロセス毎にレジスタとメモリ
を独立に持っているだけです。着目するプロセス(一番新しく生成された
プロセス)を引き数としてシミュレーションコアエンジンに引き渡し、
シミュレーションを行います。
一つのプロセスが保持する情報 ┌──────┐ │ プロセス │ │----------- │ │GPR,FPR,SPR │ │Memory,etc │ └──────┘ 実行イメージ ┌─────┐ │プロセス0 │ └─────┘ ┌─────────┐ │プロセス1 │ │ シミュレーション │ └─────┘ 実行を行うプロセスを │ コアエンジン │ │ : │ シミュレーションコア └─────────┘ └─────┘ で実行する ↑ │プロセスn │────────────┘ └─────┘
小さなプログラムの実行を目的としていたため、多くても16MB程度しか使わないで
あろうということで、メモリ空間は0x00000000から指定のメモリサイズ分だけ
確保するという事にしていました。ところが、何を血迷ったか、Linuxカーネルを
ppcsim上で動作させようとしたとき、PCI空間のように非常に大きなアドレス空間
を使用する場合に不都合が生じました。32bitアドレスで表現できるメモリ空間
を割り当てられれば何の問題もありませんが、新世紀を迎えた世の中でも4GBの
メモリをポンと割り当てることは ままなりませんので、苦肉の策としてセグメント
方式を導入することにしました。
ppcsim内では 32bitのアドレス空間を 256MB×16セグメント に分割しています。
各セグメントはそれぞれ独立にメモリサイズの割り当てが行えます。
一つのプロセスが保持するセグメントリスト ┌───────┐ │セグメント 0 │ ├───────┤ │セグメント 1 │ ├───────┤ │ : │ ├───────┤ │セグメント 15 │ └───────┘ 一つのセグメントが保持する情報 ┌─────────────┐ │セグメント情報 │ │------------------------- │ │・セグメントのサイズ │ │・実メモリ領域へのポインタ│ └─────────────┘
┌──┬───────┐ PPCアドレス │0-3 │ 4 - 31 │ └──┴───────┘ │ ↓ │ ┌───────┐ │ │セグメント 0 │ │ ├───────┤ │ │セグメント 1 │ シミュレータ └→├───────┤ メモリ │ : │ ├───────┤ │セグメント 15 │ └───────┘
---- Memory map for PID 1 --- 000000000 : size=00800000 100000000 : no alloc 200000000 : no alloc 300000000 : no alloc 400000000 : no alloc 500000000 : no alloc 600000000 : no alloc 700000000 : no alloc 800000000 : size=00010000 900000000 : no alloc a00000000 : no alloc b00000000 : no alloc c00000000 : no alloc d00000000 : no alloc e00000000 : no alloc f00000000 : size=003c0000
プログラムの実行を行う場合、引き数を与える場合があると思います。引き数
文字列の格納場所および、スタックポインタの先頭は以下のように
セグメント0のメモリ領域の一番最後尾部分を使用しています。
0x00000000┌───────────────┐ │ユーザテキスト/データ領域 │ -0x????????├───────────────┤ │スタック領域 │ -0x00001000├───────────────┤ │argvポインタリスト( 256byte) │ -0x00000f00├───────────────┤ │argv文字列群 (3840byte) │ 0x0XXXXXXX└───────────────┘
プロセスレベルでの実行を可能にするため、sc命令実行を解釈した時に
実際のハードウェアとは異なったOSレベルの動作を行います。
ppcsimではPPC-Linuxでのシステムコール実行方法を解釈するように実装されています。
実行の際に、GPR0にシステムコール番号、GPR3〜にシステムコールの引き数を
与えます。実際のラッピング方法はライブラリの実装に依存します。
Ver0.70では以下のシステムコールが実装されています。
1: // exit() シミュレータで検出したらプロセスを破棄する 2: // fork() シミュレータで検出したら子プロセス生成し実行を移す 3: // read() 同一関数をネイティブで実行 4: // write() 同一関数をネイティブで実行 5: // open() 同一関数をネイティブで実行 6: // close() 同一関数をネイティブで実行 10: // unlink() 何もしない 11: // execve() シミュレータで検出したら実行ファイルをロードする 12: // chdir() 同一関数をネイティブで実行 13: // time() 同一関数をネイティブで実行 15: // chmod() 同一関数をネイティブで実行 19: // lseek() 同一関数をネイティブで実行 30: // utime() 同一関数をネイティブで実行 43: // times() 同一関数をネイティブで実行 54: // ioctl() 何もしない 55: // fcntl() 同一関数をネイティブで実行 78: // gettimeofday() 同一関数をネイティブで実行 89: // readdir() 同一関数をネイティブで実行 205: // _opendir() 同一関数をネイティブで実行し そのコピーをシミュレータメモリに持つ 206: // closedir() 同一関数をネイティブで実行 106: // stat() 同一関数をネイティブで実行 107: // lstat() 同一関数をネイティブで実行 108: // fstat() 同一関数をネイティブで実行 181: // chown() 同一関数をネイティブで実行 182: // getcwd() 同一関数をネイティブで実行 200: // isatty() 同一関数をネイティブで実行 202: // sbrk() シミュレータで検出したらメモリ領域をチェックして値を返す 203: // pathconf() 同一関数をネイティブで実行
システムコールエミュレーション実行を行う場合は、標準出力やファイルが
使用できるため、プログラムの実行結果などはそれらに出力すれば良いでしょう。
しかし、OSレベルの開発に使用する場合にはエミュレーションを使用することは
できませんので、結果の出力や格納には本物のハードウェアと同様のデバイスを
シミュレートする必要があります。
当初はIOデバイスを実装する予定は無かったのですが、Linuxカーネルを実行する
という血迷った経緯を踏んだ事から、簡単なIOデバイスの実装を余儀されなく
なりました。
Ver0.70で実装されているIOデバイスには以下のものがあります。
ポートアドレス #define CONSOLE_OUT_CMD 0x8000ff00
ポートアドレス DISK_CMD 0x8000ff10 DISK_ACK 0x8000ff14 DISK_DRIVE 0x8000ff18 DISK_TRACK 0x8000ff1c DISK_DATA_TOP 0x8000ff20 DISK_DATA_LEN 0x8000ff24
DISK_INQ 0x01 DISK_READ 0x02 DISK_WRITE 0x03 DISK_WRITE_INQ 0x04
ポートアドレス CLOCK_READ_CMD 0x8000ff30 CLOCK_DATA 0x8000ff34
ポートアドレス GRAPHIC_VRAM_SYNC 0x8000ff40 GRAPHIC_VRAM_TOP 0xf0000000 〜
現在の実装状態は以上のようになっています。この先、都合により仕様が変更されたり
追加、削除される項目なども出てくると思うのですが、そういうものだという事で
ご了承いただきたいですm(_'_)m。
また、説明不足な点や間違いについては、随時加筆修正していく予定です。
2001/09/30 : 0.70ベース初版 2001/10/28 : 細かい言いまわしを修正。 2001/11/25 : 0.73ベースで書き換え。スタックポインタとargc,argvの項目を追加。