Inter-process-communication-semaphores
プロセス間通信-セマフォ
頭に浮かぶ最初の質問は、なぜセマフォが必要なのかということです。 複数のプロセス間で共有されるクリティカル/共通領域を保護するための簡単な答え。
複数のプロセスが同じコード領域を使用しており、すべてが並行してアクセスしたい場合、結果は重複しています。 たとえば、複数のユーザーが1つのプリンター(共通/クリティカルセクション)のみを使用している場合、たとえば3人のユーザーが同時に3つのジョブを使用している場合、すべてのジョブが並行して開始されると、1人のユーザーの出力が別のユーザーの出力と重複します。 そのため、セマフォを使用してそれを保護する必要があります。つまり、1つのプロセスの実行中にクリティカルセクションをロックし、完了したらロック解除します。 これは、ユーザー/プロセスごとに繰り返され、1つのジョブが別のジョブと重複しないようにします。
基本的にセマフォは2つのタイプに分類されます-
バイナリセマフォ-0と1の2つの状態のみ、つまりロック/ロック解除または使用可能/使用不可のミューテックス実装。
カウントセマフォ-任意のリソースカウントを許可するセマフォは、カウントセマフォと呼ばれます。
5つのプリンターがあり(1つのプリンターが1つのジョブしか受け入れないと仮定するため)、3つのジョブを印刷すると仮定します。 これで、3台のプリンター(各1台)に対して3つのジョブが与えられます。 これが進行中に再び4つのジョブが来ました。 現在、使用可能な2つのプリンターのうち、2つのジョブがスケジュールされており、リソース/プリンターのいずれかが使用可能になった後にのみ完了する2つのジョブが残っています。 リソースの可用性に応じたこの種のスケジューリングは、カウントセマフォと見なすことができます。
セマフォを使用して同期を実行するには、次の手順があります-
- ステップ1 *-セマフォを作成するか、既存のセマフォに接続します(semget())
- ステップ2 *-セマフォで操作を実行します。つまり、リソースを割り当て、解放、または待機します(semop())
- ステップ3 *-メッセージキューで制御操作を実行する(semctl())
それでは、システムコールでこれを確認しましょう。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
このシステムコールは、System Vセマフォセットを作成または割り当てます。 次の引数を渡す必要があります-
- 最初の引数keyは、メッセージキューを認識します。 キーは、任意の値でも、ライブラリ関数ftok()から派生した値でもかまいません。
- 2番目の引数nsemsは、セマフォの数を指定します。 バイナリの場合、それは1であり、セマフォセットの数の必要なカウントに従って、1セマフォセットの必要性を意味します。
- 3番目の引数semflgは、IPC_CREAT(存在しない場合はセマフォを作成する)またはIPC_EXCL(IPC_CREATで使用してセマフォを作成し、セマフォが既に存在する場合は呼び出しが失敗する)などの必要なセマフォフラグを指定します。 許可も渡す必要があります。
注-権限の詳細については、前のセクションを参照してください。
この呼び出しは、成功した場合は有効なセマフォ識別子(セマフォの以降の呼び出しに使用)を返し、失敗した場合は-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, …)
このシステムコールは、System Vセマフォの制御操作を実行します。 次の引数を渡す必要があります-
- 最初の引数semidは、セマフォの識別子です。 このIDはセマフォ識別子であり、semget()システムコールの戻り値です。
- 2番目の引数semnumは、セマフォの数です。 セマフォには0から番号が付けられます。
- 3番目の引数cmdは、セマフォで必要な制御操作を実行するコマンドです。
- タイプの4番目の引数union semunは、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*/
};
注-その他のデータ構造については、マニュアルページを参照してください。
union semun arg; cmdの有効な値は-
- IPC_STAT -struct semid_dsの各メンバーの現在の値の情報を、arg.bufが指す渡された構造にコピーします。 このコマンドには、セマフォに対する読み取り権限が必要です。
- IPC_SET -ユーザーID、所有者のグループID、権限などを設定します。 構造体semid_dsが指す。
- IPC_RMID -セマフォセットを削除します。
- IPC_INFO -arg .__ bufが指す構造体semid_dsのセマフォの制限とパラメータに関する情報を返します。
- SEM_INFO -セマフォによって消費されたシステムリソースに関する情報を含むseminfo構造体を返します。
この呼び出しは、渡されたコマンドに応じて値(負でない値)を返します。 成功すると、IPC_INFOおよびSEM_INFOまたはSEM_STATは、セマフォごとに最も使用されているエントリのインデックスまたは識別子、GETNCNTのsemncntの値、またはGETPIDのsempidの値、または成功時の他の操作のGETVAL 0のsemvalの値を返します-失敗の場合は1。 失敗の原因を知るには、errno変数またはperror()関数で確認してください。
コードを見る前に、その実装を理解しましょう-
- 子と親という2つのプロセスを作成します。
- 主にカウンタやその他のフラグを保存して共有メモリへの読み取り/書き込みプロセスの終了を示すために必要な共有メモリを作成します。
- カウンターは、親プロセスと子プロセスの両方によってカウントごとに増分されます。 カウントは、コマンドライン引数として渡されるか、デフォルトとして使用されます(コマンドライン引数として渡されない場合、または値が10000未満の場合)。 特定のスリープ時間で呼び出され、親と子の両方が同時に、つまり並行して共有メモリにアクセスするようにします。
- カウンターは親と子の両方によって1ずつ増加するため、最終値はカウンターの2倍になります。 親プロセスと子プロセスの両方が同時に操作を実行するため、カウンターは必要に応じて増加しません。 したがって、1つのプロセスが完了してから他のプロセスが完了することを保証する必要があります。
- 上記のすべての実装は、ファイルshm_write_cntr.cで実行されます。
- カウンター値がファイルshm_read_cntr.cに実装されているかどうかを確認します
- 完了を保証するために、セマフォプログラムはshm_write_cntr_with_sem.cファイルに実装されています。 プロセス全体の完了後にセマフォを削除します(他のプログラムから読み取りが行われた後)
- 共有メモリ内のカウンターの値を読み取るための個別のファイルがあり、書き込みによる影響がないため、読み取りプログラムは同じままです(shm_read_cntr.c)
- 1つの端末で書き込みプログラムを実行し、別の端末からプログラムを読み取ることは常に優れています。 プログラムは、書き込みおよび読み取りプロセスが完了した後にのみ実行を完了するため、書き込みプログラムを完全に実行した後にプログラムを実行してもかまいません。 書き込みプログラムは、読み取りプログラムが実行されるまで待機し、完了後にのみ終了します。
セマフォのないプログラム。
/*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つのプロセスがタスクを確実に実行するには、同期メカニズムを使用して実装する必要があります。
次に、セマフォを使用して同じアプリケーションをチェックします。
注意-プログラムの読み取りは同じままです。
/*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