« 最小二乗法の心(2) | トップページ | 自前のセマフォは案外たいへん(1) »

2018年6月23日 (土)

OSK でマルチスレッド

今とは違ってコンピューター(およびプログラミング)が趣味として成り立っていた頃、私は OS-9 を使っていました。その OS-9 でマルチスレッドをプログラムして遊んだことが有ります(当時、 OS-9 にはマルチスレッドの機能は有りませんでした)。

OS-9 は CPU によって幾つか種類が有ります。 6809 用は OSN、 680x0 用は OSK と呼ばれています。その他の CPU 用も有ります。 OSN には、複数のプロセスが同一空間で走る Level-1、 そして異なる空間で走る Level-2 が有ります。因みに、 6809 は 8 bit です。それに対し、 680x0 用 OSK には Level-1 しか有りません。 32 bit の 680x0 用は当然 Level-2 だろうと思っていたら、大間違いでした。

しかし、 Level-1 (空間共有型)であるが故に、面白い遊びが出来ました。他のプロセスのデータに強引にアクセスしてマルチスレッドと同様のことが出来たのです。

プログラムを1つ走らせたものがプロセスです。基本的にプログラムには大域変数の領域が1つ存在します。スレッドは一連の処理の流れを言います。システムによっては、1つのプロセスに複数のスレッドを走らせることが可能で、それら複数のスレッドは大域変数の領域を共有しています。

以下では OSK で、複数のプロセスが大域変数の領域を共有することで1つのプログラムに複数のスレッドが走っているかのような例を示します。


int main(int argc, char **argv)
{
    cha c;
    void *myglob = _glob_data;
    void *glob;
    error_code err;

    if ((c = argv[0][0]) == 'm'){    /* mt    (1-1) */
        err = make_thread();
    } else {                    /* (1-2) */
        if (argc < 2)
            return -1;
        if ((glob = (void*)strtoui(argv[1])) == NULL)    /* (1-3) */
            return -1;
        _asm("    movea.l -8(%a5),%a6");        /* glob    (1-4) */
        if (c == 'r')            /* reader */
             err = reader();
        else if (c == 'w')    /* writer */
             err = writer();
        else
            err = -1;
        _asm("    movea.l -12(%a5),%a6");        /* myglob    (1-5) */
    }

    return err;
}

このプログラム名は mt です。したがって、普通に起動された時は (1-1) の if は真となり make_thread() が実行されます。 make_thread() は、後で示しますが、 argv[0]"mt" 以外の文字列を入れて同じプログラム mt を実行します。したがって、そこで起動されたプロセスは (1-1) が偽となり else 内が実行されることになります。そのプロセスが else 内を実行することで、1つのプログラムの中に新しいスレッドが生成されたかのように見做すことが出来ます。


error_code make_thread(void)
{
    char buf[2][10];
    char *glob;
    int i, n_thread;
    char *par[3];
    char *env[2];
    unsigned short type_lang;
    process_id thread[2];
    process_id child;
    status_code status;
    error_code err;
    unsigned n;

    buf[0][8] = '\0';
    glob = uitostr((unsigned)_glob_data, buf[0]);    /* (2-1) */

    buf[1][8] = '\0';
    for (n_thread = 0; n_thread < 2; n_thread++){    /* (2-2) */
        par[0] = thread_name[n_thread];
        par[1] = glob;
        par[2] = NULL;
        env[0] = "dummy";    /* これが NULL だと不具合発生 */
        env[1] = NULL;
        type_lang = mktypelang(MT_PROGRAM, ML_OBJECT);
        if (err = _os_exec(_os_fork, 0, 3, "mt", par, env, 0, &thread[n_thread], type_lang, 0)){
            n = 25;
            _os_writeln(2, "Error: can't exec thread\n", &n);
        }
    }

    for (i = 0; i < n_thread; i++){
        _os_wait(&child, &status);
    }

    return 0;
}

(2-1) の _glob_data は大域変数のベースアドレスです。それを文字列にして glob に代入しています。その文字列は par[1] に代入され、新しいプロセス(すなわちスレッド)を起動する時に渡されます。

(2-2) の for ループの中で新しいプロセス(すなわちスレッド)を起動するためのパラメータを準備し、そして起動しています。因みに、例では2つのスレッドを起動しています。 thread_name[] にはスレッドの名前が入っている前提で書いています。普通にプログラムを実行した場合、 par[0] に相当する部分にはプログラムの名前(今回の場合 mt)が入ります。したがって、この部分をチェックすることで、普通に起動されたのかスレッドとして起動されたのかが判断できます。

説明を最初の方のコードに戻します。 (1-2) はプログラム中で新たに起動されたスレッドが実行します。 (1-3) で、スレッド起動時に渡されるアドレスを glob に代入しています。 (1-4) はインライン・アセンブラです。レジスタ %a5 には局所変数のベースアドレスが入っており、 -8(%a5)glob と同じものです。また、 %a6 は大域変数のベースアドレスとして使用されます。結局、 (1-4) はスレッド起動時に渡されたアドレスを %a6 に代入することで、そのアドレスを大域変数のベースアドレスとして使用します。これがスレッド間で共有されることになります。

例では、スレッドは reader() もしくは writer() を実行しています。

(1-5) はスレッドを終了する時に実行します。 -12(%a5)myglob と同じで、これは関数の先頭で大域変数のベースアドレス _glob_data に初期化されています。したがって (1-5) はスレッド間で共有していた大域変数のベースアドレスを、スレッド(すなわちプロセス)本来の大域変数のベースアドレスに戻していることになります。

この方式で生成したスレッドは、プリエンプティブになっています。実質は OSK のプロセスなのだから当然です。また myglob はスレッドの元々の大域変数のベースアドレスになっているので、これをスレッド専用領域(スレッド・ローカル・ストレージ)として使用可能です。

以上が、かつて OSK で遊んだ「無理矢理マルチスレッド」です。

« 最小二乗法の心(2) | トップページ | 自前のセマフォは案外たいへん(1) »

プログラミング」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/584699/66862093

この記事へのトラックバック一覧です: OSK でマルチスレッド:

« 最小二乗法の心(2) | トップページ | 自前のセマフォは案外たいへん(1) »