当初は、すぐに使わなくなるだろうと思っていたppcsimですが、建て増しを続け ながら使い続けているうちに機能が増えてきたため、機能メモ/内部仕様書ということで 本ドキュメントを作成することにしました。興味のある方は読み物として読んでみて 下さい。
初期のppcsimは単純なCPU/メモリモデルのシミュレートしか行っていませんでしたが、
OS上で実行されるプロセスとして動作させた方が、プログラミングの勝手が良いで
あろうということで、Ver0.50からプロセスの概念の導入とシステムコール
エミュレーションを行うようになりました。
プロセスの管理といっても非常に単純なもので、プロセス毎にレジスタとメモリ
を独立に持っているだけです。着目するプロセス(一番新しく生成された
プロセス)を引き数としてシミュレーションコアエンジンに引き渡し、
シミュレーションを行います。
一つのプロセスが保持する情報
┌──────┐
│ プロセス │
│----------- │
│GPR,FPR,SPR │
│Memory,etc │
└──────┘
実行イメージ
┌─────┐
│プロセス0 │
└─────┘ ┌─────────┐
│プロセス1 │ │ シミュレーション │
└─────┘ 実行を行うプロセスを │ コアエンジン │
│ : │ シミュレーションコア └─────────┘
└─────┘ で実行する ↑
│プロセスn │────────────┘
└─────┘
小さなプログラムの実行を目的としていたため、多くても16MB程度しか使わないで
あろうということで、メモリ空間は0x00000000から指定のメモリサイズ分だけ
確保するという事にしていました。ところが、何を血迷ったか、Linuxカーネルを
ppcsim上で動作させようとしたとき、PCI空間のように非常に大きなアドレス空間
を使用する場合に不都合が生じました。32bitアドレスで表現できるメモリ空間
を割り当てられれば何の問題もありませんが、新世紀を迎えた世の中でも4GBの
メモリをポンと割り当てることは ままなりませんので、苦肉の策としてセグメント
方式を導入することにしました。
ppcsim内では 32bitのアドレス空間を 16MB×256セグメント に分割しています。
各セグメントはそれぞれ独立にメモリサイズの割り当てが行えます。
一つのプロセスが保持するセグメントリスト
┌───────┐
│セグメント 0 │
├───────┤
│セグメント 1 │
├───────┤
│ : │
├───────┤
│セグメント 255│
└───────┘
一つのセグメントが保持する情報
┌─────────────┐
│セグメント情報 │
│------------------------- │
│・セグメントのサイズ │
│・実メモリ領域へのポインタ│
└─────────────┘
┌──┬───────┐
PPCアドレス │0-7 │ 8 - 31 │
└──┴───────┘
│ ↓
│ ┌───────┐
│ │セグメント 0 │
│ ├───────┤
│ │セグメント 1 │
シミュレータ └→├───────┤
メモリ │ : │
├───────┤
│セグメント255 │
└───────┘
PPC> memal ---- Memory map of PID 0 --- 00000000 : size=00010000 , normal 01000000 : size=00800000 , normal 08000000 : size=01000000 , normal 80000000 : size=00010000 , shared f0000000 : size=003c0000 , shared
プログラムの実行を行う場合、引き数を与える場合があると思います。引き数
文字列の格納場所および、スタックポインタの先頭は以下のように
セグメント0のメモリ領域の一番最後尾部分を使用しています。
0x00000000┌───────────────┐
│ユーザテキスト/データ領域 │
0x00??????├───────────────┤
│スタック領域 │
-0x00001000├───────────────┤
│argvポインタリスト( 256byte) │
-0x00000f00├───────────────┤
│argv文字列群 (3840byte) │
0x00XXXXXX└───────────────┘
プロセスレベルでの実行を可能にするため、sc命令実行を解釈した時に
実際のハードウェアとは異なったOSレベルの動作を行います。
ppcsimではPPC-Linuxでのシステムコール実行方法を解釈するように実装されています。
実行の際に、GPR0にシステムコール番号、GPR3〜にシステムコールの引き数を
与えます。実際のラッピング方法はライブラリの実装に依存します。
Ver0.96では以下のシステムコールが実装されています。
| No | name | 説明 |
| 1 | exit() | シミュレータで検出したらプロセスを破棄する |
| 2 | fork() | シミュレータで検出したら子プロセス生成し実行を移す |
| 3 | read() | 同一関数をネイティブで実行 |
| 4 | write() | 同一関数をネイティブで実行 |
| 5 | open() | 同一関数をネイティブで実行 |
| 6 | close() | 同一関数をネイティブで実行 |
| 10 | unlink() | 何もしない |
| 11 | execve() | シミュレータで検出したら実行ファイルをロードする |
| 12 | chdir() | 同一関数をネイティブで実行 |
| 13 | time() | 同一関数をネイティブで実行 |
| 15 | chmod() | 同一関数をネイティブで実行 |
| 19 | lseek() | 同一関数をネイティブで実行 |
| 20 | getpid() | シミュレータ上で割り当てたプロセスIDを返します(for linux ELF) |
| 24 | getuid() | 500を固定で返す(for linux ELF) |
| 30 | utime() | 同一関数をネイティブで実行 |
| 33 | access() | 同一関数をネイティブで実行 |
| 38 | rename() | 同一関数をネイティブで実行 |
| 39 | mkdir() | 同一関数をネイティブで実行 |
| 41 | dup() | 同一関数をネイティブで実行 |
| 43 | times() | 同一関数をネイティブで実行 |
| 45 | brk() | ヒープ領域からシミュレータで領域を割り当てます(for linux ELF) |
| 47 | getgid() | 544を固定で返す(for linux ELF) |
| 49 | geteuid() | 500を固定で返す(for linux ELF) |
| 50 | getegid() | 544を固定で返す(for linux ELF) |
| 54 | ioctl() | TIOCGWINSZとTCGETSをネイティブで実行 |
| 55 | fcntl() | 同一関数をネイティブで実行 |
| 60 | umask() | 同一関数をネイティブで実行 |
| 63 | dup2() | dup()をネイティブで実行 |
| 64 | getppid() | シミュレータ上での親プロセスIDを返します |
| 65 | getpgrp() | getpgrp()をネイティブで実行 |
| 77 | getrusage() | 常に0を返す(for linux ELF) |
| 78 | gettimeofday() | 同一関数をネイティブで実行 |
| 85 | readlink() | 同一関数をネイティブで実行 |
| 89 | readdir() | 同一関数をネイティブで実行 |
| 90 | mmap() | シミュレータでヒープよりメモリを割り当てます。アドレスは 0のみ有効(for linux ELF) |
| 91 | munmap() | シミュレータでヒープに返す(for linux ELF) |
| 106 | stat() | 同一関数をネイティブで実行 |
| 107 | lstat() | 同一関数をネイティブで実行 |
| 108 | fstat() | 同一関数をネイティブで実行 |
| 114 | wait4() | シミュレーターで自プロセスIDを返します |
| 122 | uname() | sysname :"ppcsim", nodename:"none", release :"92", version :"0", machine :"ppcsim" を返します |
| 125 | _mprotect() | 0を固定で返します(for linux ELF) |
| 133 | fchdir() | 同一関数をネイティブで実行 |
| 140 | llseek() | 同一関数をネイティブで実行(for linux ELF) |
| 173 | rt_sigaction() | 0を固定で返します |
| 174 | rt_sigprocmask() | 0を固定で返します |
| 181 | chown() | 同一関数をネイティブで実行 |
| 182 | getcwd() | 同一関数をネイティブで実行 |
| 190 | ugetrlimit() | 0を固定で返します |
| 195 | stat64() | stat()をネイティブで実行しstat64()のフォーマットに変換(for linux ELF) |
| 196 | lstat64() | lstat()をネイティブで実行しlstat64()のフォーマットに変換(for linux ELF) |
| 197 | fstat64() | fstat()をネイティブで実行しfstat64()のフォーマットに変換(for linux ELF) |
| 200 | isatty() | PowerPCnewlib時はisatty()をネイティブで実行、 LinuxPPC時は未実装システムコール |
| 202 | sbrk()|getdents64() | PowerPCnewlib時はヒープよりメモリ割り当てを行い、 LinuxPPC時はgetdents64フォーマットに合わせてシミュレータ内で データを加工して返します |
| 203 | pathconf() | PowerPCnewlib時はpathconf()をネイティブで実行、 LinuxPPC時は未実装システムコール |
| 204 | fcntl64() | fcntl()をネイティブで実行 |
| 205 | _opendir() | PowerPCnewlib時はフォーマットに合わせてシミュレータ内でデータを加工して 返します。LinuxPPC時は未実装システムコール |
| 206 | closedir() | PowerPCnewlib時はシミュレータ内でデータを処理し、 LinuxPPC時は未実装システムコール |
システムコールエミュレーション実行を行う場合は、標準出力やファイルが
使用できるため、プログラムの実行結果などはそれらに出力すれば良いでしょう。
しかし、OSレベルの開発に使用する場合にはエミュレーションを使用することは
できませんので、結果の出力や格納には本物のハードウェアと同様のデバイスを
シミュレートする必要があります。
当初はIOデバイスを実装する予定は無かったのですが、Linuxカーネルを実行する
という血迷った経緯を踏んだ事から、簡単なIOデバイスの実装を余儀されなく
なりました。
Ver0.96で実装されている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 〜
ポートアドレス FDC_PORT_TOP 0x800003f0 FDC_PORT_BOT 0x800003f7
ポートアドレス PIC_MASTER_PORT0 0x80000020 PIC_MASTER_PORT1 0x80000021 PIC_SLAVE_PORT0 0x800000a0 PIC_SLAVE_PORT1 0x800000a1
ポートアドレス SERIAL_DATA_PORT0 0x800003f8 SERIAL_SCR_PORT0 0x800003ff
現在の実装状態は以上のようになっています。この先、都合により仕様が変更されたり
追加、削除される項目なども出てくると思うのですが、そういうものだという事で
ご了承いただきたいと思いますm(_'_)m。
また、説明不足な点や間違いについては、随時加筆修正していく予定です。
2001/09/30 : 0.70ベース初版 2001/10/28 : 細かい言いまわしを修正。 2001/11/25 : 0.73ベースで書き換え。スタックポインタとargc,argvの項目を追加。 2004/09/18 : 0.96ベースで書き換え。