プロセス間通信-セマフォ

頭に浮かぶ最初の質問は、なぜセマフォが必要なのかということです。複数のプロセス間で共有されるクリティカル/共通領域を保護するための簡単な答え。

複数のプロセスが同じコード領域を使用していて、すべてが並行してアクセスしたい場合、結果は重複していると仮定します。たとえば、複数のユーザーが1つのプリンターのみ(共通/クリティカルセクション)を使用しているとします。たとえば、3人のユーザーが、同時に3つのジョブを指定した場合、すべてのジョブが並行して開始されると、1つのユーザー出力が別のユーザー出力とオーバーラップします。したがって、セマフォを使用してそれを保護する必要があります。つまり、1つのプロセスの実行時にクリティカルセクションをロックし、実行が完了するとロックを解除します。これは、あるジョブが別のジョブと重複しないように、ユーザー/プロセスごとに繰り返されます。

基本的にセマフォは2つのタイプに分類されます-

Binary Semaphores − 2つの状態0と1、つまりロック/ロック解除または使用可能/使用不可、ミューテックス実装のみ。

Counting Semaphores −任意のリソースカウントを可能にするセマフォは、カウントセマフォと呼ばれます。

5台のプリンターがあり(1台のプリンターが1つのジョブしか受け入れないと理解するため)、3つのジョブを印刷するとします。これで、3台のプリンター(各1台)に対して3つのジョブが与えられます。これが進行している間に再び4つの仕事が来ました。現在、使用可能な2つのプリンターのうち、2つのジョブがスケジュールされており、さらに2つのジョブが残っています。これらのジョブは、リソース/プリンターの1つが使用可能になった後でのみ完了します。リソースの可用性に応じたこの種のスケジューリングは、セマフォをカウントするものと見なすことができます。

セマフォを使用して同期を実行するには、次の手順に従います。

Step 1 −セマフォを作成するか、既存のセマフォに接続します(semget())

Step 2 −セマフォに対して操作を実行します。つまり、リソースを割り当て、解放、または待機します(semop())

Step 3 −メッセージキューで制御操作を実行します(semctl())

それでは、これをシステムコールで確認しましょう。

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

int semget(key_t key, int nsems, int semflg)

このシステムコールは、SystemVセマフォセットを作成または割り当てます。次の引数を渡す必要があります-

  • 最初の引数keyは、メッセージキューを認識します。キーは、任意の値にすることも、ライブラリ関数ftok()から取得できる値にすることもできます。

  • 2番目の引数nsemsは、セマフォの数を指定します。バイナリの場合は1であり、1のセマフォセットが必要であることを意味します。それ以外の場合は、必要なセマフォセット数のカウントに従います。

  • 3番目の引数semflgは、IPC_CREAT(セマフォが存在しない場合はセマフォを作成する)やIPC_EXCL(IPC_CREATでセマフォを作成するために使用され、セマフォがすでに存在する場合は呼び出しが失敗する)などの必要なセマフォフラグを指定します。権限も渡す必要があります。

Note −権限の詳細については、前のセクションを参照してください。

この呼び出しは、成功した場合は有効なセマフォ識別子(セマフォの以降の呼び出しに使用)を返し、失敗した場合は-1を返します。失敗の原因を知るには、errno変数またはperror()関数で確認してください。

この呼び出しに関するさまざまなエラーには、EACCESS(許可が拒否されました)、EEXIST(キューが既に存在しますが作成できません)、ENOENT(キューが存在しません)、ENOMEM(キューを作成するのに十分なメモリがありません)、ENOSPC(最大セット制限)があります。超過)など。

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

int semop(int semid, struct sembuf *semops, size_t nsemops)

このシステムコールは、System Vセマフォセットで操作を実行します。つまり、リソースの割り当て、リソースの待機、またはリソースの解放です。次の引数を渡す必要があります-

  • 最初の引数semidは、semget()によって作成されたセマフォセット識別子を示します。

  • 2番目の引数semopsは、セマフォセットで実行される操作の配列へのポインターです。構造は次のとおりです-

struct sembuf {
   unsigned short sem_num; /* Semaphore set num */
   short sem_op; /* Semaphore operation */
   short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};

