• 検索結果がありません。

排他制御のために何をすれば良いか —基本的な考え方—

ドキュメント内 新潟大学学術リポジトリ (ページ 169-173)

共有データ (預金額)

プロセスA の遷移 プロセスB (1)預金額(x)の読み込み←−←− x

(2)預金引き落とし後の残 高を計算: y =x−d

(ディスパッチ) ⇓

⇓ −→−→ (1)預金額(x)の読み込み

⇓ (2)預金預け入れ後の残高

を計算: z=x+a

z ←−←− (3)共有データを更新

⇓ (終了・ディスパッチ)

(3)共有データを更新 −→−→ y

(6=x−d+a)

例12.1における処理の問題点: 共有データに対するアクセスにルールが無い。

@@

共有資源については、1つのプロセスが使い始めた時点で、(その使用が終るま で)他のプロセスからのアクセスを禁止する必要がある。

'

&

$

% 預金の引出し/預け入れの例の場合は、

読み出しだけなら、矛盾は起きない。

共有データの更新を行うプロセスが1つでもあれば、その処理の間、その 共有データへのアクセスは抑止されなければならない。

クリティカル・セクション: プログラムの中で、途中に別のプロセスの実行が割り込ん ではいけない(i.e.共有データに矛盾が生じる可能性がある)区間をクリティカル・セクショ ン(critical section, 危険部分)またはクリティカル・リージョン(critical region)と言う。

12.3 排他制御のために何をすれば良いか — 基本的な考え方

{吉沢7.2節,金山 付録III}

クリティカル・セクションの最中にディスパッチ(i.e.プロセスの切替え) が起こらない 様にする。

12.3.1 割込み禁止による排他制御

考え方: クリティカル・セクションの間は割込み禁止にして他のプロセスにディスパッ チされないようにする。 そのために、割込み禁止の操作disable( )と割込みを可能にす

る操作enable( )を用いて全てのクリティカル・セクションを次のように書き換える。

disable( ); /* 割込み禁止にする */

クリティカル・セクション enable( ); /* 割込み可能にする */

この方法では、タイマ割込み、入出力割込み、他プロセッサからの通信割込みを始めとし た全ての割込みが抑止される。

=⇒ クリティカル・セクションの間中、資源を独占できそう。

欠点:

• 割込み禁止の権限をユーザプログラムに与えるのは危険。(システム全体の信頼性と 性能の低下に繋がる。)

'

&

$

% 例えば、

(意図的に、もしくは誤って)クリティカル・セクション終了後にenable( )

が実行されないと、コンピュータは割込みを受け付けないモードのままで、

3 端末からの入力が行えなくなる。

3 タイマ割り込みで起動される全ての処理が実行不可能になる。

• マルチプロセシングに対応できない。

'

&

$

% なぜなら、

マルチプロセシング環境では割込み禁止の設定はCPU毎に行われる。

=他のプロセッサ上からクリティカル・セクションの間に別のプロセス が共有資源にアクセスする可能性が残る。

12.3.2 鍵を掛けることによる排他制御

考え方: ホテルの部屋と同じ様に、クリティカル・セクションの間は資源に鍵を掛けて (具体的には既に「使用中」であることを他のプロセスが分かる様にして)、他のプロセス がその間に資源にアクセスしないようにする。そのために、資源が空くまで待って空いた らその鍵(i.e.使用権)を確保して鍵を掛ける操作lock( )と、鍵を解放する操作unlock( ) を用いて全てのクリティカル・セクションを次のように書き換える。

lock( ); /* 資源が空くまで待って空いたらその鍵を確保して鍵を掛ける */

クリティカル・セクション unlock( ); /* 鍵を解放する */

'

&

$

% 補足:

この方式で排他制御する場合、lock( )unlock( )は重要な操作である ので、システムコールとしてOSがその処理を行うのが一般的である。

ビジーウェイト法によるlock( )とunlock( )の実装(第一次案): 資源が使用中かどう かを表す変数 LOCKを用いれば、関数 lock( )とunlock( ) は次の様に実装出来るのでは ないかと考えられる。

12.3. 排他制御のために何をすれば良いか —基本的な考え方— 165

/* 大域変数 LOCK の初期設定 */

int LOCK = 0; /* LOCK==1 <==> 「使用中」 */

/* 関数 lock( ) の定義 */

void lock( ) {

while (LOCK != 0) /* 資源が空くまで待つ */

;

LOCK = 1; /* 空いたらすぐに鍵(i.e.使用権)を確保 */

}

