Operating Systems. Exercise 03

I. プロセスへのシグナル送受信 (準備課題, 配点無し)
演習問題を解くために必要なシグナル制御のためのC言語インターフェースを説明します。
配点はありませんので、 シグナル制御インターフェイスについて知っている人はここを飛ばして、下のIIに進んでください。

課題1

実行中のプロセスに対してシグナルを送り、それをプログラム側で受けとることができます。

シグナルの受取側(プログラム)ではシグナルを受信したときに呼び出される関数を登録しておき、
シグナル送信は今回はターミナルからkillコマンドを使って送ります。 以下のプログラムでいろいろ実験してください。
[実験のために、プログラムを実行するターミナル(ktermなどのこと)と、
シグナルを送信する側のターミナルの計2つを用意しておくと良いでしょう]

シグナルSIGUSR1待ちプログラム (SignalTest)


#include <stdio.h>
#include <signal.h>

//プロトタイプ宣言を適宜

int main() {

  int i;

  //シグナルハンドラ関数を登録する
  //コマンド(% kill -USR1 "プロセスID")などにより シグナルSIGUSR1が送信されたとき 
  //シグナルハンドラ関数SignalHandlerが呼び出されるようになる
  signal(SIGUSR1, SignalHandler);

  for (i=0; ; ++i) {
    sleep(1); //すごい勢いで出力され重くなるので わざと1秒ずつ待っています
    printf("%d\n", i);
  }

  return 0;
}

// signal SIGUSR1 handler
void SignalHandler(int code) {

  printf("signal received\n");

  //シグナルハンドラ関数の再登録
  signal(SIGUSR1, SignalHandler);

}
	
ターミナル1 (シグナル待ちプログラム実行)

% ./SignalTest

0
1
2
3 
.
.
signal received (ターミナル2からSIGUSR1受信)
.
[Ctrl-C] (自分で終了させてください)
	      
  ターミナル2 (シグナル送信側)

% ps -a | grep SignalTest

 5197 ttys000    0:03.64 ./SignalTest
 5199 ttys001    0:00.00 grep SignalTest


% kill -USR1 5197 (ターミナル2からプロセス5197にSIGUSR1送信)
	      


II. プロセス間通信(配点100%)

課題1

課題: 演習1で作成したハノイの塔の問題を任意の引数で与えた個数の円盤に対して解くプログラムを 修正し、
シグナル"SIGUSR1"を受け取るごとに現在のハノイの塔の状況を出力するように改変してください。

: プログラムの実行形式のファイル名を "ProcComm1"とし, 円盤数を"20"としたとき

ターミナル1 (ハノイの塔プログラム実行)

% ./ProcComm1 20

(ターミナル2からSIGUSR1を受け取るたびに
現在の塔の状況を出力する) pid:5237 | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 1| | 0| | 0| | 2| | 0| | 0| | 5| | 0| | 0| | 8| | 0| | 0| |11| | 0| | 0| |12| | 0| | 0| |13| | 0| | 0| |14| | 0| | 0| |15| | 0| | 0| |16| | 0| | 0| |17| | 0| | 0| |18| | 4| | 3| |19| | 7| | 6| |20| |10| | 9| ---- ---- ---- A B C Number of Moves: 682 Numer of Disks: 20
  ターミナル2 (シグナル送信側)

% ps -a | grep ProcComm1

 5237 ttys000    0:03.64 ./ProcComm1 20
 5239 ttys001    0:00.00 grep ProcComm1


% kill -USR1 5237 (ターミナル2からSIGUSR1送信)
	      
補足: ハノイの塔を解くプログラムがあまりに早く終わってシグナルを送る暇がない場合、 適当なところでsleepやusleepを使ったり、円盤数を増やしたりしてください。

課題2

導入: 課題1のプログラム中で, 関数Solveの中に円盤を移動する以下のようなコードが有ると思います。

ハノイの塔の円盤を移動する部分


    *_naB = *_naA;  //処理1 : 棒_Aから_Bへ移動
    *_naA = 0;      //処理2 : 移動元の_Aの円盤をクリア
	

もし"処理1"と"処理2"の間に偶然シグナルが発生しハノイの塔の状態を出力すると、
そのとき動かしている円盤が2つ現れてしまいます(クリティカルパス)。
クリティカルパスが発生する状況を再現するために, プログラムを以下のように 改変します

クリティカルパスの状況を再現


    *_naB = *_naA; //処理1 : 棒AからBへ移動
    sleep(1)       //<--- わざと時間をかけ ここでシグナルが発生する状況を再現する
    *_naA = 0;     //処理2 : 移動元のAの円盤をクリア
	

改変したコードでシグナルを送る実験します。 すると、高確率で同じ円盤が2つ現れてしまいます

ターミナル1 (ハノイの塔プログラム実行)

% ./ProcComm2 20

(ターミナル2からSIGUSR1を受け取るたびに
現在の塔の状況を出力する) pid:6812 | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 0| | 5| | 0| | 0| | 6| | 0| | 0| | 7| | 0| | 0| | 8| | 0| | 0| | 9| | 0| | 0| |10| | 0| | 0| |11| | 0| | 0| |12| | 0| | 0| |13| | 0| | 0| |14| | 0| | 0| |15| | 0| | 0| |16| | 0| | 0| |17| | 1| | 0| |18| | 2| | 0| |19| | 3| | 0| |20| | 4| | 1| ---- ---- ---- A B C Number of Moves: 14 Numer of Disks: 20 [1が二つ出ている]
  ターミナル2 (シグナル送信側)

% ps -a | grep ProcComm2

 6812 ttys000    0:03.64 ./ProcComm2 20
 6814 ttys001    0:00.00 grep ProcComm2


% kill -USR1 6812 (ターミナル2からSIGUSR1送信)
	      

課題: 今回の改変後のコードのようなケースで、2重に円盤が出力されるという状況を 確実に回避する手段があるでしょうか?
有ればそのプログラムを, 無ければその理由を説明してください。

考え方: シグナルを受信を確認したらすぐにDisplayするのではなく, シグナルを受け取ったことを記憶しておいて
確実に処理1と処理2が終わった後でDisplayするようにできないか?