上記の構造体の要素sem_opは、実行する必要のある操作を示します-

  • sem_opが–veの場合、リソースを割り当てるか取得します。このプロセスが割り当てることができるように、他のプロセスによって十分なリソースが解放されるまで、呼び出しプロセスをブロックします。

  • sem_opがゼロの場合、呼び出しプロセスは、セマフォ値が0に達するまで待機またはスリープします。

  • sem_opが+ veの場合、リソースを解放します。

例-

struct sembuf sem_lock = {0、-1、SEM_UNDO};

struct sembuf sem_unlock = {0、1、SEM_UNDO};

  • 3番目の引数nsemopsは、その配列内の操作の数です。

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

int semctl(int semid, int semnum, int cmd, …)

このシステムコールは、SystemVセマフォの制御操作を実行します。次の引数を渡す必要があります-

  • 最初の引数semidは、セマフォの識別子です。このIDは、semget()システムコールの戻り値であるセマフォ識別子です。

  • 2番目の引数semnumは、セマフォの数です。セマフォには0から番号が付けられます。

  • 3番目の引数cmdは、セマフォで必要な制御操作を実行するためのコマンドです。

  • タイプの4番目の引数unionsemunは、cmdに依存します。まれに、4番目の引数が適用されない場合があります。

ユニオンセムンをチェックしてみましょう−

union semun {
   int val; /* val for SETVAL */
   struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
   unsigned short *array; /* Buffer for GETALL and SETALL */
   struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};

sys /sem.hで定義されているsemid_dsデータ構造は次のとおりです。

struct semid_ds {
   struct ipc_perm sem_perm; /* Permissions */
   time_t sem_otime; /* Last semop time */
   time_t sem_ctime; /* Last change time */
   unsigned long sem_nsems; /* Number of semaphores in the set */
};

Note −その他のデータ構造については、マニュアルページを参照してください。

ユニオンセムン引数; cmdの有効な値は次のとおりです。

  • IPC_STAT− struct semid_dsの各メンバーの現在の値の情報を、arg.bufが指す渡された構造体にコピーします。このコマンドには、セマフォへの読み取り権限が必要です。

  • IPC_SET −構造体semid_dsが指すユーザーID、所有者のグループID、権限などを設定します。

  • IPC_RMID −セマフォセットを削除します。

  • IPC_INFO −arg .__ bufが指す構造体semid_dsのセマフォ制限とパラメーターに関する情報を返します。

  • SEM_INFO −セマフォによって消費されたシステムリソースに関する情報を含むseminfo構造体を返します。

この呼び出しは、渡されたコマンドに応じて値(負でない値)を返します。成功すると、IPC_INFOとSEM_INFOまたはSEM_STATは、セマフォに従って最も使用頻度の高いエントリのインデックスまたは識別子、GETNCNTの場合はsemncntの値、GETPIDの場合はsempidの値、成功した場合は他の操作の場合はGETVAL0のsemvalの値を返します。失敗した場合は1。失敗の原因を知るには、errno変数またはperror()関数で確認してください。

コードを見る前に、その実装を理解しましょう-

  • たとえば、子と親の2つのプロセスを作成します。

  • 共有メモリへの読み取り/書き込みプロセスの終了を示すために、主にカウンタおよびその他のフラグを格納するために必要な共有メモリを作成します。

  • カウンターは、親プロセスと子プロセスの両方によってカウントごとに増分されます。カウントは、コマンドライン引数として渡されるか、デフォルトとして使用されます(コマンドライン引数として渡されない場合、または値が10000未満の場合)。親と子の両方が共有メモリに同時に、つまり並行してアクセスすることを保証するために、特定のスリープ時間で呼び出されます。

  • カウンターは親と子の両方によって1ずつ増加するため、最終的な値はカウンターの2倍になります。親プロセスと子プロセスの両方が同時に操作を実行するため、カウンターは必要に応じてインクリメントされません。したがって、あるプロセスの完了とそれに続く他のプロセスの完全性を確保する必要があります。

  • 上記のすべての実装は、ファイルshm_write_cntr.cで実行されます。

  • カウンタ値がファイルshm_read_cntr.cに実装されているかどうかを確認します

  • 確実に完了するために、セマフォプログラムはファイルshm_write_cntr_with_sem.cに実装されています。プロセス全体の完了後(他のプログラムからの読み取りが行われた後)にセマフォを削除します

  • 共有メモリ内のcounterの値を読み取るための個別のファイルがあり、書き込みによる影響がないため、読み取りプログラムは同じままです(shm_read_cntr.c)

  • ある端末で書き込みプログラムを実行し、別の端末から読み取りプログラムを実行することをお勧めします。プログラムは書き込みと読み取りのプロセスが完了した後にのみ実行を完了するので、書き込みプログラムを完全に実行した後にプログラムを実行しても問題ありません。書き込みプログラムは、読み取りプログラムが実行されるまで待機し、実行が完了した後にのみ終了します。

