« 自前のセマフォは案外たいへん(1) | トップページ | 自前のセマフォは案外たいへん(3) »

2018年7月14日 (土)

自前のセマフォは案外たいへん(2)

セマフォ自作の実況第2段です。第1段

セマフォの獲得は mtx_lock() で行います。

int mtx_lock(queue_t *que)
{
    int err;

    spinlock(&(que->lock));        /* (3-1) */
    if (que->id[que->first] == 0){        /* (3-2) */
        err = mtx_append(que, DMYID);
        spinunlock(&(que->lock));
    } else {
        process_id id;
        u_int16      priority, group, user;

        _os9_id(&id, &priority, &group, &user);
        err = mtx_append(que, id);        /* (3-3) */
        if (err < 0){
            spinunlock(&(que->lock));
        } eles {
            u_int32 tick = 0;
            _os_sigmask(1);            /* (3-4) */
            spinunlock(&(que->lock));        /* (3-5) */
            _os9_sleep(&tick);        /* (3-6) */
            if (mysig == que->signal){
                mysig = 0;
            } else {
                err = -1;
            }
        }
    }
    return err;
} /* end of mtx_lock() */

que->id[que->first] は現在セマフォを獲得しているスレッド(実際はプロセス)のプロセス ID です。これが 0 の時は、どのスレッドも当該セマフォを獲得していないことを意味します。従って (3-2) が成り立つ時はセマフォをすぐに獲得出来ます。 mtx_append() はセマフォの待ち行列に自身を登録する関数で、第2引数はプロセス ID です。セマフォを即刻に獲得できる場合はプロセス ID としてダミーの DMYID を渡します。 DMYID は、ユーザープロセスとしては存在しない番号 1 に設定しておきます。

セマフォをすぐには獲得できない時はプロセス ID を待ち行列に挿入します(3-3)。 mtx_append() の戻り値が負(エラー)なら、すぐにスピンロックを解除してリターンします。この場合のエラーは、 que が満席で待ち行列に入れないことを意味しています。 mtx_append() が成功したら、 _os9_sleep() して順番を待ちます(3-6)。ただし、その前にやることが2つ。1つは、スピンロックの解除です(3-5)。そしてその前にシグナルのマスクです。もし、スピンロックの解除をしてからスリープするまでの間にセマフォの順番が回って来たことを示すシグナルが来た場合、(スリープする必要が無くなったにもかかわらずスリープして)セマフォを獲得し損ねることになります。それを防ぐためにスピンロックを解除する前にシグナルをマスクしておきます(3-4)。

que の操作は排他制御されなければなりません(3-1)。その排他制御は spinlock() で行います。

void spinlock(char *lock)
{
    _asm(
    "    movea.l   %a0,-(%a7)\n"
    "    movea.l   %d0,%a0\n"        /* (4-1) */
    "spinlock1\n"
    "    tas         (%a0)\n"        /* (4-2) */
    "    beq        spinlock2\n"
    "    move.l    #1,%d0\n"
    "    OS9       F$Sleep\n"        /* (4-3) */
    "    bra         spinlock1\n"
    "spinlock2\n"
    "    movea.l   (%a7)+,%a0\n"
    );
} /* end of spinlock() */

spinlock() は CPU の命令 tas (Test And Set) を使うためアセンブラで実装します。 spinlock() にはパラメーターとして lock のアドレスがレジスタ %d0 で渡されるので、それをレジスタ %a0 に代入(4-1)して、そのアドレスで指されている箇所をテストしています(4-2)。 tas はメモリの読み込みと書き込みをクリティカルに実行し、最上位ビット(ビット7)を1にセットします。もし、読み込んだ結果が0になっていたらスピンロック獲得成功です。0でないならば、それは既に他のスレッドがスピンロックを獲得していることを意味するので、スレッドのタイムスライスを放棄するために一時的なスリープをします(4-3)。目覚めた後は、再度トライします。このスピンロックは滅多に競合しないという前提で実装しています。

スピンロックの解除は下記の通り簡単です。

#define	spinunlock(p)	*(char*)(p) = 0

mtx_lock() に話を戻します。眠っていて、セマフォ獲得の順番が回って来たら _os9_sleep() から目覚めます。その場合は mysigque->signal になっています。そうなっていない場合、何か他の要因で眠りから覚めたので、それに合わせた処理をしなければなりません。今回は単にエラーコード -1 を返すだけにしています。

- 以下続く -

« 自前のセマフォは案外たいへん(1) | トップページ | 自前のセマフォは案外たいへん(3) »

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

コメント

コメントを書く

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

トラックバック

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

この記事へのトラックバック一覧です: 自前のセマフォは案外たいへん(2):

« 自前のセマフォは案外たいへん(1) | トップページ | 自前のセマフォは案外たいへん(3) »