9    分散ロック・マネージャ

この章では,分散ロック・マネージャ (DLM) を使用して,クラスタ内の共用リソースへのアクセスの同期をとる方法について説明します。クラスタ・ファイル・システム (CFS) は,POSIX に完全に準拠しているので,アプリケーションでは flock システム・コールを使って,複数のインスタンスによる共用ファイルへのアクセスの同期をとることができます。また,アプリケーションではこの目的に,クラスタ DLM が提供する関数を使うこともできます。

DLM には,これまでの POSIX のファイル・ロックにはない機能があります。次のような機能です。

注意

DLM のアプリケーション・プログラミング・インタフェース (API) ライブラリは,TruCluster Server のクラスタ・ソフトウェアとして提供され,TruCluster Server のベース・オペレーティング・システムには添付されていません。このため,スタンドアロン・システムとクラスタの両方で動作することを意図し,DLM 関数を呼び出すコードでは,clu_is_member を使ってアプリケーションがクラスタ内で動作しているかどうかを調べる必要があります。

この章では,基本的なDLM 操作の例についても記載しています。/usr/examples/cluster/ ディレクトリにこれらのコーディング例があります。

この章では,次の項目について説明しています。

9.1    DLM の概要

分散ロック・マネージャ (DLM) が提供する関数を使うと,1 つのクラスタ内で連携動作するプロセスによる,raw ディスク・デバイス,ファイル,プログラムなどの共用リソースへのアクセスの同期をとることができます。DLM で,共用リソースへのアクセスの同期を効果的にとるためには,クラスタ内のプロセスが特定のリソースを共用する場合は必ず,DLM の関数を使ってリソースへのアクセスを制御しなければなりません。

DLM の関数では,次のことができます。

表 9-1 は,DLM が提供する関数を表にしたものです。 アプリケーションでは,libdlm ライブラリからこれらの関数を利用することができます。

表 9-1:  分散ロック・マネージャの関数

関数 説明
dlm_cancel ロック変換要求を取り消す。
dlm_cvt 現在のロックを,同期をとって新しいモードに変換する。
dlm_detach すべてのネームスペースからプロセスをデタッチする。
dlm_get_lkinfo 指定したプロセスに関連するロック要求の情報を取得する。
dlm_get_rsbinfo DLM が管理しているリソースのロック情報を取得する。
dlm_glc_attach 既存のプロセス・ロック・グループ・コンテナにプロセスをアタッチする。
dlm_glc_create グループ・ロックのコンテナを作成する。
dlm_glc_destroy グループ・ロックのコンテナを削除する。
dlm_glc_detach プロセス・ロック・グループからデタッチする。
dlm_lock 指定したリソースのロックを要求する (同期式)。
dlm_locktp グループ・ロックまたはトランザクション ID を使って,指定したリソースのロックを要求する (同期式)。
dlm_notify 処理待ちの完了通知やブロック通知の配信を要求する。
dlm_nsjoin 指定したネームスペースにプロセスを接続する。
dlm_nsleave 指定したネームスペースからプロセスを切断する。
dlm_perrno 指定された DLM メッセージ ID に対応するメッセージを出力する。
dlm_perror 指定された DLM メッセージ ID に対応するメッセージと,呼び出し側で指定したメッセージ文字列を出力する。
dlm_quecvt 既存のロックを,非同期に新しいモードに変換する。
dlm_quelock 指定したリソースへのロックを要求する (非同期)。
dlm_quelocktp グループ・ロックまたはトランザクション ID を使って,指定したリソースのロックを要求する (非同期)。
dlm_rd_attach プロセスまたはプロセス・ロック・グループを回復ドメインにアタッチする。
dlm_rd_collect 指定した回復ドメインのリソースのロックで,不正なロック値ブロックを持つものを収集し,そのドメインの回復手順を開始する。
dlm_rd_detach プロセスまたはプロセス・ロック・グループを回復ドメインからデタッチする。
dlm_rd_validate 指定した回復ドメインで収集したリソースを有効にし,回復手順を終了する。
dlm_set_signal 完了通知およびブロック通知で使用するシグナルを指定する。
dlm_sperrno 指定された DLM メッセージ ID に対応する文字列を取得し,変数に格納する。
dlm_unlock ロックを解放する。

DLM 自体は,リソースへのアクセスが正しく行われているかどうかを保証しません。リソースにアクセスしているプロセス間の合意によって,リソースへのアクセスが許可されます。この動作は,ロック・マネージャの使用規則に従って,DLM の関数を使用することで実現できます。この規則は次のようになっています。

ロックはプロセスが所有するので,DLM を使用するアプリケーションでは次の点を考慮しなければなりません。

9.2    リソース

クラスタ内のすべてのものが,リソースとなり得ます。たとえば,ファイル,データ構造,raw ディスク・デバイス,データベース,実行可能プログラムなどがリソースになります。複数のプロセスが同じリソースに同時にアクセスする場合,これらのアクセスの同期をとらなければ,正しい結果が得られません。

ロック管理の関数を使うと,プロセスは,名前あるいはバイナリ・データとリソースを関連付けて,そのリソースへのアクセスの同期をとることができます。同期をとらなければ,あるプロセスが新しいデータを書き込んでいる間に,別のプロセスがそのリソースを読み取った場合,読み取ったデータが,書き込みによってまったく違ったものになっている可能性があります。

DLM 側から見るとリソースは,プロセス (あるいは DLM のプロセス・グループの代表プロセス) から,そのリソースの名前に対して最初にロックが要求されたときに作成されます。このとき DLM は,リソースのロック・キューやロック値ブロックなどを含む,構造体を作成します。

リソースをロックしているプロセスが 1 つでもある間は,そのリソースは存在し続けます。リソースへの最後のロックが解除されると,DLM はリソースを削除することができます。通常は,dlm_unlock 関数を呼び出すことでロックを解除しますが,プロセスが突然終了したときには,通常の手続きによらずに,ロック,そしてリソースが解放されることがあります。

9.2.1    リソースの細分性

多くのリソースはさらに細かい部品に分割することができますリソースの部品は,リソース名で識別できる限りロックすることができます。

図 9-1 は,データベースのモデルです。このデータベースはボリュームに分割され,各ボリュームはさらにファイルに分割されています。ファイルはさらにレコードに分割され,レコードはさらに項目に分割されています。

図 9-1 に示したデータベースにロック要求を出すプロセスは,データベース全体をロックすることも,個々のボリューム,ファイル,レコード,あるいは項目単位でロックすることもできます。データベース全体をロックすることを,粗い細分性でのロックといいます。単体の項目をロックすることを,細かい細分性でのロックといいます。

親ロックとサブロックとは,プロセスがさまざまな細分性のレベルでロックをかけることができるように,DLM で採用している方式のことです。親ロックとサブロックについての詳細は,9.6.5 項を参照してください。

図 9-1:  データベースのモデル

9.2.2    ネームスペース

ネームスペースは,リソース名のコンテナと考えることができます。セキュリティ上の理由,あるいはモジュール性を持たせる目的で,関連しないアプリケーションを分離するために複数のネームスペースが存在します。

ネームスペースは,実効ユーザ ID または実効グループ ID で修飾する場合と,パブリック・ネームスペースにして,ユーザ ID およびグループ ID に関係なく,すべてのプロセスがアクセスできるようにする場合があります。

ユーザ ID ベースのネームスペースには,そのユーザ ID を持つプロセスだけがアクセスできます。グループ ID ベースのネームスペースには,そのグループのメンバだけがアクセスできます。パブリック・ネームスペースの場合は,すべてのプロセスがそのネームスペースに加わり,ロック操作を行うことができます。