セマフォのないプログラム。

/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);

   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();

   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   
   shmp->write_complete = 1;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Writing Done\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Writing Done\n");
   return;
}

コンパイルと実行のステップ

Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

それでは、共有メモリ読み取りプログラムを確認しましょう。

/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};

int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   int total_count;
   int cntr;
   int sleep_time;
   if (argc != 2)
   total_count = 10000;
   
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   
   /* Read the shared memory cntr and print it on standard output */
   while (shmp->write_complete != 1) {
      if (shmp->cntr == -1) {
         perror("read");
         return 1;
      }
      sleep(3);
   }
   printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   shmp->read_complete = 1;
   
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

コンパイルと実行のステップ

Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

上記の出力を観察すると、カウンターは20000になりますが、1つのプロセスタスクが完了する前に、他のプロセスも並行して処理しているため、カウンター値は期待どおりではありません。出力はシステムごとに異なり、実行ごとに異なります。1つのタスクの完了後に2つのプロセスがタスクを確実に実行するには、同期メカニズムを使用して実装する必要があります。

それでは、セマフォを使用して同じアプリケーションを確認しましょう。

Note −読書プログラムは同じままです。

/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20

struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   
   if (shmp == (void *) -1) {
      perror("Shared memory attach: ");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();
   
   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);
   
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   
   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   remove_semaphore();
   return 0;
}

/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   int semid;
   struct sembuf sem_buf;
   struct semid_ds buf;
   int tries;
   int retval;
   semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
   //printf("errno is %d and semid is %d\n", errno, semid);
   
   /* Got the semaphore */
   if (semid >= 0) {
      printf("First Process\n");
      sem_buf.sem_op = 1;
      sem_buf.sem_flg = 0;
      sem_buf.sem_num = 0;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Operation: ");
         return;
      }
   } else if (errno == EEXIST) { // Already other process got it
      int ready = 0;
      printf("Second Process\n");
      semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Semaphore GET: ");
         return;
      }
      
      /* Waiting for the resource */
      sem_buf.sem_num = 0;
      sem_buf.sem_op = 0;
      sem_buf.sem_flg = SEM_UNDO;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Locked: ");
         return;
      }
   }
   sem_buf.sem_num = 0;
   sem_buf.sem_op = -1; /* Allocating the resources */
   sem_buf.sem_flg = SEM_UNDO;
   retval = semop(semid, &sem_buf, 1);
   
   if (retval == -1) {
      perror("Semaphore Locked: ");
      return;
   }
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   shmp->write_complete = 1;
   sem_buf.sem_op = 1; /* Releasing the resource */
   retval = semop(semid, &sem_buf, 1);
   
   if (retval == -1) {
      perror("Semaphore Locked\n");
      return;
   }
   
   if (pid == 0)
      printf("SHM_WRITE: CHILD: Writing Done\n");
      else if (pid > 0)
      printf("SHM_WRITE: PARENT: Writing Done\n");
      return;
}
   
void remove_semaphore() {
   int semid;
   int retval;
   semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Remove Semaphore: Semaphore GET: ");
         return;
      }
   retval = semctl(semid, 0, IPC_RMID);
   if (retval == -1) {
      perror("Remove Semaphore: Semaphore CTL: ");
      return;
   }
   return;
}

コンパイルと実行のステップ

Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

次に、読み取りプロセスでカウンタ値を確認します。

実行手順

Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete