クリティカルセクションの相互排除

問題の説明

1.クリティカルセクションとは何ですか?
クリティカルセクションは、共有リソース(共有ファイル、共有変数、グローバル変数など)によってアクセスされるプログラムコードの一部です。共有メモリへのアクセスは、クリティカルセクションと他のコードの違いです。コンピュータで実行されている複数のプロセスにすべてクリティカルセクションを実行するコードがある場合、現時点では共有メモリの競合が発生します。複数のクリティカルセクションが共有メモリを読み取っている場合、問題は大きくなく、競合は発生しません。クリティカルセクションが共有メモリを変更する場合、この時点で問題は大きくなります。プロセスAが共有変数を変更したい場合、変更のために変数を読み取った後、書き戻しようとしていますが、中断され、別のプロセスBが実行中に共有変数を読み取ります。このとき、競合状態が発生しましたプロセスBによって読み取られた値は古い値であり、更新された値ではありません。競合状態につながる同様の状況が多数あります。2つ以上のプロセスが共有データの読み取りと書き込みを行う場合、最終的な結果は、プロセスが実行される正確な順序によって異なります。

したがって、問題は、各クリティカルセクションが共有データを読み書きするときに競合状態を防ぐ方法です。つまり、クリティカルセクションの相互排除を実現する方法は?
2.解決策は何ですか?
一般的に使用される方法は、ビジー待機方法と他の重要な領域のブロックに分けることができます。
ビジーウェイトにはいくつかの方法があります。

  • シールド割り込み
  • ロック変数
  • 厳密な回転
  • ピーターソンのソリューション
  • TSL命令

ブロックする方法はいくつかあります。

  • スリープおよびウェイクアッププリミティブを使用する
  • 信号
  • 観城

次に、それらを一つずつ紹介します。

解決

ビジーウェイト方式

1.シールドされた割り込み
シングルプロセッサシステムでは、競合の理由の大部分は、共有メモリにアクセスするときにプロセスが切り替わる、つまり、クリティカルセクションを実行するプロセスが中断されてから、他のクリティカルセクションが移動することです。再び共有メモリにアクセスしました。単純で失礼な方法は、クリティカルセクションが実行されたときに割り込みを直接シールドすることです。割り込みをシールドした後、クロック割り込みもシールドされ、CPUはクロック割り込みが発生したときにのみプロセスを切り替えることができます。共有メモリにアクセスするときにプロセスを切り替えることができないことも、共有メモリへのアクセスの継続性を維持し、他のプロセスによって中断されることはなく、共有メモリへのアクセスは一貫性を保ちます他のプロセスがアクセスしたい場合、それらは待機(ビジー待機)のみが可能です。
しかし、この方法は良くありません。割り込みをシールドする権利をユーザープロセスに渡すことは非常に危険です。ユーザープロセスがクリティカルエリアに入り、割り込みをオンにしないと、これが原因でシステム全体がクラッシュする可能性があります。また、マルチコアプロセッサの場合、他のCPUで実行されているプロセスが共有変数にアクセスするのを防ぐことはできません。

2.変数をロックします。
鮮やかに言うと、共有メモリにロックを追加することです。プロセスのクリティカルセクションで共有メモリにアクセスする場合は、最初にそれを開こうとする必要があります。開くことができる場合は、開くことができない場合は、アクセスできます。開くことができるようになるまで待ちます(つまり、ビジーウェイト)具体的な実装方法:最初に共有変数を作成します(つまり、ロック。他のプロセスもアクセスできるように、このロックも共有する必要があります。ただし、ロックも共有されるため、ロック変数にアクセスするコードには次のようなものがあります。クリティカルセクションになり、これにより新しい問題が発生します)、ロックを0に初期化します。これは、共有メモリにアクセスできることを意味します。1の場合、プロセスが共有メモリにアクセスしていることを意味し、アクセスできるのは次の場合のみです。ロックは0(ビジーなど)です。
ロック変数の問題は明らかです。異なるプロセスがロックをテストするとき、それらはクリティカルセクションも実行しています。では、ロック変数にアクセスするときに相互排除を保証するにはどうすればよいでしょうか。

3.厳密な回転方法
厳密な回転方法は、実際には一種のロック変数です。厳密なローテーション方式では、クリティカルセクションに入るターンがどのプロセスであるかを記録する整数変数ターンがあり、初期値は0です。厳密なローテーション方式を実装するための擬似コード:

进程 0 的代码:
while(True){
	while( turn != 0) ; // 循环,忙等
	临界区代码,进行共享内存的访问;
	turn = 1;
	非临界区;
}