ユーザ ID またはグループID で修飾されたネームスペースの場合,セキュリティは,このネームスペースへのアクセス権を,プロセスが持つ実効ユーザ ID または実効グループ ID に基づいて制限することで保護されます。したがって,ユーザ ID およびグループ ID のネームスペースは,クラスタ全体で一貫性がなければなりません。そのネームスペースへのアクセスがプロセスに許可されれば,このネームスペース内では,個々のロック・オペレーションは制限されません。

アプリケーションが,パブリック・ネームスペースを使用する場合は,セキュリティやアクセス・メカニズムは,アプリケーション側で用意しなければなりません。パブリック・ネームスペース内でロックへのアクセスを制御し保護する方法としては,ロックを保持しているプロセスが,保護読み取り (PR) モード以上のモードを維持し,すべてのブロック通知に応答しないようにするものがあります。こうすると,ロックを保持しているプロセスが実行している間は,他のプロセスがロック値ブロックを変更することはできません。

連携して動作するプロセスは,特定のリソースへのロックを調整できるように,同じネームスペースを使用しなければなりません。プロセスが,dlm_lock 関数,dlm_locktp 関数,dlm_quelock 関数,または dlm_quelocktp 関数を呼び出してリソースのロックを取得するためには,必要なリソースがあるネームスペースに加わらなければなりません。プロセスが,ユーザ ID またはグループ ID で修飾されたネームスペースに対して dlm_nsjoin 関数を呼び出すと,DLM はそのプロセスのグループ ID またはユーザ ID によって,そのプロセスがそのネームスペースへのアクセスを許可されていることを確かめます。プロセスがこのチェックにパスすると,DLM は,ネームスペースへのハンドルを返します。プロセスが,パブリック・ネームスペースに対して dlm_nsjoin 関数を呼び出した場合は,DLM はすぐにそのネームスペースへのハンドルを返します。

プロセスは,次に DLM 関数を呼び出してルート・ロックを取得するときに,このハンドルを指定しなければなりません。ルート・ロックとは,ネームスペース内の特定のリソースをロックするための,元となる親ロックのことです。ルート・ロックの下位には,ネームスペースへのアクセス権のチェックを受けることなく,サブロックを追加することができます。

プロセスは,最大 DLM_NSPROCMAX 個のネームスペースのメンバになることができます。

9.2.3    リソースの識別

DLM は以下のパラメータで,リソースを識別します。

たとえば,次の 2 つのパラメータ・セットは同じリソースを表します。

パラメータ nsp resnam resnlen parid
リソース 1 14 disk1 5 80
リソース 1 14 disk1 5 80

次の 2 つのパラメータ・セットも同じリソースを表します。

パラメータ nsp resnam resnlen parid
リソース 1 14 disk1 5 40
リソース 1 14 disk12345 5 40

次の 2 つのパラメータ・セットは異なるリソースを表します。

パラメータ nsp resnam resnlen parid
リソース 1 0 disk1 5 80
リソース 2 0 disk1 5 40

9.3    ロックの使用

分散ロック・マネージャ (DLM) の関数を使うには,プロセスは dlm_lock 関数,dlm_locktp 関数,dlm_quelock 関数,または dlm_quelocktp 関数を使って,リソースへのアクセス (つまりロック) を要求しなければなりません。この要求では,以下のようなパラメータを指定します。

ヌル・モードのロック (9.3.1 項を参照) は,他のすべてのロック・モードと共存でき,いつでもすぐに許可されます。

次の場合は,新規のロックはすぐに許可されます。

次の場合は,新規のロックは許可されません。

プロセスは,dlm_cvt 関数および dlm_quecvt 関数を使ってロック・モードを変更することもできます。これをロック変換と呼びます。詳細は,9.3.4 項を参照してください。

9.3.1    ロック・モード

他のロック要求とリソースを共用できるかどうかは,ロックのモードで決まります。表 9-2 では,6 つのロック・モードについて説明します。

表 9-2:  ロック・モード

モード 説明
ヌル (DLM_NLMODE) リソースへのアクセスは許可しない。ヌル・モードは,後でロック変換を行うための準備として使用する。またはリソースのロックがすべて解放されても,そのリソースとリソースのコンテキストが保持されるようにするために使用する。
同時読み取り (DLM_CRMODE) リソースへの読み取りアクセスを許可し,他の読み取りや書き込みプロセスとのリソースの共用を許す。引き続き,より細かい細分性でサブロックを実行する場合や,保護していない状態で,つまり書き込みを許したままリソースからデータを読み取る場合には,通常,同時読み取りモードが使われる。
同時書き込み (DLM_CWMODE) リソースへの書き込みアクセスを許可し,他の書き込みプロセスとのリソースの共用を許す。引き続き,より細かい細分性でロックを実行する場合や,保護していない状態でリソースに書き込む場合には,通常,同時書き込みモードが使われる。
保護読み取り (DLM_PRMODE) リソースへの読み取りアクセスを許可し,他の読み取りプロセスとのリソースの共用を許す。リソースへの書き込みアクセスは許可しない。一般的にいう,共用ロック。
保護書き込み (DLM_PWMODE) リソースへの書き込みアクセスを許可し,同時読み取りモードの読み取りプロセスとのリソースの共用を許す。他の書き込みプロセスによる書き込みアクセスは許さない。一般的にいう,更新ロック。
排他 (DLM_EXMODE) リソースへの書き込みアクセスを許可し,他の読み取りプロセスや書き込みプロセスとのリソースの共用は許さない。一般的にいう,排他ロック。

9.3.2    ロックのレベルと共存性

複数のプロセスでリソースを共用できるロックを低レベルのロックと呼び,プロセスがほぼ排他的にリソースにアクセスできるロックを高レベルのロックと呼びます。ヌル・モードおよび同時読み取りモードのロックは低レベルのロックです。保護書き込みモードおよび排他モードのロックは高レベルのロックです。アクセス・モードをレベルの低い順に並べると,次のようになります。

  1. ヌル (NL)

  2. 同時読み取り (CR)

  3. 同時書き込み (CW) および 保護読み取り (PR)

  4. 保護書き込み (PW)

  5. 排他 (EX)

同時書き込み (CW) と保護読み取り (PR) モードは,同じレベルと見なされます。

リソースに対して許可されている他のロックと共用できるロック (つまり,リソースのグループ許可モード) は,共存できるロック・モードを持っているといわれます。高レベルのロック・モードは,低レベルのロック・モードに比べ,他のロック・モードとの共存性が低くなります。

表 9-3 では,ロック・モードの共存性を示します。

表 9-3:  ロック・モードの共存性

要求されたロックのモード リソースのグループ許可モード
  NL CR CW PR PW EX
ヌル (NL) 可能 可能 可能 可能 可能 可能
同時読み取り (CR) 可能 可能 可能 可能 可能 不可能
同時書き込み (CW) 可能 可能 可能 不可能 不可能 不可能
保護読み取り (PR) 可能 可能 不可能 可能 不可能 不可能
保護書き込み (PW) 可能 可能 不可能 不可能 不可能 不可能
排他 (EX) 可能 不可能 不可能 不可能 不可能 不可能

9.3.3    ロック管理のキュー

リソースに対するロックは,次の 3 種類の状態のいずれかになります。

図 9-2 で示すように,3 種類のそれぞれの状態に対応したキューがあります。

図 9-2:  3 つのロック・キュー

