CELLプロセッサ--libc 解析 take.4 (libspe)

HOME > 社内活動 > CELLプロセッサ > cell > libc > libc 解析 take.4 (libspe)

libc 解析 take.4 (libspe)-CELLプロセッサ

topics

"PPE Serviced SPE C Library Functions" ...

image

"PPE Serviced SPE C Library Functions" の 4 回目は libspe の処理につ いて解説します。

4.2 PPE Serviced SPE C Library Functions

  1. The SPE constructs a local store image of the input and outout parameters.
  2. The SPE creates a 32-bit message consisting of an opcode and pointer to the local store parameter image array.
  3. The SPE executes a Stop and Signal instruction.
  4. The PPE detects the Stop and Signal.
  5. The PPE invoke a specialist to service the request according to the message opcode.
  6. The PPE services the requested function.
  7. The PPE returns results in the local store image array.
  8. The PPE resumes SPE execution.
  9. 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 としての公開はされていませ ん。

トラックバック

トラックバックURL:

コメントを投稿


※上に表示されている番号を入力してください



HOME |  企業情報 |  事業内容 |  採用情報 |  社内活動 |  お問合せ |  サイトマップ
.
Copyright(c)2006 Sijam.Inc all rights Reserved
システム開発 株式会社シジャム