进程 1 的代码:
while(True){
	while( turn != 1) ; // 循环,忙等
	临界区代码,进行共享内存的访问;
	turn = 0;
	非临界区;
}

厳密なローテーション方式には、変数をロックするという問題もあります。ターンアクセスの相互排除を誰が保証しますか?したがって、両方のプロセスが同じ共有メモリにアクセスしている可能性があります。また、ロック変数との違いは、厳密なローテーション方式のローテーション機能です。上記のコードから、共有メモリにアクセスするには、プロセス0とプロセス1に交互アクセスする必要があることがわかります。この2つのプロセスの一方は高速で実行され、もう一方は低速ですか?現時点では、高速プロセスはビジー待機中のみループインを続けることができます(ビジー待機に使用されるロックはスピンロックとも呼ばれます)。実行速度の差が大きいプロセスの場合、ラウンドロビン方式は適していません。

4. Petersonのソリューション
Petersonのメソッドは、rotationメソッドでturn変数も使用し、配列を使用して、クリティカルセクションを実行するユーザーを記録します。Petersonのソリューションのコードは次のとおりです。

#define FALSE 0
#define TRUE 1
#define N 2									//进程数量

int turn;									//轮到谁进入临界区
int interested[N];							//所有值初始化为 FALSE

void enter_region(int process)				//进程号是0或1
{
	int other;								//另一进程号
	 other = 1 - process;
	 interested[process] = TRUE;			//表示 process 进程想要访问临界区
	 turn = process;
	 while(turn == process && interested[other] == TRUE) ;		//忙等,关键语句,这是这个语句解决了turn的同时访问问题
}

void leave_region(int process)				//process 进程离开临界区
{
	interested[process] = FALSE;
}

ここで関心のある配列は、ターンが複数のプロセスによってアクセスされる場合の上記の問題を解決します複数のプロセスが同時にターンを書き込む場合は、独自のプロセス番号(0または1)に設定しますが、有効なのは後で書き込まれるプロセス番号のみであり、最初に書き込まれたターンは後で書き込まれるターンによって上書きされます。プロセス1が後で書き込まれると仮定すると、ターンは1です。両方のプロセスがwhileステートメントまで実行されると、プロセス01は0サイクルを実行してクリティカルセクションに入り、プロセス1はプロセス0がクリティカルセクションを出るまでループします。

5. TSL命令
このメソッドは、TSL命令によってのみ実装する必要があります。

TSL RX, LOCK

TSL命令はテストアンドロックになります。上記のコードは、メモリワードLOCKをレジスタRXに読み込み、ゼロ以外の値をメモリアドレスに格納します。読み取りおよび書き込み操作はアトミック(分離不可)であることに注意してください。TSL命令を実行するCPUは、メモリバスをロックして、この命令が終了する前に他のCPUがメモリにアクセスできないようにします。ストレージバスのロックは、シールド割り込みと同じではありません。シールドプロセッサはマルチコアプロセッサでは実行できませんが、バスがロックされた後、他のプロセッサはメモリにアクセスできませんTSL命令を使用して、次のように相互排除を実現します。

enter_region:
	TSL REGISTER, LOCK					;复制锁到寄存器并设锁为1
	CMP REGISTER, #0 					;测试锁是否为0
	JNE enter_region					;若不是0,说明已被设置,则循环,忙等
	RET									;返回调用者,调用者进入临界区

leave_region:
	MOVE LOCK, #0						;在锁中存0
	RET

TSL命令は、LOCKの元の値をレジスタにコピーして1に設定し、この元の値を0と比較します。ゼロ以外の場合はロックされて再テストされています。0の場合はRETと呼び出し元クリティカルセクションを実行します。

ブロッキング方法

1.スリープおよびウェイクアッププリミティブを使用する
プリミティブは中断できないプロセスです。スリーププリミティブにより、呼び出し元はスリープ状態になり、ブロックされます。ウェイクアッププリミティブは、プロセスをウェイクアップしますsleppとwakeupは、通常、システムコールとして使用されます。sleppとwakeupを使用する場合、通常、sleppとwakeupは、特定の変数の値に基づいて相互排除を実現するために呼び出されます。生産者/消費者問題など:

#define N 100					//缓冲区的槽数目
int count = 0;					//缓冲区中的数据项数目

void  producer()
{
	int item;
	while(TRUE){
		item = produce_item();			//生产数据
		if (count == N) sleep();			//如果缓冲区满则调用sleep进入睡眠
		insert_item(item);
		count++;
		if(count == 1) wakeup(consumer);		//如果刚才缓冲区是空的则唤醒睡眠的消费者
	}	
}


