libc 解析 take.4 (libspe)-CELLプロセッサ
"PPE Serviced SPE C Library Functions" ...
"PPE Serviced SPE C Library Functions" の 4 回目は libspe の処理につ いて解説します。
4.2 PPE Serviced SPE C Library Functions
- The SPE constructs a local store image of the input and outout parameters.
- The SPE creates a 32-bit message consisting of an opcode and pointer to the local store parameter image array.
- The SPE executes a Stop and Signal instruction.
- The PPE detects the Stop and Signal.
- The PPE invoke a specialist to service the request according to the message opcode.
- The PPE services the requested function.
- The PPE returns results in the local store image array.
- The PPE resumes SPE execution.
- The SPE returns that results back to the caller.
この内、libspe で行うのは 5., 6., 7., 8. です。
1 回目に説明した通り、spu_run システムコールの返り値として SPE の stop 命令のオペランドが返ります。また、2, 3 回目に説明した通り、SPE の stop 命令の次のアドレスに OPCODE と 引数へのアドレスが格納されており、こ れらを以下のようにして処理していきます。
- lispe の spe_create_thread() API で生成された PPE thread の本体であ る do_spe_run() が spu_run システムコールを発行して SPE thread を実行し ています。SPE thread が stop 命令を発行すると、SPE thread が停止し、 spu_run システムコールから復帰。
- do_spe_run() は、返り値に含まれる stop 命令のオペランド (OPCLASS) を 見ることで、対応する libc のオペクラスハンドラ ( default_c99_handler() or default_posix1_handler() ) を実行。
- オペクラスハンドラは、stop 命令の次に書かれた OPCODE を見ることで対 応する libc のオペコードハンドラ (default_{c99,posix1}_handler_*) を実行。
- オペコードハンドラは libc 関数を実行し、実行結果 (返り値と errno) を SPE のメモリイメージに格納。
- do_spe_run() が spu_run システムコールを発行して SPE を再実行。
以下は do_spe_run() の関連コードを抜粋したものにコメントで解説を付け たものです。
int do_spe_run (void *ptr)
{
:
do {
// SPE thread を (再) 実行
ret = spe_run(runfd, &npc, &status);
// ret は SPU Status Register の値が入っている
// - 下位 16 bit [0:15] が状態を表す値
// - 上位 14 bit [16:29] が stop 命令のオペランド
// よって code は stop 命令のオペランドとなる
code = ret >> 16;
:
if (ret < 0) {
:
// code は libc のハンドラの場合は以下の値を取る
// C の関数の場合: SPE_C99_CLASS(0x2100)
// POSIX の関数の場合: SPE_POSIX1_CLASS(0x2101)
} else if ((code&0xff00)==0x2100) {
int callnum = code&0xff;
:
if (!handlers[callnum])
{
:
}
else
{
// 対象となるオペクラスハンドラを実行
// ちなみにオペクラスハンドラは 256 個作れますが、
// 2 つしか使ってません
int (*handler)(void *, unsigned int);
handler=handlers[callnum];
rc = handler(thread_store->mem_mmap_base, npc);
if (rc)
{
:
}
else
{
// npc がスタック上の bi 命令を指すように変更
npc += 4;
}
}
}
else if (code < 0x2000)
{
:
}
// 0x21?? の場合はエラーではなく、libc のハンドラ実行依頼
// なので SPE thread を再実行するためにループの先頭へ
} while (((code & 0xff00) == 0x2100 || ... );
}
お次は C99 のオペクラスハンドラ。
int default_c99_handler(unsigned long *base, unsigned long offset)
{
int op, opdata;
if (!base) {
DEBUG_PRINTF("%s: mmap LS required.\n", __func__);
return 1;
}
// stop 命令の次のアドレスの値から OPCODE を得る
offset = (offset & LS_ADDR_MASK) & ~0x1;
opdata = *((int *)((char *) base + offset));
op = SPE_C99_OP(opdata);
// 値のチェック
if ((op <= 0) || (op >= SPE_C99_NR_OPCODES)) {
DEBUG_PRINTF("%s: Unhandled type %08x\n", __func__,
SPE_C99_OP(opdata));
return 1;
}
// オペコードハンドラ実行
default_c99_funcs[op-1] ((char *) base, opdata);
return 0;
}
そして getc() に対応するオペコードハンドラである default_c99_handler_getc() です。
/**
* default_c99_handler_getc
* @ls: base pointer to SPE local-store area.
* @opdata: per C99 call opcode & data.
*
* SPE C99 library operation, per: ISO/IEC C Standard 9899:1999,
* implementing:
*
* int getc(FILE *stream);
*/
int default_c99_handler_getc(char *ls, unsigned long opdata)
{
// 引数へのポインタ arg0 を得る
// SPE は 128 bit 単位であるため、arg0 はこれを吸収するため
// の構造体 spe_reg128 へのポインタとなっている
DECL_1_ARGS();
// 返り値と errno を格納するポインタ (arg0 と同じ) を得る
DECL_RET();
FILE *stream;
int rc;
DEBUG_PRINTF("%s\n", __func__);
CHECK_C99_OPCODE(GETC);
// 引数から stream を得る
stream = get_FILE(arg0->slot[0]);
// getc() を実行
rc = getc(stream);
// 返り値 rc と errno を SPE のメモリイメージに格納
PUT_LS_RC(rc, 0, 0, errno);
return 0;
}
getc() 実行時の SPE thread スタックはこうなります。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain |
|------------|
| |
|------------|
| lnop |
| bi $2 |
| opdata | <---- ncp
| stop |
|------------|
| slot[3] | * slot[] は各 32 bit で有効なデータがない状態
| slot[2] | を表しています
| slot[1] |
| stream | <---- arg0, ret
`------------'
low memory
getc() 実行後に 返り値 rc と errno を格納し、do_spe_run() 内で npc を +4 した後の SPE thread スタックに以下のようになります。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain |
|------------|
| |
|------------|
| lnop |
| bi $2 | <---- ncp
| opdata |
| stop |
|------------|
| errno |
| slot[2] |
| slot[1] |
| rc | <---- arg0, ret
`------------'
low memory
可変引数関数の場合も同様ですが、以下に fprintf() 実行時 (PPE では vfprintf が実行されるため、対応するオペコードハンドラは default_c99_handler_vfprintf() となる) のスタックのみ示しておきます。
| Stack | high memory
| Parms |
| |
|------------|
| Link Reg |
|------------|
| Back Chain |<-----. <---- input SP
|------------| |
| Reg 79 | |
|------------| |
| Reg 78 | |
|------------| |
// ... // |
|------------| |
| Reg 6 | |
|------------| |
| Reg 5 |<--. |
|------------| | |
| slot[3] | | |
| slot[2] | | |
| slot[1] | | |
|caller_stack|------'
|------------| |
| slot[3] | |
| slot[2] | |
| slot[1] | |
| next_arg |---'
|------------|
| slot[3] |
| slot[2] |
| slot[1] |
| format | <---- arg1
|------------|
| slot[3] |
| slot[2] |
| slot[1] |
| stream | <---- arg0, ret
|============|
| |
|------------|
| |
|------------|
| lnop |
| bi $2 |
| opdata | <---- ncp
| stop |
`------------'
low memory
なお、現在は公開されてませんが、spe.c 内には
register_handler(void * handler, unsigned int callnum )
なる関数があり、ユーザー独自のハンドラを登録できるようにすることが検 討されていた形跡はあります。が、libspe の API としての公開はされていませ ん。