既存のリソースに対して,新規にロックを要求すると,DLM は次のようにして,変換待ちキューまたは待ちキューで待っているロックが他にあるかどうかを確認します。

9.3.4    ロック変換

ロック変換を行うことにより,プロセスはロックのモードを変更することができます。たとえば,プロセスはリソースに対して低レベルのロックを保持しておいて,リソースへのアクセスを制限しようと決めた段階で,ロック変換を行うことができます。

ロック変換を行うには,変換したい,許可されているロックのロック ID を指定して,dlm_cvt 関数,または dlm_quecvt 関数を呼び出します。要求したロック・モードが現在許可されているロックと共存できる場合,変換要求はすぐに許可されます。要求したロック・モードが,許可キューにある既存のロックと共存できない場合,要求は変換待ちキューの最後に置かれます。変換要求が許可されるまでは,ロックは現在許可されているモードのままです。

DLM は,ある変換要求を許可すると,変換待ちキューに並んでいる順に共存可能な要求を許可していきます。DLM は,変換待ちキューが空になるまで,または共存できないロックに出会うまで,順番に要求を許可していきます。

変換待ちキューが空の場合,DLM は待ちキューを調べます。待ちキューの先頭のロック要求が,現在許可されているロックと共存できれば,要求が許可されます。DLM は,待ちキューが空になるまで,または共存できないロックに出会うまで,順番に要求を許可していきます。

9.3.5    デッドロックの検出

DLM は次の 2 種類の形態のデッドロックを検出することができます。

図 9-4:  複数リソース・デッドロック

DLM は,変換デッドロック,または複数リソース・デッドロックが発生したことを検出すると,犠牲にするロックを選び,これを使ってデッドロックを解消します。犠牲にするロックは任意に選択されますが,選ばれたロックは,変換待ちキュー,または待ちキューのどちらかに置かれることが保証されます (つまり,許可キューには置かれません)。

DLM は,dlm_lock 関数,dlm_locktp 関数,dlm_cvt 関数のいずれかを呼び出し,犠牲にされたプロセスに,最終的な終了状態コードとして DLM_DEADLOCK を返します。あるいは,dlm_quelock 関数,dlm_quelocktp 関数,dlm_quecvt 関数のいずれかを呼び出す際に指定された完了ルーチンへのパラメータ completion_status として,この状態を渡します。許可されたロックが取り消されることはありません。変換待ちキューおよび待ちキューのロック要求だけが,DLM_DEADLOCK 状態コードを受け取る可能性があります。

注意

DLM がデッドロックを解消する際に,どのロックを選択するかを想定しないでください。また,セマフォやファイル・ロックのような他のサービスを DLM と一緒に使用した場合は,検出できないデッドロックが発生する可能性があります。DLM で検出できるデッドロックは,DLM のロックに関連して発生したデッドロックのみです。

9.4    ロックのキューからの削除

プロセスは,リソースをロックする必要がなくなった場合,dlm_unlock関数を呼び出して,ロックを解放することができます。

ロックが解放されると,そのロック要求は,どのキューにあっても削除されます。ロックは,許可キュー,待ちキュー,変換待ちキューのいずれのキューからも外されます。あるリソースの最後のロックがキューから外されると,そのリソースは DLM のデータベースから削除されます。

dlm_unlock 関数では,valb_p パラメータと DLM_VALB フラグを指定することで,リソースのロック値ブロックへ書き込んだり,内容を無効にすることができます。キューから外されるロックに PW モードまたは EX モードが許可されている場合,プロセスの値ブロックの内容が,リソースの値ブロックに格納されます。キューから外されるロックが上記以外のモードの場合,ロック値ブロックは使用されません。DLM_INVVALBLK フラグを指定した場合は,リソースのロック値ブロックに,無効であることを示す印が付けられます。

dlm_unlock 関数では以下のフラグを使用できます。

DLM_VALB フラグと DLM_INVVALBLK フラグの両方を,同じ要求で指定することはできません。

9.5    変換要求の取り消し

dlm_cancel は,ロック変換を取り消す関数です。プロセスは,ロック要求がまだ許可されていない場合だけ,つまり,ロック要求が変換待ちキューにある場合だけ,ロック変換を取り消すことができます。取り消しを実行すると,変換待ちキュー内のロックは,変換要求を出す前に許可されていたモードに戻ります。ロックの値 blkrtn および notprm も元の値に戻ります。DLM は,変換要求で指定されている完了ルーチンを呼び出して,要求が取り消されたことを伝えます。返される状態コードは,DLM_CANCEL です。

9.6    高度なロック技術

ここまでの節では,すべてのアプリケーションに役立つロック技術および概念について説明してきました。これ以降の各項では,分散ロック・マネージャ (DLM) のより特化された機能について説明します。

9.6.1    ロック要求の非同期完了

dlm_lock 関数,dlm_locktp 関数,および dlm_cvt 関数は,ロック要求が許可されるか失敗したときに完了し,結果を示す状態コードが返されます。

ロック要求が完了するまでアプリケーションを待たせたくない場合は,dlm_quelock 関数,dlm_quelocktp 関数,および dlm_quecvt 関数を使うようにしてください。これらの関数は,ロック要求をキューに入れた後に,呼び出し元のプログラムに制御を返します。これらの関数が返す状態値は,要求が正常にキューに入れられたか,または拒否されたかを知らせるものです。要求がキューに入れられた後,呼び出し元のプログラムは,要求が許可されるまではリソースにアクセスすることはできません。

dlm_quelock 関数,dlm_quelocktp 関数,および dlm_quecvt 関数の呼び出しでは,完了ルーチンのアドレスを指定しなければなりません。ロック要求に成功した場合,または失敗した場合に,完了ルーチンが実行されます。DLM は,ロック要求が成功したか,または失敗したかを示す状態情報を完了ルーチンに渡します。

注意

DLM からの完了通知を受けたいアプリケーションでは,この通知が必要な最初のロック要求を出す前に一度,dlm_set_signal 関数を呼び出さなければなりません。これ以外に,アプリケーションが dlm_notify 関数を定期的に呼び出す方法もあります。dlm_notify 関数を使うと,プロセスは,保留になっている通知をポーリングし,dlm_set_signal 関数を呼び出さなくても,完了通知の配信を要求することができます。ただし,このポーリングによる方法は,お勧めしません。

9.6.2    同期完了の通知

DLM には,ロック要求がその場で許可されたかどうかをプロセスが確認する機能があります。つまり,そのロックが変換待ちキュー,または待ちキューに入れられなかったかどうかを確認できます。この機能を使うと,シグナルの配信と,その結果の完了ルーチンの実行によるオーバヘッドが回避されるため,ほとんどのロックがその場で許可される (これが一般的) アプリケーションでは,性能を改善することができます。アプリケーションでは,要求を処理する際にこの機能を使って,衝突するロックがないかテストすることもできます。

この機能のメカニズムは,次のようになっています。

9.6.3    ブロック通知

DLM 関数を使用するアプリケーションの中には,他のプロセスがリソースをロックするのを防げているかどうかを,プロセスが意識しなければならないものがあります。DLM では,ブロック通知を使って,プロセスにこのことを知らせます。ブロック通知を使うには,ロック要求の blkrtn パラメータにブロック通知ルーチンのアドレスを指定しなければなりません。これにより,このロックのために,別のロックの許可が防げられたときに,ブロック通知が配信され,ブロック通知ルーチンが実行されます。

DLM は,ブロック通知ルーチンに以下のパラメータを渡します。

notprm

