Operating Systems. Exercise 10

I. メッセージキュー (準備課題, 配点無し)

プロセス間通信のための仕組みにはメッセージキューというものもあります。
送信側はデータブロック(パケット)をMessage Queueに格納し,
受信側はMessage Queueに格納された順番にパケットを取り出し利用するというものです

1. メッセージキューによるプロセス間通信

例として "MsgQueueSend.c" がQueueにデータを送信し, "MsgQueueReceive.c" がQueueからデータを取り出すプログラムとします。
送信側はMyMsgPacket型の構造体(自分で型を宣言する)を送信し受信側はそれを受信します

MsgQueue.h (共通ヘッダ)

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//キューIDを生成するユニークキー
#define MY_MESG_KEY 1234L 

//メッセージの読み書きパーミッション
#define MY_MESG_PERMISSION 0666

//メッセージパケット型
typedef struct {

  //---- メッセージパケットの最初の変数は使っても使わなくても必ずlong intにする ----
  long int nMessageType;

  //---- それ以降の変数は任意に宣言できる ----
  char sMessage[32];

} MyMessagePacket;
      

マクロ"MY_MESG_KEY"は"Queue IDを生成するユニークキー"です。新しくMessage Queueを作りたい場合既に作成されているユニークキーと
被らないものを指定してください。 既に作成されたキーからメッセージパケットを取り出したい場合は同じユニークキーを使います。

"MyMessagePacket(メッセージパケット型)" は通信に使うためのデータをまとめた構造体で, 自分で型を宣言します
メッセージパケット型の最初の変数は使っても使わなくても必ずlong intにします

MsgQueueSend.c (メッセージ送信側)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MsgQueue.h"

void SendMessage(int);

int main(int argc, char *argv[]) {
  int i;

  //ユニークキーMY_MESG_KEYからメッセージキューを作成
  int nMesgQueueID = msgget(MY_MESG_KEY, MY_MESG_PERMISSION|IPC_CREAT);
  if (nMesgQueueID < 0) { 
    fprintf(stderr, "ERR:msgget\n"); 
    exit(1); 
  }

  //メッセージキューにメッセージ送信
  for (i=0; i<1; ++i) {
    SendMessage(nMesgQueueID);
    sleep(10);
  }

  //メッセージキュー削除
  if (msgctl(nMesgQueueID, IPC_RMID, NULL) < 0) { 
    fprintf(stderr, "Can't RMID queue?\n"); 
    exit(1); 
  }

  return 0;
}


void SendMessage(int nMesgQueueID) {

  MyMessagePacket mesgPacket;

  //パケットの中身を作成
  mesgPacket.nMessageType = 1; //--- 使わなくても1以上の値に初期化はする必要がある ---
  strncpy(mesgPacket.sMessage, "string", 7);

  //送信メッセージの大きさは MyMessagePacket型の最初のlong intを除いたサイズ分を指定する
  int nMesgLength = sizeof(mesgPacket) - sizeof(mesgPacket.nMessageType);

  //メッセージ送信
  if (msgsnd(nMesgQueueID, &mesgPacket, nMesgLength, IPC_NOWAIT) < 0) { 
    fprintf(stderr, "ERR:msgsnd\n"); 
  }

}
      
MsgQueueReceive.c (メッセージ受信側)

#include <stdio.h>
#include <stdlib.h>
#include "MsgQueue.h"

int ReceiveMessage(int nMesgQueueID);

int main(int argc, char *argv[]) {

  int nMesgQueueID = msgget(MY_MESG_KEY, 0);
  if (nMesgQueueID < 0) { 
    fprintf(stderr, "ERR:msgget\n"); 
    exit(1); 
  }

  while (ReceiveMessage(nMesgQueueID) == 0) {

    //メッセージキューが削除されていないか確認する
    nMesgQueueID = msgget(MY_MESG_KEY, 0);
    if (nMesgQueueID < 0) {
      fprintf(stderr, "Queue has been removed\n"); 
      exit(1);
    }

  }

  return 0;
}

