System V IPC の概要
Linux におけるプロセス間通信(IPC)の一種である System V IPC には、主に以下の 3 つの仕組みが含まれます。
- メッセージキュー
- 共有メモリ
- セマフォ
これらのリソースはカーネルによって管理され、キー(key)値によって一意に識別されます。特徴として、これらは永続性を持ちます。つまり、生成したプロセスが終了してもリソースは消滅せず、明示的な削除操作が行われるまでシステム内に残存します。
カーネル内部では链表によってこれらのオブジェクトが管理されており、それぞれ固有の識別子(メッセージ队列 ID、共有メモリ ID、セマフォ ID)が割り当てられます。ユーザー空間のプログラムは、共通のキー値を使用することで、異なるプロセス間でも同じ IPC オブジェクトにアクセスすることが可能です。
システム上の IPC 状態を確認するには、ipcs コマンドを使用します。
メッセージキューの仕組み
メッセージキューは、あるプロセスから別のプロセスへデータブロックを送信するための機構です。送信される各データブロックにはタイプが付与されており、受信側プロセスは特定のタイプを持つメッセージを選択的に受け取ることができます。
他の通信手段との比較
シグナルとの比較
シグナルが通知程度の少量の情報しか運べないのに対し、メッセージキューは任意のサイズの自定义データを転送可能です。
パイプとの比較
名前付きパイプと同様に、メッセージキューも無関係なプロセス間での通信に利用できます。しかし、以下のような明確な違いがあります。
- データ形式:パイプは無形式のバイトストリームですが、メッセージキューは構造化されたデータ(レコード)を扱います。
- 読み出し方法:パイプは FIFO(先入れ先出し)順に読み取る必要がありますが、メッセージキューはメッセージタイプを指定してランダムに読み出すことが可能です。
- 永続性:プロセスが終了してもキューとその内容は保持されます。
- API:パイプが
write/readを使用するのに対し、メッセージキューはmsgsnd/msgrcvを使用します。
主要な API リファレンス
メッセージキューを操作するための主要なシステムコールは以下の通りです。
msgget:キューの生成または取得
新しいメッセージキューを作成するか、既存のキューの識別子を取得します。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
key:プロセス間で共通するキー値。IPC_PRIVATEを指定すると私有キューが作成されます。msgflg:アクセス権限や動作フラグ(IPC_CREAT,IPC_EXCLなど)。- 戻り値:成功時はキュー識別子(msqid)、失敗時は -1。
主なエラーコード:
EACCES:権限不足。EEXIST:キューが既に存在し、IPC_CREAT | IPC_EXCLが指定されている。ENOENT:キューが存在せず、IPC_CREATが指定されていない。
msgsnd:メッセージの送信
指定されたメッセージキューの末尾にデータを送信します。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgp:送信メッセージ構造体へのポインタ。最初のメンバーはlong型のメッセージタイプである必要があります。msgsz:メッセージ本文のサイズ(タイプフィールドを除く)。msgflg:0(ブロック)、IPC_NOWAIT(ノンブロック)など。
msgrcv:メッセージの受信
メッセージキューからデータを取り出します。受信したメッセージはキューから削除されます。
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgtyp:0:キュー内の最初のメッセージを受信。>0:指定したタイプと一致する最初のメッセージを受信。<0:指定した値以下のタイプを持つ最初のメッセージを受信。
msgflg:IPC_NOWAITなどを指定可能。- 戻り値:成功時は読み込んだバイト数、失敗時は -1。
msgctl:キューの制御
メッセージキューの状態取得や削除などを行います。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd:IPC_STAT:状態の取得。IPC_SET:属性の変更。IPC_RMID:キューの削除(ブロッキングしているプロセスはエラーを返す)。
実装例:送信側と受信側
以下に、メッセージキューを使用した簡単な通信プログラムの例を示します。送信プロセスはユーザー入力を受け付けてキューに送り、受信プロセスはキューからメッセージを読み出して表示します。
送信プログラム (sender.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#define MSG_KEY 0x7777
#define MAX_LEN 256
struct msg_buffer {
long m_type;
char m_text[MAX_LEN];
};
int main() {
int mq_id;
struct msg_buffer packet;
char input[MAX_LEN];
// キューの生成または取得
mq_id = msgget((key_t)MSG_KEY, IPC_CREAT | 0666);
if (mq_id == -1) {
perror("msgget failed");
exit(EXIT_FAILURE);
}
printf("Connected to queue ID: %d\n", mq_id);
while (1) {
printf("Input message (type 'end' to stop): ");
if (fgets(input, MAX_LEN, stdin) == NULL) {
break;
}
// 終了コマンドのチェック
if (strncmp(input, "end", 3) == 0) {
printf("Terminating sender.\n");
break;
}
packet.m_type = 1; // タイプを固定
strncpy(packet.m_text, input, MAX_LEN - 1);
packet.m_text[strcspn(packet.m_text, "\n")] = 0; // 改行削除
if (msgsnd(mq_id, (void *)&packet, strlen(packet.m_text), 0) == -1) {
perror("msgsnd failed");
exit(EXIT_FAILURE);
}
printf("Message sent.\n");
}
exit(EXIT_SUCCESS);
}
受信プログラム (receiver.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_KEY 0x7777
#define MAX_LEN 256
struct msg_buffer {
long m_type;
char m_text[MAX_LEN];
};
int main() {
int mq_id;
struct msg_buffer packet;
// キューの取得
mq_id = msgget((key_t)MSG_KEY, 0666);
if (mq_id == -1) {
perror("msgget failed");
exit(EXIT_FAILURE);
}
printf("Waiting for messages on queue ID: %d\n", mq_id);
while (1) {
// タイプ 1 のメッセージを受信
if (msgrcv(mq_id, (void *)&packet, MAX_LEN, 1, 0) == -1) {
perror("msgrcv failed");
exit(EXIT_FAILURE);
}
printf("Received: %s\n", packet.m_text);
// 終了メッセージがあればループを抜け、キューを削除
if (strncmp(packet.m_text, "end", 3) == 0) {
printf("End message received. Cleaning up queue.\n");
if (msgctl(mq_id, IPC_RMID, NULL) == -1) {
perror("msgctl failed");
}
break;
}
}
exit(EXIT_SUCCESS);
}
コンパイルと実行
それぞれのソースファイルをコンパイルし、別々のターミナルで実行します。
gcc -o sender sender.c
gcc -o receiver receiver.c
まず受信側を起動し、その後送信側でメッセージを入力すると、受信側のターミナルに内容が表示されます。送信側で「end」と入力すると、受信側はそれを検知してメッセージキューを削除し、両方のプロセスが終了します。