ブロックされたロックのコンテキスト・パラメータ。このパラメータは,ブロックされたロックのロック要求で,dlm_lock 関数,dlm_locktp 関数,dlm_quelock 関数,dlm_quelocktp 関数,dlm_cvt 関数,dlm_quecvt 関数のいずれかの関数の呼び出し元プロセスが指定したものです。

blocked_hint

最初にブロックされたロックの hint パラメータ。このパラメータは,最初にブロックされたロックのロック要求で,dlm_lock 関数,dlm_locktp 関数,dlm_quelock 関数,dlm_quelocktp 関数,dlm_cvt 関数,dlm_quecvt 関数のいずれかの関数の呼び出し元プロセスが指定したものです。

lkid_p

ブロックされたロックのロック ID へのポインタ。

blocked_mode

最初にブロックされたロックで要求されたモード。

通知が配信されるまでに,次のような状況になっている可能性があります。

こうした状況が発生する可能性があるため,DLM では,ブロック・ルーチンの実行時の,blocked_hint パラメータ,および blocked_mode パラメータの有効性を保証することはできません。

注意

DLM からブロック通知の配信を受けたいアプリケーションでは,ブロック通知を必要とする最初のロック要求を発行する前に,dlm_set_signal 関数を (1 度) 呼び出さなければなりません。

また,dlm_set_signal の呼び出し時に指定したシグナルがブロックされた場合,このシグナルのブロックが解除されるまで,ブロック通知は配信されません。これ以外に,dlm_notify 関数を定期的に呼び出す方法があります。dlm_notify 関数を使うと,プロセスは,保留になっている通知をポールし,これらの通知を配信するように要求することができます。ただし,このポーリングによる方法は,お勧めしません。

9.6.4    ロック変換

ロック変換には,次のような機能があります。

9.6.4.1    ロック変換のキューイング

ロック変換を行う場合,dlm_cvt 関数または dlm_quecvt 関数を呼び出します。変換するロックは,lkid_p パラメータで指定します。ロック変換を要求するときには,そのロックは既に許可されていなければなりません。

9.6.4.2    変換待ちキューへの強制的配置

特定のリソースへのアクセスがより公平に行われるようにするために,許可できる変換要求を,強制的に変換待ちキューに入れることができます。DLM_QUECVT フラグを設定して変換要求を行うと,その要求は,既にキューに入っている変換要求の後ろに強制的に置かれます。このように,DLM_QUECVT フラグを指定して,他のロックが許可されるチャンスをつくることができます。ただし,キューに入っている変換要求がない場合は,その変換要求はすぐに許可されます。

DLM_QUECVT は,可能な変換要求のサブセットに対してだけ有効です。表 9-5 は,DLM_QUECVT フラグを指定した場合に許される,変換要求の組み合わせを示しています。無効な変換要求は,DLM_BADPARAM 状態が返されて,失敗します。

表 9-5:  DLM_QUECVT フラグを指定した場合に許される変換

取得しているロックのモード 変換後のロックのモード
  NL CR CW PR PW EX
ヌル (NL) --
同時読み取り (CR) -- --
同時書き込み (CW) -- -- -- --
保護読み取り (PR) -- -- -- --
保護書き込み (PW) -- -- -- -- -- --
排他 (EX) -- -- -- -- -- --

9.6.5    親ロック

プロセスが dlm_lock 関数,dlm_locktp 関数,dlm_quelock 関数,dlm_quelocktp 関数のいずれかを呼び出してロック要求を出す際に,parid パラメータに親ロック ID を指定すると,新規のロックに対して親ロックを宣言することができます。親ロックを持つロックを,サブロックと呼びます。親ロックが許可された後でなければ,その親に属するサブロックは許可されません。モードは,親と同じモードでも,別のモードでもかまいません。

親ロックとサブロックを使うメリットは,細かい細分性のリソースに,高レベルのサブロック (保護書き込みモード,または排他モード) をかけ,一方で粗い細分性のリソースに,低レベルのロック (同時読み取りモード,または同時書き込みモード) をかけられることです。たとえば,低レベルのロックで,あるファイル全体へのアクセスを制御し,それと同時に,それより上位のレベルで,そのファイル内の個々のレコードやデータ項目にサブロックをかけることができます。

データベースへのアクセスが必要なプロセスがたくさんある場合を考えます。データベースを,ファイルのロックとレコードのロックの 2 つのレベルでロックできるとします。ファイル内のすべてのレコードを更新する場合,ファイル全体をロックして,他のロックは行わずにレコードを更新した方が,高速で効率もよくなります。しかし,特定のレコードを更新する場合は,必要に応じてレコードをロックする方が効率的です。

親ロックをこのように使う場合,すべてのプロセスの要求で,ファイルをロックします。すべてのレコードを更新するプロセスは,該当するファイルに対して,保護書き込み (PW) モード,または排他 (EX) モードのロックを要求しなければなりません。個々のレコードを更新するプロセスは,ファイルに対して同時書き込み (CW) モードのロックを要求し,その後,個々のレコードに対して,PW モード または EX モードのサブロックをかけます。

このように,すべてのレコードにアクセスする必要があるプロセスはファイルをロックすることでアクセスでき,一方で,ファイルを共用するプロセスは,個々のレコードをロックすることができます。これにより,多くのプロセスが,ファイル・レベルのロックを同時書き込みモードで共用する一方で,サブロックを使って特定のレコードを更新することができます。

9.6.6    ロック値ブロック

ロック値ブロックは DLM_VALBLKSIZE のサイズの符号なしロングワードの構造体です。この構造体は,プロセスが DLM の関数を呼び出す際に valb_p パラメータおよび DLM_VALB オプションを指定することで,リソースに対応付けます。ロック・マネージャがリソースを作成する際に,このリソースのロック値ブロックも作成されます。DLM は,リソースへのロックがなくなるまで,リソースのロック値ブロックを維持します。

プロセスが新しいロックを要求するときに,DLM_VALB オプションを指定し,valb_p パラメータに有効なアドレスを指定して,これが許可された場合,リソースのロック値ブロックの内容は,プロセスのロック値ブロックにコピーされます。

プロセスが,PW モードまたは EX モードから,同等レベルまたは下位レベルのモードへのロック変換で,valb_p パラメータと DLM_VALB オプションを指定した場合,プロセスのロック値ブロックの内容が,リソースのロック値ブロックに格納されます。

このようにしてプロセスは,リソースの所有権とともに,ロック値ブロックの値を渡し,更新することができます。表 9-6 では,ロック変換によって,プロセスおよびリソースのロック値ブロックの内容が,どのように変化するかを示します。

表 9-6:  ロック変換によるロック値ブロックへの影響

取得しているロックのモード ロック変換後のモード
  NL CR CW PR PW EX
ヌル (NL) 読取り 読取り 読取り 読取り 読取り 読取り
同時読み取り (CR) -- 読取り 読取り 読取り 読取り 読取り
同時書き込み (CW) -- -- 読取り -- 読取り 読取り
保護読み取り (PR) -- -- -- 読取り 読取り 読取り
保護書き込み (PW) 書込み 書込み 書込み 書込み 書込み 読取り
排他 (EX) 書込み 書込み 書込み 書込み 書込み 書込み

PW モードまたは EX モードで許可されているロックが dlm_unlock 関数を使って解放されて,ロック値ブロックのアドレスが valb_p パラメータに指定され,DLM_VALB オプションが指定されていると,プロセスのロック値ブロックの内容がリソースのロック値ブロックに書き込まれます。解放されるロックがこれ以外のモードの場合は,ロック値ブロックは使用されません。