int ReceiveMessage(int nMesgQueueID) {

  MyMessagePacket mesgPacket;
  int nMesgLength = sizeof(mesgPacket) - sizeof(mesgPacket.nMessageType);

  nMesgLength = msgrcv(nMesgQueueID, &mesgPacket, 
		       nMesgLength, 0, MSG_NOERROR);

  printf("message length : %d\n", nMesgLength);

  if (nMesgLength < 0) { 
    fprintf(stderr, "ERR;msgrcv\n"); 
    return 0; 
  }

  printf("sMessage : %s\n", mesgPacket.sMessage);

  return nMesgLength;
}
      

ターミナル1 (MsgQueueSend実行)

% ./MsgQueueSend
(キューにメッセージが送信される)
	      
  ターミナル2 (MsgQueueReceive実行)

% ./MsgQueueReceive
message length : 32
sMessage : string

(もう一度受信しようとしてみる)
% ./MsgQueueReceive

(メッセージはもう取り出されているので何も来ない.
  キューそのものが消えたら終了する)
message length : -1
ERR;msgrcv
Queue has been removed

	      
送信側のループ終了回数を2回に変更し, 2回メッセージが送信されるように変更したら
ターミナル1 (MsgQueueSend実行)

% ./MsgQueueSend
(キューにメッセージが2回送信される)
	      
  ターミナル2 (MsgQueueReceive実行)

% ./MsgQueueReceive
message length : 32
sMessage : string

(キューに送信された数だけ受信できる)
% ./MsgQueueReceive
message length : 32
sMessage : string
	      

2. 自動的に発生するシグナルによる一定時間ごとの処理

アラームを設定することによって、一定時間後にシグナルを自動送信することが出来ます
以下のプログラムでいろいろ実験してください。

アラームによるシグナル送信プログラム (AlarmTest)


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

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

int nSignalReceived = 0;
int nSignalPeriod = 5; //秒

int main(int argc, char *argv[]){

  int i;

  //秒指定のアラーム設定
  alarm(nSignalPeriod);

  //アラームシグナルのハンドラ設定
  signal(SIGALRM, SignalHandler);

  for (i=0; i<30; ++i) {

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

    if (nSignalReceived) {
      printf("signal received\n");
      nSignalReceived = 0;
    }

  }

  return 0;
}

void SignalHandler(int code) {

  nSignalReceived = 1;

  //アラーム再設定
  alarm(nSignalPeriod);
}
	
実行

    % ./AlarmTest
	
結果

    0
    1
    2
    3 
    4
    signal received
    5
    6
    .
    .
	


II. メッセージキューによるプロセス間通信(配点100%)

課題

ハノイの塔の状態を格納する構造体などを宣言したヘッダファイルが与えられています(以下参照)
ハノイの塔を解きながら一定時間ごとにメッセージキューに塔データを送信するプロセス1と,
メッセージキューから塔データを取り出し塔の状態を出力するプロセス2を担当する プログラムを作成してください。

ヘッダ : HanoiMsgQueue.h

ハノイの塔の円盤移動(Solve) (テンプレート)
ハノイの塔の状態を出力する(Display) (テンプレート)

:

ターミナル1 (プロセス1実行)

% ./HanoiMsgQueueSolve 30
(指定秒後)
Message sent
(指定秒後)
Message sent
	      
  ターミナル2 (プロセス2実行)

(送信側が自動的にキューにメッセージを送り その回数と同じだけ
 受信することができます)

% ./HanoiMsgQueueDisplay

(塔の中身省略)
----   ----   ----
  A      B      C
Number of Moves: 70885
Number of Disks: 30

% ./HanoiMsgQueueDisplay

(塔の中身省略)
----   ----   ----
  A      B      C
Number of Moves: 59790685
Number of Disks: 30