ppcsim Ver0.73インターナル

目次

  1. このドキュメントについて
  2. 内部管理の概要
  3. メモリモデル
  4. スタックポインタとargc/argv
  5. システムコールエミュレーション
  6. IOデバイス
  7. その他
  8. 履歴

このドキュメントについて

当初は、すぐに使わなくなるだろうと思っていた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命令でアクセスするアドレス空間との対応は以下のようになっています。

               ┌──┬───────┐
  PPCアドレス  │0-3 │    4 - 31    │
               └──┴───────┘
                  │          ↓
                  │  ┌───────┐
                  │  │セグメント 0  │
                  │  ├───────┤
                  │  │セグメント 1  │
  シミュレータ    └→├───────┤
     メモリ           │      :       │
                      ├───────┤
                      │セグメント 15 │
                      └───────┘


シミュレーションメモリの割り当ては memal コマンドで行います。セグメントの割り当て が無いか、セグメント内のセグメントサイズを越えるアクセスを行うとメモリアクセス例外が 発生します。

Ver0.70でのデフォルトメモリ割り当ては、以下のようになっています。

---- 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


Ver0.70では、0x80000000にはPseudo-IOデバイスポートが割り当たっています。 0xf0000000からはPseudo-グラフィックデバイス空間が割り当たっています。

セグメント0はプログラム配置領域ということで、fork()システムコール時に 親プロセスからの資源を受け継ぐ唯一の領域になっています。従いまして、 セグメント0以外の領域にデータやプログラムを置いて、fork()システムコール を実行しても、子プロセスに引き継がれないので注意して下さい。

スタックポインタとargc/argv

プログラムの実行を行う場合、引き数を与える場合があると思います。引き数 文字列の格納場所および、スタックポインタの先頭は以下のように セグメント0のメモリ領域の一番最後尾部分を使用しています。

      0x00000000┌───────────────┐
                │ユーザテキスト/データ領域     │
     -0x????????├───────────────┤
                │スタック領域                  │
     -0x00001000├───────────────┤
                │argvポインタリスト( 256byte)  │
     -0x00000f00├───────────────┤
                │argv文字列群      (3840byte)  │
      0x0XXXXXXX└───────────────┘


SPはGPR1,argcはGPR3,argvはGPR4にそれぞれ格納されます。
引き数文字列が領域に収まらない場合はプロセスの起動に失敗します。 注意点として、スタックはアドレスの前方向に消費してゆきます。 そのため、スタックを大量に消費すると、データ領域を破壊する場合があります。 シミュレータではチェックできませんので、シミュレータ上で動作する プログラムでスタックオーバーフローを起こさないように配慮する必要があります。

システムコールエミュレーション

プロセスレベルでの実行を可能にするため、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()        同一関数をネイティブで実行


同一関数をネイティブで実行するものは、引き数をそのままネイティブ関数 に渡すため、エンディアンや構造体メンバーの違いに注意する必要があります。 シミュレータ上で動作する実行ファイルを生成する際の標準ライブラリは newlibを使用しています。Cygwinネイティブでの標準ライブラリもnewlibを 使用しているため、引き数を渡す際はエンディアンにのみ注意を払う必要が ありますが、他のOSで動作させる場合は構造体メンバーの違いにも注意を 払う必要があります。場合によってはPPC用標準ライブラリをOSと同じものに する必要がある(同じにした方が楽?)かも知れません。
fork(),execve(),exit()については、ppcsim内のプロセス管理ルーチンが 実行されます。fork()実行時に子プロセスに受け継がれるメモリ領域はVer0.70では セグメント0の領域のみとなっていますので、セグメント0以外にメモリを 割り当てて使用する場合には注意が必要です。

sbrk()については、ppcsim内のプロセス情報でヒープメモリの先頭アドレス を保持しています。ヒープの先頭アドレスはppcsim内でハードコーディング されており、0x200000が先頭になります。sbrk()システムコールが実行 されると、メモリ領域をチェックしたのち、sbrk()の仕様にしたがった 戻り値を返します。

IOデバイス