リソースのロック値ブロックが無効になる場合があります。このような場合,DLM は valb_p パラメータを指定して関数を呼び出したプロセスに対し,DLM_SUCCVALNOTVALID または DLM_SYNCVALNOTVALID の終了状態を返して警告します。次のような場合に,リソースのロック値ブロックが無効になることがあります。

9.7    DLM 関数を使ったローカル・バッファ・キャッシング

アプリケーションは,分散ロック・マネージャ (DLM) を使って,ローカル・バッファ・キャッシング (分散型バッファ管理とも呼ぶ) を行うことができます。ローカル・バッファ・キャッシングを行うと,多くのプロセスが,データ (たとえばディスク・ブロックなど) のコピーを,各プロセスのローカル・バッファに持つことができます。他のプロセスによる変更で,バッファ内のデータが有効でなくなった場合は,各プロセスに通知されます。変更の頻度が低いアプリケーションでは,バッファのコピーをローカルに持つことにより,入出力動作を大幅に削減することができます。このため,ローカル・バッファ・キャッシングあるいは分散型バッファ管理という名前で呼ばれています。ロック値ブロック,またはブロック通知 (あるいはその両方) を使って,バッファ・キャッシングを行うことができます。

9.7.1    ロック値ブロックを使う方法

ロック値ブロックを使ってローカル・バッファ・キャッシングを行うため,バッファをキャッシュしている各プロセスは,各バッファの現在の内容に対応するリソースに,ヌル (NL) モードのロックをかけます。この説明では,バッファにディスク・ブロックが格納されていることを前提とします。各リソースに対応するロック値ブロックは,ディスク・ブロックのバージョン番号を入れておくために使います。あるディスク・ブロックに初めてロックがかけられたとき,アプリケーションは,プロセスのロック値ブロックにある,そのディスク・ブロックの現在のバージョン番号を返します。

バッファの内容をキャッシュすると,このバージョン番号はバッファの内容とともに保存されます。バッファの内容を再利用するには,バッファを読み取るのか書き込むのかによって,NL モードのロックを,保護読み取り (PR) モードまたは排他 (EX) モードに変換しなければなりません。この変換で,ディスク・ブロックの最新のバージョン番号が返されます。アプリケーションでは,ディスク・ブロックのバージョン番号と,保存していたバージョン番号を比較します。これらが等しい場合,キャッシュされているコピーは有効です。これらが等しくない場合には,アプリケーションはそのディスク・ブロックのコピーを,ディスクから新しく読み取らなければなりません。

プログラムでバッファを変更する場合は必ず,変更したバッファをディスクに書き込み,バージョン番号をインクリメントして,対応するロックを NL モードに変換します。このようにすることで,次のプロセスが同じバッファのローカル・コピーを使おうとした場合,バージョン番号が異なることに気づき,キャッシュしてあるバッファ (無効になった) を使わずに,ディスクから最新のコピーを読み取ります。

9.7.2    ブロック通知を使う方法

ブロック通知は,現在ロックが許可されているプロセスに,他のプロセスが共存できないモードで同じリソースにアクセスしようとしてキューに置かれたことを通知するために使用します。

ローカル・バッファ・キャッシングを行う際に,ブロック通知を使う方法は 2 つあります。1 つは,バッファへの書き込みを遅らせる方法で,もう 1 つは,ロック値ブロックを使わずにローカル・バッファ・キャッシングを代替する方法です。

9.7.2.1    バッファへの書き込みの延期

ローカル・バッファ・キャッシングを行っているときには,EX モードのロックを解放する前に,バッファへの変更をディスクに書き込まなければなりません。特に短時間に多くの変更が見込まれる場合は,変更処理を行っている間 EX モードでロックしておき,バッファへの書き込みを 1 回だけにすると,ディスクへの入出力を減らすことができます。

しかし,これでは,その間は他のプロセスが同じディスク・ブロックを使えなくなります。EX モードでロックしているプロセスに,ブロック通知ができれば,このような事態は避けられます。この通知では,ロックしているプロセスに,他のプロセスで同じディスク・ブロックを使う必要があることを知らせます。この通知を受け取った場合,EX モードでロックしているプロセスは,バッファをディスクに書き込んで,ロック・モードを NL モードに変換することができます。これにより,他のプロセスがそのディスク・ブロックにアクセスできるようになります。ただし,同じディスク・ブロックにアクセスしたいプロセスが他にない場合は,最初のプロセスは変更を何度も行ってから,書き込みを 1 回だけで済ませることができます。

注意

ブロック通知を受け取ったプロセスは,次のブロック通知を受け取れるように,ロックを変換しなければなりません。

9.7.2.2    バッファのキャッシング

ブロック通知を使ってローカル・バッファ・キャッシングを実行する場合,バッファへの処理が終わったプロセスは,PR モードまたは EX モードのロックを NL モードに変換しません。代わりに,他のプロセスが共存できないモードで同じリソースをロックしようとするたびに,ブロック通知を受け取るようにします。この方式では,次にそのプロセスがバッファを使おうとしたときではなく,別の書き込みプロセスでこのバッファが必要になったときに,このプロセスでキャッシュしているバッファが無効であることが通知されます。

9.7.3    バッファのキャッシング方式の選択

ローカル・バッファ・キャッシングを実行する場合,バージョン番号を使う方法と,ブロック通知のどちらを使えばよいかは,アプリケーションの性格に依存します。バージョン番号を使用するアプリケーションでは,ロック変換が増え,ブロック通知を使用するアプリケーションでは,通知の配信が増えます。これらの方式は共存できることに注意してください。一方の方式を使っているプロセスがあって,同時に,もう一方の方式を使っているプロセスがあるということが可能です。一般的には,ブロック通知は,衝突が少ない環境で選択し,バージョン番号は衝突が多い環境で選択します。組み合わせて使ったり,必要に応じて切り替えて使うこともできます。

組み合わせて使うときに,プロセスが短時間にバッファの内容を再利用すると予測される場合は,ブロック通知を使い,すぐにバッファの内容を再利用すると予測できない場合は,バージョン番号を使います。

必要に応じて切り替えて使う場合,アプリケーションはブロック通知と変換の発生頻度を評価します。ブロック通知が頻繁に届く場合,バージョン番号を使う方式に切り替え,変換がたくさん発生し,キャッシュされたコピーが長時間変更されず有効な場合は,ブロック通知を使う方式に切り替えます。

例として,あるプロセスがデータベースの状態を絶えず表示し続け,他のプロセスが時々このデータベースをアップデートする場合を考えてみます。バージョン番号を使った場合,表示するプロセスはデータベースのコピーが有効かどうか,ロック変換を行って常に検証しなければなりません。これに対し,ブロック通知を使った場合,表示するプロセスは,データベースがアップデートされるたびに通知を受けます。ただし,アップデートが頻繁に行われる場合は,ブロック通知を頻繁に配信するよりは,バージョン番号を使う方式の方が効率的です。

9.8    分散ロック・マネージャの関数のコーディング例

次のプログラム例では,ネームスペースに加わり,そのネームスペースのリソースの初期ロックを取得するためにアプリケーションが使用する基本的な手法について説明します。また,ロック変換,ロック値ブロックの使用方法,ブロック通知ルーチンの使用方法といった DLM の主要な概念についても,この中で具体的に説明します。

