03.2資源の逐次化その2(SETLOCKとCS,CDS命令)

By 神居 - Posted: 2008/11/17 Last updated: 2009/12/15 - Leave a Comment
印刷用ページの表示 印刷用ページの表示

システムリソースの逐次化

MVSは制御表のキューの操作や、数ビットあるいは数ワードに対して連続的な変更をしている間、それらのメモリー領域が逐次化されることを必要とする処理が随所にあります。これらの排他制御を行うのがロック機能です。ロックはENQ/DEQと異なり資源に自由な名前を付けることはできません。代わりにロックワードと呼ばれるフルワード領域を資源毎に用意し、逐次化する資源を分類します。
ロックはSETLOCKマクロを使って獲得と解放を行うことができます。サンプルに示したのはローカルロックと呼ばれる、アドレス空間に固有の資源を逐次化する際に用いられるロックです。一般のプログラムがロックを使うことはありませんが、MVS自身は逆にENQ/DEQではなくロック機能を使って、コントロールブロックの変更やチェインの追加・切り離し、など制御に必要な逐次化を行っています。
OS以外のプログラム、特にアプリケーションが排他制御を行う目的でSETLOCKマクロを使うことはまずありません。しかしながらGETMAIN(BRANCH=YES)、POST(BRANCH=YES)、CHANGKEYなど一部のAPIで呼び出し時の環境要件としてローカルロックの保持を求められる場合があり、そのために使うことはあります。ただしそのようなAPIはSRBルーチンなど、SVC命令の発行が許されないような状況でOSのサービスを受けるために使用する特別なものです。

ロックを使えば複数のメモリー領域(例えば2000番地からの1ワード、3000番地からの2ワード、4000番地の1ビット)を続けて変更するような時、すべてのフィールドの変更が終了するまでそれらのメモリー領域の変更を逐次化することができます。しかし1ワードあるいは連続した2ワードの領域の内容を変更するだけならCPU命令によって逐次化することができます。1バイトの領域であっても前後のバイトを含めて1ワードの領域として逐次化することができます。


フルワードカウンターの更新

CS命令は第1オペランドで指定したレジスターの内容と第2オペランドで指定したフルワード領域の内容を比較して、一致していれば第3オペランド(r3レジスター)で指定したレジスターの内容を第2オペランドで指定したフルワード領域に書き込みます。一致していなければ主記憶の内容を第1オペランドで指定したレジスターにロードし直し、主記憶の内容は変更されません。実行結果は条件コードで判定できます。
この時CS命令は参照する主記憶域に特別な操作を行います。それが逐次化(Serialization)あるいはインターロック(Interlock)と呼ばれる動作です。CS命令は主記憶からデータを読み出し、比較し、書き込む、と言う一連の動作を行います。読み出し?格納までの間、他のCPUから同じ主記憶アドレスへの参照は禁止されます。この機能が無いと、マルチCPU環境では読み出してから格納するまでの一瞬の間に同じ領域が書き換えられる可能性があります。1つの同じ命令ですら読み出しから格納までには他のCPU動作が入り込む隙があるのですから、L命令とST命令の2つで同じ事をやったら壊れてしまうタイミングはもっと増えてしまいます。

システムプログラムでは複数の実行単位(マルチタスクはもちろん、シングルタスクでもAPIの非同期出口ルーチン機能を使う場合も複数の実行単位となる)で構成されるプログラムで同じ領域を更新する場合は、プログラムを動かすプロセッサーがマルチCPUかどうかに関係なく、CS命令を使用してフィールドを更新することをまず考えます。

最初の例はCS命令を使わない方法です。普通はこう考えます。CTRWORDフィールドを変更するプログラムが1つのタスクでのみ動くなら問題ありません。しかし同じプログラムが複数のタスクで動く場合はCTRWORDフィールドは競合します。命令のリファレンスには逐次化機能は2つ以上のCPU間で主記憶領域を排他制御するための仕組みと解説されていますから、CPUが1つなら大丈夫か?と思えますが例示のロジックの場合、CPUの数は関係ありません。
シングルCPUのプロセッサーでも、タスクAがLA命令で値を更新し、ST命令を実行しようとしたタイミングでI/O割込みなどが発生するとST命令の実行直前でプログラムは中断されます。割込み処理が終われば、プログラムの実行は再開されますが、必ずしも次にまたタスクAが実行される保証はありません。同じプログラムを動かすタスクBにディスパッチが移るかも知れません。そうなるとCTRWORDはタスクBによって更新されます。その後タスクAが再開されるとST命令によって上書きされますが、その内容はタスクBで更新した値が反映されません。これはCPU間の主記憶の競合の問題よりは排他を掛けずに主記憶フィールドを読み出し、書き込む、と分割した動作でやっているからです。
少し工夫して2番目のように改良できますが、BNE命令とST命令の間に割込みが入れば同じことです。CS命令はこのような主記憶域の更新動作を排他制御付きでやってくれる命令です。最初はわかりにくいかも知れませんが、カウンター更新のサンプルを見てその動きをぜひ覚えて下さい。SETLOCKなどを使うことはまずないと思いますが、CS命令によるフルワード領域の更新はシステムプログラムを書く場合、必須の知識になります。

CS命令では更新に失敗すると(他のタスクで更新された)、第1オペランドレジスターに最新の主記憶内容が読み込まれます。これは大切なポイントです。最初のサンプルと少し変えてラベルTRYAGAINを1つ前の命令にずらしてみました。このようにCTRWORDを読み直すようなロジックを何度か見たことがあります。もちろん間違いではありませんが余計なことです。失敗したCS命令によって読まれた最新の値に+1することで正しくカウンターは更新できます。

