システムVメッセージキューによる双方向通信の実装
プロセス間で非同期にデータを交換する際、メッセージキューは排他制御と順序保証を簡素化します。以下の例では、ftokで生成した一意なキーを用いて共有キューを確保し、親子プロセスが異なるメッセージタイプ(10と20)を振り分けて相互にやり取りする設計を示します。文字列の末尾処理にはstrcspnを用い、改行文字の安全な削除を実現しています。
/* 接続ノードA.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct Packet { long category; char body[256]; };
#define BODY_SZ (sizeof(struct Packet) - sizeof(long))
int main(void) {
key_t route_key = ftok(".", "N");
int mq = msgget(route_key, IPC_CREAT | 0666);
if(mq < 0) { perror("生成失敗"); return 1; }
pid_t c = fork();
if(c > 0) {
struct Packet out;
while(1) {
memset(out.body, 0, sizeof(out.body));
printf("[親/送信10] > ");
fgets(out.body, sizeof(out.body), stdin);
out.body[strcspn(out.body, "\n")] = '\0';
out.category = 10;
msgsnd(mq, &out, BODY_SZ, 0);
if(strcmp(out.body, "終了") == 0) break;
}
} else if(c == 0) {
struct Packet in;
while(1) {
memset(in.body, 0, sizeof(in.body));
msgrcv(mq, &in, BODY_SZ, 20, 0);
printf("[子/受信20]: %s\n", in.body);
if(strcmp(in.body, "終了") == 0) break;
}
exit(0);
} else {
perror("fork失敗");
}
return 0;
}
/* 接続ノードB.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct Packet { long category; char body[256]; };
#define BODY_SZ (sizeof(struct Packet) - sizeof(long))
int main(void) {
key_t route_key = ftok(".", "N");
int mq = msgget(route_key, IPC_CREAT | 0666);
if(mq < 0) { perror("生成失敗"); return 1; }
pid_t c = fork();
if(c > 0) {
struct Packet in;
while(1) {
memset(in.body, 0, sizeof(in.body));
msgrcv(mq, &in, BODY_SZ, 10, 0);
printf("[親/受信10]: %s\n", in.body);
if(strcmp(in.body, "終了") == 0) break;
}
} else if(c == 0) {
struct Packet out;
while(1) {
memset(out.body, 0, sizeof(out.body));
printf("[子/送信20] > ");
fgets(out.body, sizeof(out.body), stdin);
out.body[strcspn(out.body, "\n")] = '\0';
out.category = 20;
msgsnd(mq, &out, BODY_SZ, 0);
if(strcmp(out.body, "終了") == 0) break;
}
exit(0);
} else {
perror("fork失敗");
}
return 0;
}
タイマーシグナルを活用した周期処理のシミュレーション
シグナルハンドラ内で直接I/O処理を行うことは推奨されません。以下の実装では、SIGALRM受信時にフラグを更新するのみとし、メインループ内でそのフラグを監視して周期処理を制御する設計に変更しています。入力が行われるたびにタイマーをリセットすることで、対話型のイベント駆動モデルを再現します。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
volatile sig_atomic_t trigger = 0;
void alarm_hook(int sig) {
if (sig == SIGALRM) trigger = 1;
}
int main(void) {
signal(SIGALRM, alarm_hook);
alarm(3);
char cmd = 0;
printf("周期イベント監視を開始します(入力可能)\n");
while (1) {
if (trigger) {
trigger = 0;
printf("[システム] 周期イベントが発火しました\n");
alarm(3);
}
if (scanf(" %c", &cmd) == 1) {
printf("[ユーザ] 入力検知: %c\n", cmd);
alarm(3);
}
}
return 0;
}
プロセス間シグナル送信と制御フローの検証
シグナルを用いたプロセス間の制御は、古いsignal()よりもsigaction()を使用することで動作の予測可能性が向上します。以下の例では、親プロセスがSIGUSR2を待ち受けて継続実行し、子プロセスが所定時間後に制御要求を送信するシナリオを構成しています。ハンドラ内では安全な終了関数_exit()を用いて、プロセスツリーのクリーンアップを行っています。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
void sig_trap(int signo) {
if (signo == SIGUSR2) {
printf("[制御] 外部割り込みを受信、処理を終了します\n");
_exit(0);
}
}
int main(void) {
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_trap;
sigemptyset(&act.sa_mask);
sigaction(SIGUSR2, &act, NULL);
pid_t sub = fork();
if (sub > 0) {
while (1) {
printf("[メイン] 実行継続中...\n");
sleep(1);
}
} else if (sub == 0) {
sleep(4);
printf("[サブ] 親プロセスへ制御要求を送信\n");
kill(getppid(), SIGUSR2);
while (1) {
printf("[サブ] 独立稼働モード\n");
sleep(1);
}
} else {
perror("生成失敗");
}
return 0;
}