/* 関数 unlock( ) の定義 */

void unlock( ) {

LOCK = 0;

} '

&

$

% 問題点:

ビジーウェイト(busy wait;ループして 絶えず確かめながら 待つ)によって 資源が空くのを待つので、この間はCPUの浪費となる。

=タイマ割り込みやタイムスライススケジューリング等により、ビジー ウェイトをしているプロセスを待ち状態にするような機構が必要。

プロセッサ数が2以上のマルチプロセシング環境においては、複数のプロセ スが大域変数LOCKに同時にアクセスして同じ資源の鍵を各々が手にすると いうことが起こり得る。

=大域変数LOCKへのアクセスも排他制御しないといけない。

(1つの資源を排他制御するためにフラッグを導入したが、そのフラッ グ自身が共有資源であって排他制御の必要性が生じる。...これじゃ、

切りがない。)

=ハードウェアの整備。(p.166 Test-and-set命令等)

最終的にはlock( )も機械語で実行される。LOCKは大域共有変数なので、機 械語レベルで関数lock( )を実装しようとすると、その機械語コードの中に クリティカルセクションが出来るのでは? この点に関しては次の段落で議論

=もしそうなら、CPU1台の場合もこれではうまく排他制御できない。

=ハードウェアの整備。(p.166 Test-and-set命令等)

機械語レベルでのlock( )の処理: LOCKは大域共有変数なので、関数lock( )を機械語 風に書き下ろしてみると、例えば次の様になる。

Obt Load R1,LOCK # while (LOCK != 0)

Comp R1,ZERO # ;

BNonZero Obt #

Load R1,ONE # LOCK = 1;

Store R1,LOCK #

しかし、LOCKは大域共有変数なので、これだとこの本体全体がクリティカルセクション になってしまう。 実際、プロセスAが1行目の“Load R1,LOCK” を実行してから最後の

“Store R1,LOCK” までの間にディスパッチされて別のプロセスBが lock( )の実行を始 めると、結果的にプロセスAもBも鍵(i.e.使用権)を確保してしまう。

@@

関数lock( )をクリティカルセクション無しの機械語コードで表すことが可能

になる様な、機械語命令がほしい。例えば、次のような命令:

• 共有変数の値のチェックと(その結果に応じた)更新を単一ステップ内で 実行。

• 実行中は他プロセッサからの共有変数へのアクセスを抑止する。

Test-and-set命令: lock( )手続きをクリティカルセクション無しの機械語コードで 表せる様にするために、次の様な機械語命令がハードウェアから提供されていることが 多い。

• 共有変数(e.g.LOCK)の変数値のチェックと(その結果に応じた)更新を単一ステップ内

で実行。

• マルチプロセシング環境にも対処するため、その機械語命令を実行する間は、他のプ ロセッサから共有変数へのアクセスを抑止する。

例えば、IBM System/370においては次の様な命令が用意されていた。

































TS(Test and Set)命令 · · · 指定したオペランドの先頭の1バイトを共有変数 と見て、共有変数に対する先頭ビットのチェックと値X”FF”の代入を atomic operationとして(i.e.途中で割り込まれることなく)実行する。

CS(Compare and Swap)命令 · · ·指定した4バイトの語領域を共有変数と見て、

共有変数のチェックと(その結果に応じた)更新をatomic operationと して実行する。

CDS(Compare Double and Swap)命令 · · ·指定した8バイトの倍長語領域を共 有変数と見て、共有変数のチェックと(その結果に応じた)更新をatomic operationとして実行する。

このうち、CS命令は次の様に動作する。

(命令形式) CS R1, R3, D2(B2)

R1 · · ·1オペランド(operand,「operateされるもの」という程

度の意味)となる、汎用レジスタの番号。

R3 · · ·3オペランドとなる、汎用レジスタの番号。

D2(B2) · · ·2オペランドとなる変数領域の番地を指定した部分 である。B2は変数領域の近くを指す汎用レジスタ(ベース レジスタと言う)の番号、D2B2から変数領域までの変位 (displacement)を表す。

 (処理内容) if (レジスタR1の内容 == D2(B2)の指す変数領域の内容) {

D2(B2)の指す変数領域 ←− レジスタR3の内容

}else {

レジスタR1 ←− D2(B2)の指す変数領域の内容 }

(副作用) 式 (レジスタR1の内容−D2(B2)の指す変数領域の内容) の計算をし た時と同等のコンディションコードがセットされる。

ドキュメント内 新潟大学学術リポジトリ (ページ 169-173)