CDS命令はCS命令と同じ動作で、対象領域がダブルワードに拡張されたものです。なおLoadやSTore命令と異なり、第2オペランドで指定する主記憶アドレスはワード境界またはダブルワード境界になっていなけれ指定例外でABENDします。


トランザクションのキューイング

トランザクション・キューイング(チェイニング)のサンプルです。QUEUEPTRフィールドは最後に発生したトランザクションを示す制御表のアドレスを持っています。そこからポイントされる制御表(TRXBLOCK)のNEXTTRXフィールドはさらにその1つ前のトランザクションをポイントします。最初に発生したトランザクションのNEXTTRXはそれより古いのがないので0になっています。
上の図に示すように最新のトランザクションをQUEUEPTRに繋げ、そのNEXTTRXフィールドにそれまでの最新であるTRX2のアドレスが入るようにします。こういうのを連結リストなどと言いますが、ここではアルゴリズムのことは解説しません。MVSではOS自身もこのような構造でコントロールブロックを連結しているものがあります。(TCB?RBチェインなど)

サンプルではQUEUEPTRはプログラム内に持っていますが、実戦ではこのフィールドはプログラムの外に置かれ、複数のタスクで参照されます。トランザクションをキューイングする各々のタスクがQUEUEPTRフィールドにTRXBLOCKを繋げようとしますから競合します。CS命令を使えばこのような簡単なロジックで、ロストすることなく確実にチェインさせることができます。取り出すときはFIFOであればQUEUEPTRからリンクをたどり、NEXTTRXが0になっているチェインの最後のTRXBLOCK(TRX1)を取り出します。この時、1つ前のTRXBLOCK(TRX2)にTRX1のNEXTTRXの内容(実際は0)を格納します。取り出し時も、1つしかTRXBLOCKが繋がっていなければQUEUEPTRが、2つ以上繋がっていればTRXBLOCKが競合しますから、やはり更新にはCS命令を使う必要があります。そちらのサンプルは例示しませんので興味がある方はご自分で考えてみてください。


ビットフラグの排他制御付き更新

フラグビットのON/OFFにはOIおよびNI命令を使いますが、マルチタスクで同じフラグバイト内のビットを扱うとやはり競合が発生します。ビットを1にするタスクとビットを0にするタスクが異なる場合、何も考えずにOI命令とNI命令を使うと手痛い目に遭うことがあります。そういうプログラムを作ってしまい、市場に出してしまってから泣きそうな苦労をしていたエンジニアを見たことがあります。ほとんどのケースでは上手く行くが、タイミングによって問題が浮かび上がる、と言う典型的な例でもあります。一番いいのは競合するようなフラグを使わずに済むデザイン、使ったとしてもマルチタスクで突き合うようなことをしないで済むデザインを考えることですが、どうしてもフラグで制御したい方はOI/NI命令の代わりにOIL/NILマクロを利用できます。
上のサンプルはいずれもCNTLFLG2領域にFLAG2ビットをON/OFFする例ですが、OIL/NILマクロを使うと前後のバイトを含めたワード単位でCS命令によって更新が掛かります。作業用に3つのレジスターを必要としますが、WREGSパラメーターで指定することができます。省略時はGR0,1,2の3レジスターが使われます。この例ではレジスター2に代えてレジスター14を使うようにしています。WREGS=(,,14)はWREGS=(0,1,14)と同じです。このマクロはAPF許可プログラム用のAPIマニュアルに記載されていますが、実際はAPF許可は不要です。MSPとVOS3ではマニュアルに記載がありませんが、まったく同じマクロが提供されています。MVS3.8のSYS1.MACLIBにも入っていて30年以上も使われている伝統的なものでもあります。CS命令の応用的な使い方の実例でもありますので、マクロの展開形を見ると勉強になります。

MVSで動くプログラムであれば逐次化の方法には3種類あることを知って下さい。最も汎用的なのがENQ/DEQマクロによる方法でどんなものに対しても排他制御を掛けることができます。ただしオーバーヘッドは最も大きく、頻繁に掛け合う排他制御に使うとCPU消費量は大きくなります。
オーバーヘッドを避けるにはSETLOCKマクロによるロックを使う方法もありますが、本来はMVSがOSとしての制御に使うものでアプリケーションが自分の排他制御に使うものではありません。知識として知っておけばいいでしょう。SETLOCKを必要とするようなプログラムをきちんと作れるようになったらシステムプログラマーとしては上級レベルです。(SETLOCKマクロが使えればと言う意味ではありません…)
CS(CDS)命令によるメモリー領域の逐次化の方法はシステムプログラミングの基本で、メインフレームの仕組みでは最もオーバーヘッドの少ない排他制御になります。メモリー領域の更新であればENQ/DEQサービスを使う前にCSあるいはCDS命令を組み合わせたロジックで排他制御が実現できないかをまず考えましょう。連続しない複数のフィールドを一連の処理として更新する場合は命令による逐次化はできませんが、逆に言えば命令による逐次化が可能なデータ構造にできないかを考えることも重要です。

IBM社のz/Architecture解説書の付録「数の表現と命令の使用例」に「マルチプログラミングとマルチプロセッシングの例」が記載されています。ここに書かれているCS/CDS命令の解説やそれを使った各種のサンプルは、そのまま実戦のプログラミングに流用できる非常に優れたものです。命令の使い方を覚えたらぜひ一度は読むことを勧めます。

Posted in 中級編 • • Top Of Page