例 9-1 に示すプログラム api_ex_master.c および api_ex_client.c は,ディレクトリ /usr/examples/cluster にあり,TCRMANxxx サブセットがインストールされている場合は,同じクラスタ・メンバ上,または異なるクラスタ・メンバ上で,並列に実行することができます。どちらのプログラムも,同じユーザ ID (UID) を持つアカウントから実行しなければなりません。また,プログラム api_ex_master.c を先に実行しなければなりません。 このプログラムの出力結果は,次のようになります。

% api_ex_master &
api_ex_master: grab a EX mode lock
api_ex_master: value block read
api_ex_master: expected empty value block got <>
api_ex_master: start client and wait for the blocking notification to
	       continue
% api_ex_client &
        api_ex_client: grab a NL mode lock
        api_ex_client: value block read
        api_ex_client: expected empty value block got <>
        api_ex_client: converting to NL-->EX to get the value block.
        api_ex_client: should see blocking routine run on master
api_ex_master: blk_and_go hold the lock for a couple of seconds
api_ex_master: blk_and_go sleeping
api_ex_master: blk_and_go sleeping
api_ex_master: blk_and_go sleeping
api_ex_master: blk_and_go setting done
api_ex_master: now convert (EX-->EX) to write the value block <abc>
api_ex_master: blkrtn: down convert to NL
api_ex_master: waiting for blocking notification
api_ex_master: trying to get the lock back as PR to read value block
        api_ex_client: blkrtn: dequeue EX lock to write value block <>
        api_ex_client: hold the lock for a couple of seconds
        api_ex_client: sleeping
        api_ex_client: sleeping
        api_ex_client: sleeping
        api_ex_client: value block read
        api_ex_client: expected <abc> got <abc>
        api_ex_client: sleeping waiting for blocking notification
        api_ex_client: done
api_ex_master: value block read
api_ex_master: expected <efg> got <efg>
api_ex_master done
 

例 9-1:  ロック,ロック値ブロック,およびロック変換

/********************************************************************
 *                                                                  *
 *                       api_ex_master.c                            *
 *                                                                  *
 ********************************************************************/
 
/* cc -g -o api_ex_master api_ex_master.c -ldlm */ 
 
#include <assert.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <errno.h> 
#include <signal.h> 
 
#include <sys/dlm.h> 
 
char *resnam = "dist shared resource"; 
char *prog; 
int done = 0; 
 
#ifdef DLM_DEBUG  
int dlm_debug = 2; 
#define Cprintf if(dlm_debug)printf 
#define Dprintf if(dlm_debug >= 2 )printf 
#else /* DLM_DEBUG */ 
#define Cprintf ; 
#define Dprintf ; 
#endif /* DLM_DEBUG */ 
 
void 
error(dlm_lkid_t *lk, dlm_status_t stat) 
{ 
        printf("%s: lock error %s on lkid 0x%lx\n",  
                prog, dlm_sperrno(stat), lk); 
        abort(); 
} 
 
void  
blk_and_go(callback_arg_t x, callback_arg_t y,  
           dlm_lkid_t *lk, dlm_lkmode_t blkmode) 
{ 
        int i; 
 
        printf("%s: blk_and_go hold the lock for a couple of seconds\n", 
                prog); 
        for (i = 0; i < 3; i++) { 
                printf("%s: blk_and_go sleeping\n", prog); 
                sleep(1); 
        } 
        printf("%s: blk_and_go setting done\n", prog); 
        /* done waiting */ 
        done = 1; [13] 
} 
 
void  
blkrtn(callback_arg_t x, callback_arg_t y,  
       dlm_lkid_t *lk, dlm_lkmode_t blkmode) 
{ 
        dlm_status_t stat; 
 
        Cprintf("%s: blkrtn: x 0x%lx y 0x%lx lkid 0x%lx blkmode %d\n", 
                prog, x, y, *lk, blkmode); 
        printf("%s: blkrtn: down convert to NL\n", prog); 
        if ((stat = dlm_cvt(lk, DLM_NLMODE, 0, 0, 0, 0, 0, 0)) != DLM_SUCCESS)
                error(lk, stat);  [16]
        /* let waiters know we're done */ 
        done = 1; 
} 
 
main(int argc, char *argv[]) 
{ 
        int resnlen, i; 
        dlm_lkid_t lkid; 
        dlm_status_t stat; 
        dlm_valb_t vb; 
        dlm_nsp_t nsp; 
 
        /* this prog must be run first */ 
 
        /* first we need to join a namespace */ 
        if ((stat = dlm_nsjoin(getuid(), &nsp, DLM_USER)) != DLM_SUCCESS) {  [1] 
                printf("%s: can't join namespace\n", argv[0]); 
                error(0, stat); 
        } 
 
        prog = argv[0]; 
 
        /* now let DLM know what signal to use for blocking routines */ 
        dlm_set_signal(SIGIO, &i);  [2]
        Cprintf("%s: dlm_set_signal: i %d\n", prog, i); 
 
        resnlen = strlen(resnam);  [3]
        Cprintf("%s: dlm_set_signal: i %d\n", prog, i); 
 
        printf("%s: grab a EX mode lock\n", prog); 
        stat = dlm_lock(nsp, (uchar_t *)resnam, resnlen, 0, &lkid,  
                        DLM_EXMODE, &vb, (DLM_VALB | DLM_SYNCSTS), 0, 0,  
                        blk_and_go, 0);  [4]
        /*  
         * since we're the only one running it  
         * had better be granted SYNC status  
         */ 
        if(stat !=  DLM_SYNCH) {  [5]
                printf("%s: dlm_lock failed\n", prog); 
                error(&lkid, stat); 
        } 
        printf("%s: value block read\n", prog); 
        printf("%s: expected empty value block got <%s>\n",
                prog, (char *)vb.valblk); 
        if (strlen((char *)vb.valblk)) {   [6]
                printf("%s: lock: value block not empty\n", prog); 
                error(&lkid, stat); 
        } 
        printf("%s: start client and wait for the blocking
                notification to continue\n",  
                prog); 
        while (!done) 
                sleep(1);  [7]
 
        done = 0; 
        /* put a known string into the value block */ 
        (void) strcat((char *)vb.valblk, "abc"); [14]
        printf("%s: now convert (EX-->EX) to write the value block <%s>\n",  
                prog, (char *)vb.valblk); 
        /* use a new blocking routine */ 
        stat = dlm_cvt(&lkid, DLM_EXMODE, &vb, (DLM_VALB | DLM_SYNCSTS),  
                0, 0, blkrtn, 0);  [15]
        /*  
         * since we own (EX) the resource the  
         * convert had better be granted SYNC  
         */ 
        if(stat !=  DLM_SYNCH) { 
                printf("%s: convert failed\n", prog); 
                error(&lkid, stat); 
        } 
 
        printf("%s: waiting for blocking notification\n", prog); 
        while (!done)  
                sleep(1); 
        printf("%s: trying to get the lock back as PR to read value block\n",  
                prog); 
        stat = dlm_cvt(&lkid, DLM_PRMODE, &vb, DLM_VALB, 0, 0, 0, 0);  [19]
        if (stat != DLM_SUCCESS) { 
                printf("%s: error on conversion lock\n", prog); 
                error(&lkid, stat);
        } 
        printf("%s: value block read\n", prog); 
        printf("%s: expected <efg> got <%s>\n", prog, (char *)vb.valblk); 
        /* compare to the other known string */ 
        if (strcmp((char *)vb.valblk, "efg")) { 
                printf("%s: main: value block mismatch <%s>\n",  
                        prog, (char *)vb.valblk); 
                error(&lkid, stat);  [23]
        } 
        printf("%s done\n", prog);  [24]
        exit(0); 
} 
 