void consumer()
{
	int item;
	while(TRUE){
		if(count == 0) sleep();			//如果缓冲区为空则睡眠
		item = remove_item();
		count--;
		if(count == N-1) 	wakeup(producer);			//如果刚才缓冲区是满的,则唤醒生产者
		consume_item(item);
			
	}

}

この方法の問題は、カウントへのアクセスが競合することです。これにより、ウェイクアップ信号が簡単に失われます。たとえば、コンシューマーがバッファーが空であることを検出し、カウントが0であること読み取ると、スリープ前に一時停止されます。その後、プロデューサーが実行されます。アイテムを生成した後、コンシューマーがスリープしていると考えて、カウントが1であることがわかります。 、次にwankeup(consumer)、ウェイクアップ信号がコンシューマーに送信されますが、今回はコンシューマーがスリープしていなかったため、ウェイクアップ信号の損失が発生します。コンシューマーが実行中の場合、以前に読み取られたカウントが0であるため、コンシューマーはスリープ状態になります。その後、プロデューサーは遅かれ早かれバッファを埋め、次にSLEEPを実行します。したがって、消費者と生産者の両方が眠り、誰もそれらのいずれも起こさない。

2.セマフォ
セマフォは整数変数を使用してウェイクアップの数累積します。この変数はセマフォと呼ばれます。セマフォの値は0で正の値にすることができます。セマフォでは、ダウンとアップの2つの操作も使用されます(pおよびvとも呼ばれます)。セマフォでダウン操作を実行することは、その値が0より大きいかどうかを確認することです。値が0より大きい場合、値は1で減算され(つまり、保存されたウェイクアップ信号が使用されます)、実行が続行されます。値が0の場合、プロセスはスリープ状態になり、この時点でダウンは終了しません。 。セマフォでアップ操作を実行すると、セマフォが1増加します。1つ以上のプロセスがこのセマフォでスリープしている場合、つまり、プロセスのダウン操作が完了していない場合は、スリープしているプロセスの1つをウェイクアップし、ダウンを完了し続けます。オペレーティング。**チェック値、変数の変更、およびダウン操作とアップ操作で可能なスリープ操作は切り離せないことに注意してください。**セマフォを使用して生産者/消費者問題を解決します。

#define N 100					//缓冲区的槽数目
typedef int semaphore 			//信号量是一种特殊的整形数据
semaphore mutex = 1;			//控制对临界区的访问
semaphore empty = N;			//计数缓冲区的空槽数
semaphore full = 0;				//技术缓冲区的满槽数
int count = 0;					//缓冲区中的数据项数目

void  producer()
{
	int item;
	while(TRUE){
		item = produce_item();			//生产数据
		down(&empty);			//将空槽数减一,若已满则会阻塞
		down(&mutex);			//进入临界区
		insert_item(item);
	    up(&mutex);				//离开临界区
		up(&full);	 			//将满槽数加1
	}	
}


void consumer()
{
	int item;
	while(TRUE){
		down(&full);			//将满槽数减一,若为空则会阻塞
		down(&mutex);			//进入临界区
		insert_item(item);
	    up(&mutex);				//离开临界区
		up(&empty);	 			//将空槽数加1
	}

}

セマフォは相互排除の問題を非常にうまく解決しているようですが、コード交換シーケンスが次の場合は問題を検討してください。

down(&empty);			//将空槽数减一,若已满则会阻塞
down(&mutex);			//进入临界区

プロデューサーがブロックされ、同時にコンシューマーもブロックされる可能性があります。したがって、両方のプロセスがブロックされます。セマフォを使用するときは細心の注意を払い、操作の順序に注意する必要があることに注意してください。

3.モニター
モニターは、プロセス、条件変数、およびデータ構造で構成されるコレクションであり、特別なモジュールまたはソフトウェアパッケージを形成します。プロセスは、必要なときにいつでもモニター内のプロシージャーを呼び出すことができますが、モニター外で宣言されたプロセス内のモニター内のデータ構造にアクセスすることはできません。監視プロセスの重要な特徴は、監視プロセスには常に1つのアクティブなプロセスしか存在できないことです。モニターはプログラミング言語の不可欠な部分であり、コンパイラーはその特殊性を認識しているため、他のプロシージャー呼び出しとは異なるメソッドを使用してモニターへの呼び出しを処理できることに注意してください。条件変数は通常、モニターで操作不能なプロセスをブロックするため使用され、2つの操作(待機とシグナル)が使用されます。

おすすめ

転載: blog.csdn.net/Miha_Singh/article/details/90383838