システムコールエミュレーション実行を行う場合は、標準出力やファイルが 使用できるため、プログラムの実行結果などはそれらに出力すれば良いでしょう。 しかし、OSレベルの開発に使用する場合にはエミュレーションを使用することは できませんので、結果の出力や格納には本物のハードウェアと同様のデバイスを シミュレートする必要があります。

当初はIOデバイスを実装する予定は無かったのですが、Linuxカーネルを実行する という血迷った経緯を踏んだ事から、簡単なIOデバイスの実装を余儀されなく なりました。

Ver0.70で実装されているIOデバイスには以下のものがあります。

  1. 出力専用コンソールポート
  2. ディスクデバイス
  3. 時計デバイス
  4. グラフィックデバイス

いずれもきわめて単純な方法で実装されています。実際にアクセスは それぞれアクセスポートの割り当てられたアドレス空間を、手順に従い操作 することで行います。因みに、Linuxカーネルを実行する際に使用したのは、 出力専用コンソールポートだけで、他は取り合えず付けただけという感じに なっています(^_^;

----出力専用コンソールポート------------------------
ポートアドレス

#define CONSOLE_OUT_CMD      0x8000ff00

CONSOLE_OUT_CMDにint型で0以外の値を書きこむとppcsimコンソールに一文字出力 されます。

----ディスクデバイス--------------------------------
ポートアドレス

DISK_CMD             0x8000ff10
DISK_ACK             0x8000ff14
DISK_DRIVE           0x8000ff18
DISK_TRACK           0x8000ff1c
DISK_DATA_TOP        0x8000ff20
DISK_DATA_LEN        0x8000ff24

DISK_DRIVEにドライブ番号、DISK_TRACKにアクセスするトラック番号、 DISK_DATA_TOPにディスクからの読みこみもしくは書き込みを行う メモリアドレス、DISK_DATA_LENに読みこみもしくは書き込みを行う バイト数を指定し、最後にDISK_CMDにアクセスコマンドを書き込みます。 アクセスコマンドには以下の種類があります。
DISK_INQ             0x01
DISK_READ            0x02
DISK_WRITE           0x03
DISK_WRITE_INQ       0x04

----時計デバイス------------------------------------
ポートアドレス

CLOCK_READ_CMD        0x8000ff30
CLOCK_DATA            0x8000ff34

CLOCK_READ_CMDにint型で0以外の値を書きこむと、CLOCK_DATAに4byteの timi_t型の現在時刻がセットされます。

----グラフィックデバイス----------------------------
ポートアドレス

GRAPHIC_VRAM_SYNC     0x8000ff40
GRAPHIC_VRAM_TOP      0xf0000000 〜

グラフィックデバイスは GRAPHIC_VRAM_TOPから、2048×480×32bit の実アドレス空間に 割り当てられています。実際の表示はGRAPHIC_VRAM_TOPから640×480のサイズと なっています。画素は Alpha=8bit,Red=8bit,Green=8bit,Blue=8bitの32bitで 表されます。内部で描画バッファリングを行っているため、直ぐには描画に反映されません。 バッファをフラッシュするには0x8000ff40にint型で1を書き込む事でバッファを フラッシュし、アクセスしたデータが表示に反映されます。
これらのデバイスを使用し、PPCネイティブ動作するOSが開発されれば ppcsim自体を他のOSに移植した場合でも、全く同じ様にシミュレーション動作 させる事が可能なハズですが、このドキュメントを書いている時点では 実物のPPCネイティブ動作可能なppcsimで動作するOSは存在していません(^^;

その他

現在の実装状態は以上のようになっています。この先、都合により仕様が変更されたり 追加、削除される項目なども出てくると思うのですが、そういうものだという事で ご了承いただきたいですm(_'_)m。
また、説明不足な点や間違いについては、随時加筆修正していく予定です。

履歴

  2001/09/30 : 0.70ベース初版
  2001/10/28 : 細かい言いまわしを修正。
  2001/11/25 : 0.73ベースで書き換え。スタックポインタとargc,argvの項目を追加。


TOP PREV