/********************************************************************
 *                                                                  *
 *                      api_ex_client.c                             *
 *                                                                  *
 ********************************************************************/
 
/* cc -g -o api_ex_client api_ex_client.c -ldlm */ 
 
#include <assert.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <errno.h> 
#include <signal.h> 
 
#include <sys/dlm.h> 
 
char *resnam = "dist shared resource"; 
char *prog; 
int done = 0; 
 
#ifdef DLM_DEBUG  
int dlm_debug = 2; 
#define Cprintf if(dlm_debug)printf 
#define Dprintf if(dlm_debug >= 2 )printf 
#else /* DLM_DEBUG */ 
#define Cprintf ; 
#define Dprintf ; 
#endif /* DLM_DEBUG */ 
 
void 
error(dlm_lkid_t *lk, dlm_status_t stat) 
{ 
        printf("\t%s: lock error %s on lkid 0x%lx\n",  
                prog, dlm_sperrno(stat), *lk); 
        abort(); 
} 
 
/*  
 * blocking routine that will release the lock and in doing so will 
 * write the resource value block. 
 */ 
void  
blkrtn(callback_arg_t x, callback_arg_t y, dlm_lkid_t *lk, dlm_lkmode_t blkmode\
) 
{ 
        dlm_status_t stat; 
        dlm_valb_t   vb; 
        int          i; 
 
        Cprintf("\t%s: blkrtn: x 0x%lx y 0x%lx lkid 0x%lx blkmode %d\n", 
                prog, x, y, *lk, blkmode); 
        printf("\t%s: blkrtn: dequeue EX lock to write value block <%s>\n",  
               prog, (char *)vb.valblk); 
        printf("\t%s: hold the lock for a couple of seconds\n", 
                prog); 
        for (i = 0; i < 3; i++) { 
                printf("\t%s: sleeping\n", prog); 
                sleep(1); 
        } 
        /* make sure its clean */ 
        bzero(vb.valblk, DLM_VALBLKSIZE); 
        /* write something different */ 
        (void) strcat((char *)vb.valblk, "efg");  [20]
        if((stat = dlm_unlock(lk, &vb, DLM_VALB)) != DLM_SUCCESS)  
                error(lk, stat);  [21]
        /* let waiters know we're done */ 
        done = 1; 
} 
 
