ロック・サービスの作成

プログラマーによっては、 スレッド・ライブラリーで提供されている標準ロック・サービス (mutex) は使用せず、 独自のハイレベル・ロック・サービスをインプリメントすることを希望します。

例えば、データベース・プロダクトが、 既に一組の内部定義サービスを使用している場合があります。 これらのロック・サービスを新しいシステムに合わせる方が、 このサービスを使用する全内部モジュールを調整するより容易であることがあります。

このため、AIX®は、より高レベルのロックサービスを構築するために使用できるアトミックロッキングサービスプリミティブを提供している。 マルチプロセッサー・セーフのサービス (標準 mutex サービスなど) を作成するには、プログラマーは、 compare_and_swap サブルーチンなどのアトミック操作サービスではなく、このセクションで説明するアトミック・ロック・サービスを使用する必要があります。

マルチプロセッサー・セーフ・ロック・サービス

ロック・サービスは、同時に使用される可能性のあるリソースへのアクセスを逐次化するために使用されます。 例えば、ロック・サービスは、 複数のポインター更新が必要なリンク・リストへの挿入に使用することができます。 1 つのプロセスによる更新シーケンスが同じリストにアクセスしようとする 2 番目のプロセスによって割り込まれた場合、 エラーが起こる可能性があります。 割り込みを受け付けられない操作のシーケンスを、 クリティカル・セクション と呼びます。

ロック・サービスは、ロック・ワードを使用してロックの状況を示します。 ここで、0 (ゼロ) は自由に使用できることを、そして 1 は使用中であることを示します。 そのため、ロック獲得のサービスは、次のようになります。

test the lock word
if the lock is free
        set the lock word to busy
        return SUCCESS
... 
この操作 (読み取り、テスト、セット) のシーケンス自体がクリティカル・セクションなので、 特殊な処理が必要になります。 ユニプロセッサー・システムでは、 クリティカル・セクション中に割り込みを使用不可にすると、 コンテキスト・スイッチによって割り込みを防ぎます。 しかし、マルチプロセッサー・システムでは、 通常、ハードウェアが特殊なマシン・インストラクションによって、 テスト・アンド・セット・プリミティブを提供する必要があります。 さらに、インポート/エクスポート・フェンス と呼ばれる特殊なプロセッサーに依存する同期命令を使用して、 他の読み取りと書き込みを一時的にブロックします。 これらの命令は、複数のプロセッサーによる同時アクセス、 および最新のプロセッサーによる読み取りと書き込みのリオーダーを防止し、次のように定義されます。
インポート・フェンス
インポート・フェンス は、既に実行されているすべての命令が完了するまで遅延する、特殊なマシン・インストラクションです。 ロックと連動させて使用すると、そのロックが取得されるまで、命令を推定した実行が防止されます。
エクスポート・フェンス
エクスポート・フェンス は、ロックが解除される前に、 保護されるデータが必ず他のすべてのプロセッサーから見えるようにします。

このように複雑なマシン固有の命令の代わりに、 次のサブルーチンが定義されています。

_check_lock
条件によって、シングル・ワードの変数を自動的に更新し、 同時にマルチプロセッサー・システム用のインポート・フェンス を発行します。 compare_and_swap サブルーチンはこれと似ていますが、 インポート・フェンスを発行しないので、ロックのインプリメントには使用できません。
_clear_lock
シングル・ワードの変数を自動的に書き込み、 同時にマルチプロセッサー・システム用のエクスポート・フェンス を発行します。

カーネル・プログラミング

カーネル・プログラミングの詳細については、「 カーネル・エクステンション機能とデバイス・サポート・プログラミングの概念」を参照してください。 この項では、マルチプロセッサー・システムで必要とされる機能の主な相違点に焦点を当てます。

ある種のクリティカル・リソースにアクセスするときに、 逐次化が必要になることがよくあります。 ロック・サービスを使用することによって、 プロセス環境においてスレッド・アクセスを逐次化することができますが、 これでは、割り込み環境で起こるアクセスから保護することはできません。 新しいコードや移植されたコードでは、カーネルサービスのdisable_lockunlock_enableを使用する必要があります。これらのカーネルサービスは、i_disableカーネルサービスの代わりに、割り込み制御に加えて単純なロックを使用します。 これらのカーネル・サービスは、 ロックのない割り込みサービスを使用するだけのユニプロセッサー・システムでも使用することができます。 詳しくは、「 Kernel Extensions and Device Support Programming Concepts」の「 Locking Kernel Services 」を参照してください。

デフォルトのデバイス・ドライバーは、論理的ユニプロセッサー環境で実行され、 この環境ではファネル・モードと呼ばれています。 ユニプロセッサー・システム用の適切なドライバーの大部分は、 このモードでもそのまま動きますが、注意深く調べて、 マルチプロセッシングによる効果が得られるように変更する必要があります。 最後に、タイマーのカーネル・サービスは、 マルチプロセッサー環境では常に成功するとは限らないので、戻り値を持っています。 したがって、新しいコードや移植されたコードの場合は、この戻り値を調べる必要があります。 詳しくは、「 Kernel Extensions and Device Support Programming Concepts」の「 Using Multiprocessor-Safe Timer Services 」を参照してください。

ロック・サービスの例

マルチプロセッサー・セーフのロック・サブルーチンを使用することによって、 スレッド・ライブラリーから独立したカスタム・ハイレベル・ルーチンを作成することができます。 以下の例は、スレッド・ライブラリー内の pthread_mutex_lock および pthread_mutex_unlock サブルーチンに類似したサブルーチンの部分的なインプリメンテーションを示しています。
#include <sys/atomic_op.h>       /* for locking primitives */
#define SUCCESS          0
#define FAILURE          -1
#define LOCK_FREE        0
#define LOCK_TAKEN       1

typdef struct {
        atomic_p         lock;   /* lock word */
        tid_t            owner;  /* identifies the lock owner */
        ...              /* implementation dependent fields */
} my_mutex_t;

...

int my_mutex_lock(my_mutex_t *mutex)
{
tid_t   self;   /* caller's identifier */
  
        /*
        Perform various checks:
          is mutex a valid pointer?
          has the mutex been initialized?
        */
        ...

        /* test that the caller does not have the mutex */
        self = thread_self();
        if (mutex->owner == self)
                return FAILURE;

        /*
        Perform a test-and-set primitive in a loop.
        In this implementation, yield the processor if failure.
        Other solutions include: spin (continuously check);
                 or yield after a fixed number of checks.
        */
        while (_check_lock(mutex->lock, LOCK_FREE, LOCK_TAKEN))
                yield();

        mutex->owner = self;
        return SUCCESS;
} /* end of my_mutex_lock */

int my_mutex_unlock(my_mutex_t *mutex)
{
        /*
        Perform various checks:
          is mutex a valid pointer?
          has the mutex been initialized?
        */
        ...

        /* test that the caller owns the mutex */
        if (mutex->owner != thread_self())
                return FAILURE;

        _clear_lock(mutex->lock, LOCK_FREE);
        return SUCCESS;
} /* end of my_mutex_unlock */