main(int argc, char *argv[]) 
{ 
        int resnlen, i; 
        dlm_lkid_t lkid; 
        dlm_status_t stat; 
        dlm_nsp_t nsp; 
        dlm_valb_t vb; 
 
        /* first we need to join a namespace */ 
        if ((stat = dlm_nsjoin(getuid(), &nsp, DLM_USER)) != DLM_SUCCESS) { 
                printf("\t%s: can't join namespace\n", argv[0]); 
                error(0, stat);  [8]
        } 
 
        prog = argv[0]; 
 
        /* now let DLM know what signal to use for blocking routines */ 
        dlm_set_signal(SIGIO, &i); 
        Cprintf("\n\t%s: dlm_set_signal: i %d\n", prog, i);  [9]
 
        resnlen = strlen(resnam); 
        Cprintf("\t%s: resnam %s\n", prog, resnam); 
 
        printf("\n\t%s: grab a NL mode lock\n", prog); 
        stat = dlm_lock(nsp, (uchar_t *)resnam, resnlen, 0, &lkid,  
                        DLM_NLMODE, &vb, (DLM_VALB | DLM_SYNCSTS), 0, 0, 0, 0); 
        /* NL mode better be granted SYNC status */ 
        if(stat !=  DLM_SYNCH) {  [10]
                printf("\t%s: dlm_lock failed\n", prog); 
                error(&lkid, stat); 
        } 
        /* should be nulls since master hasn't written anything yet */ 
        printf("\t%s: value block read\n", prog); 
        printf("\t%s: expected empty value block got <%s>\n", prog, (char *)vb.\
valblk); 
        if (strlen((char *)vb.valblk)) {  [11]
                printf("\t%s: value block not empty\n", prog); 
                error(&lkid, stat); 
        } 
 
 
        done = 0; 
        printf("\t%s: converting to NL->EX to get the value block.\n", prog); 
        printf("\t%s: should see blocking routine run on master\n", prog); 
        stat = dlm_cvt(&lkid, DLM_EXMODE, &vb, DLM_VALB, 0, 0, blkrtn, 0);  [12]
        if(stat !=  DLM_SUCCESS) { 
                printf("\t%s: dlm_cvt failed\n", prog); 
                error(&lkid, stat); 
        } 
        /* should have read what master wrote, "abc" */ 
        printf("\t%s: value block read\n", prog); 
        printf("\t%s: expected <abc> got <%s>\n", prog, (char *)vb.valblk); 
        if (strcmp((char *)vb.valblk, "abc")) {  [17]
                printf("\t%s: main: value block mismatch <%s>\n",  
                prog, (char *)vb.valblk); 
                error(&lkid, stat); 
        } 
        /* now wait for blocking from master */ 
        printf("\t%s: sleeping waiting for blocking notification\n", prog); 
        while (!done) 
                sleep(1);  [18]
        printf("\t%s: done\n", prog);  [22]
        exit(0); 
} 
 

  1. プログラム api_ex_master.c は,dlm_nsjoin 関数を呼び出して,ロックを要求するリソースのネームスペースに加わります。このネームスペースは,現在のプロセスの UID です。これは getuid システム・コールで取得しています。このネームスペースは,DLM_USER パラメータで示されるとおり,実効 UID がリソースの所有者の UID と同じプロセスにアクセスを許すネームスペースです。正常終了の場合,この関数は,nsp パラメータで示されるメモリ位置に,ネームスペース・ハンドルを返します。 [例に戻る]

  2. プログラム api_ex_master.c は,dlm_set_signal 関数を呼び出して,DLM が,このプロセスに完了通知およびブロック通知を送る際に,SIGIO シグナルを使うように指定しています。 [例に戻る]

  3. プログラム api_ex_master.c は,次に呼び出す dlm_lock 関数に渡すために,リソース名の長さを取得します。リソースの名前は dist shared resource です。 [例に戻る]

  4. プログラム api_ex_master.c は,ネームスペース uid のリソース dist shared resource を,排他モード (DLM_EXMODE) でロックするために,dlm_lock 関数を呼び出します。ネームスペース・ハンドル,リソース名,およびリソース名の長さは,すべて必須パラメータとして渡されます。

    DLM_SYNCSTS フラグで指示しておくと,ロック要求がすぐに許可された場合,DLM は DLM_SYNCH 状態を返します。関数の呼び出しに成功すると,DLM は,lkid パラメータで指定されたメモリ位置に,排他モード (EX) ロックのロック ID を返します。

    この関数には DLM_VALB フラグ,およびリソースのロック値ブロックの内容を書き込むまたは読み取るメモリ位置を指定します。DLM は,dlm_lock 関数を呼び出して要求したロックが許可された場合に,リソースのロック値を,このメモリ位置にコピーします。最後に,ブロック通知ルーチン blk_and_go を指定します。DLM は,ロックが許可された後,このリソースに対する他のロック要求がブロックされたときに,このルーチンを呼び出します。 [例に戻る]

  5. プログラム api_ex_master.c は,dlm_lock 関数から返された状態値を調べます。状態値が DLM_SYNCH 状態 (dlm_lock 関数を呼び出す際に DLM_SYNCSTS フラグを指定した場合の,成功の条件値) でない場合は,ロック要求が許可されるのを待たなければなりません。今回は,このロックを要求しているプログラムが他に実行されていないので,このような状況は発生しません。 [例に戻る]

  6. プログラム api_ex_master.c は,DLM が vb パラメータで指定されたメモリ位置に書き込んだ,値ブロックの内容が空であることを検証します。 [例に戻る]

  7. プログラム api_ex_master.c は,ユーザがプログラム api_ex_client.c を起動するのを待ちます。このプログラムは,dist shared resource をロックしている排他モード (DLM_EXMODE) のロックのために,プログラム api_ex_client.c で出された同じリソースへのロック要求がブロックされたというブロック通知を受け取ったときに再開されます。 [例に戻る]

  8. プログラム api_ex_client.c を起動すると,uid ネームスペースに加わるために dlm_nsjoin 関数を呼び出します。このネームスペースは,プログラム api_ex_master.c を実行しているプロセスが,前に加わっていたのと同じものです。 [例に戻る]

  9. プログラム api_ex_client.c では,プログラム api_ex_master.c と同じように,dlm_set_signal 関数を呼び出して,DLM が,完了通知およびブロック通知をこのプロセスに送る際に,SIGIO シグナルを使うように指示します。 [例に戻る]

  10. プログラム api_ex_client.c では,dlm_lock 関数を呼び出して,api_ex_master.c を実行しているプロセスが既に排他モードでロックしているリソースに,ヌル・モード (DLM_NLMODE) のロックをかけます。DLM_SYNCSTS フラグで指示しておくと,ロック要求がすぐに許可された場合,DLM は DLM_SYNCH 状態を返します。ヌル・モード (NL) のロックは,この前に許可されている排他モードのロックと共存できるため,このロック要求はすぐに許可されます。この関数ではまた,DLM_VALB フラグとロック値ブロックへのポインタも指定しています。DLM は dlm_lock で要求したロックが許可された場合,リソースのロック値をこのメモリ位置にコピーします。 [例に戻る]

  11. プログラム api_ex_client.c は,DLM が,vb パラメータで指定されたメモリ位置に書き込んだ値ブロックの内容を調べます。値ブロックは,プログラム api_ex_master.c がまだ書き込んでいないため,空でなければなりません。 [例に戻る]

  12. プログラム api_ex_client.c は,dlm_cvt 関数を呼び出して,このプロセスがリソースにかけているヌル・モードのロックを,排他モードに変換します。ここで,ブロック通知ルーチン blkrtn を指定します。プログラム api_ex_master.c を実行しているプロセスが,既にこのリソースに排他ロックをかけているので,そのプロセスは,プログラム api_ex_client.c のロック変換要求をブロックします。しかし,プログラム api_ex_master.c がかけている排他モードのロックで,ブロック通知ルーチンを指定しているため,DLM は,SIGIO シグナルを使って,プログラム api_ex_master.c を実行しているプロセスにブロック通知を送り,ブロック通知ルーチン (blk_and_go) をトリガします。 [例に戻る]

  13. blk_and_go ルーチンは 3 秒間スリープし,その後,done フラグを立てます。これにより,プログラム api_ex_master.c が再開されます。 [例に戻る]

  14. プログラム api_ex_master.c は,そのリソースの値ブロックのローカル・コピーに,文字列 abc を書き込みます。 [例に戻る]

  15. プログラム api_ex_master.c は,ロック値ブロックに書き込むために dlm_cvt 関数を呼び出します。これを行うために,リソースにかけている排他モードのロックを,排他モード (DLM_EXMODE) に変換します。このとき,関数にはパラメータとして,ロック ID,値ブロックのコピーのあるメモリ位置,および DLM_VALB フラグを指定します。DLM_SYNCSTS フラグで指示しておくと,ロック要求がすぐに許可されたとき,DLM は DLM_SYNCH 状態を返します。このプロセスは既に排他モードでリソースをロックしているので,このロック変換要求はすぐに許可されるはずです。

    dlm_cvt 関数には,ブロック通知ルーチンとして blkrtn ルーチンも指定します。リソースにかけている,この排他モードのロックで,プログラム api_ex_client.c から出されるロック変換要求がブロックされるので,DLM はこのブロック通知ルーチンをすぐに呼び出します。 [例に戻る]

  16. プログラム api_ex_master.cblkrtn ルーチンが起動され,すぐに dlm_cvt 関数を呼び出して,リソースにかけているロックを,排他モードからヌル・モードに下位変換します。この呼び出しは,すぐに正常終了します。 [例に戻る]

  17. この変換が実行されるとすぐに,プログラム api_ex_client.c のロック変換要求が正常終了します。これは,プログラム api_ex_master.c を実行しているプロセスがかけたヌル・モードのロックが,プログラム api_ex_client.c を実行しているプロセスが今取得した排他モードのロックと共存できるためです。ヌル・モードのロックを排他モードに上位変換する際,DLM は,プログラム api_ex_client.c を実行しているプロセスに,リソースのロック値ブロックをコピーします。このとき,プログラム api_ex_client.c は,プログラム api_ex_master.c が前にリソースのロック値ブロックに書き込んた文字列 abc を受け取ります。 [例に戻る]

  18. プログラム api_ex_client.c は,スリープ状態に入ってブロック通知を待ちます。 [例に戻る]

  19. プログラム api_ex_master.c は,リソース dist shared resource のロックを下位変換してから,スリープ状態にありましたが,ここで dlm_cvt 関数を呼び出して,リソースにかけているヌル・モードのロックを保護読み取りモード (DLM_PRMODE) に変換します。プログラム api_ex_client.c を実行しているプロセスが,既に排他モードでこのリソースをロックしているので,プログラム api_ex_master.c のロック変換要求はこのロックによってブロックされることになります。つまり,排他モードのロックと保護読み取りモードのロックは共存できません。ただし,プログラム api_ex_client.c がかけている排他モードのロックでブロック通知ルーチンが指定されているため,DLM は,SIGIO シグナルを送信して,プロセスにブロック通知を配信し,そのプロセスのブロック通知ルーチン (blkrtn) をトリガします。 [例に戻る]

  20. プログラム api_ex_client.c のブロック通知ルーチン blkrtn は,数秒間スリープ状態になり,その後,そのリソースの値ブロックのローカル・コピーに文字列 efg を書き込みます。 [例に戻る]

  21. プログラム api_ex_client.c のブロック通知ルーチン blkrtn は,dlm_unlock 関数を呼び出して,このリソースのロックを解放します。リソースのロック値ブロックのローカル・コピーのアドレス,および DLM_VALB フラグを指定して,DLM に対し,許可されるモードが保護書き込み (DLM_PWMODE) または排他 (DLM_EXMODE) の場合は,値ブロックのローカル・コピーを,リソースの値ブロックに書き込むよう要求します。このプログラムでは,許可されるモードは DLM_EXMODE なので,値ブロックのローカル・コピーがリソースのロック値ブロックに書き込まれます。 [例に戻る]

  22. プログラム api_ex_client.c が完了し終了します。 [例に戻る]

  23. プログラム api_ex_client.c を実行しているプロセスがリソースのロックを解放すると,すぐにプログラム api_ex_master.c のロック変換要求が正常終了します。ヌル・モードのロックを保護読み取りモードに上位変換する際に,DLM は,リソースのロック値ブロックを,プログラム api_ex_master.c を実行しているプロセスにコピーします。このとき,プログラム api_ex_master.c は,前にプログラム api_ex_client.c がリソースのロック値ブロックに書き込んだ文字列 efg を受け取ります。 [例に戻る]

  24. プログラム api_ex_master.c が完了し終了します。 [例に戻る]