Linuxで、C言語を用いてセマフォを活用する方法を、記述します。
セマフォとは、プロセスやスレッドの同時実行を制御するための、OSで用意された機構です。あらかじめ同時実行できる数を指定し、それをセマフォの値の初期値とします(初期値が1の場合は、ほぼミューテックスと等しい動作になります)。セマフォによって同時実行が制限される処理を実行する場合、プロセスはセマフォから実行権を貰います。これをP操作と言います。逆に実行権を返すことをV操作と言います、もし他のプロセスによって、全ての実行権が取られていた場合は、他のプロセスのV操作により、自プロセスがP操作が出来るようになるまで待ちます。
ちなみにLinuxには、SystemVとPOSIXのセマフォがあり、両者は別物ですので、注意して下さい。両者の差異は勉強中です。(プロセス単位、スレッド単位?)
以下の例は、acceptor内で任意の数の子プロセスを生成し、子プロセスがtransport内の処理を実行するものです。transport内の処理を逐次処理とするために、セマフォを使用しております。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define PROCESS_MAX 5
// semctlのコマンド用(開発者がソース内で定義しなくては駄目)
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
void transport(int sem) {
int res = 0;
struct sembuf semb; // semop へ渡す設定内容の構造体
semb.sem_num = 0;
semb.sem_op = -1; // セマフォから権限を取得する
semb.sem_flg = SEM_UNDO;
res = semop(sem, &semb, 1);
printf("tansport started\n");
sleep(2);
printf("tansport finished\n");
semb.sem_num = 0;
semb.sem_op = 1; // セマフォから得た権限を返還する
semb.sem_flg = SEM_UNDO;
res = semop(sem, &semb, 1);
}
void acceptor() {
int res = 0;
int i = 0;
int sem;
union semun semcmd; // semctl へ渡すコマンド用の共用体
// セマフォを取得する
sem = semget(IPC_PRIVATE, 1, 0666|IPC_CREAT);
// セマフォに初期値を設定する(この値が、同時実行を許可する数になる)
semcmd.val = 1;
res = semctl(sem, 0, SETVAL, semcmd);
while(i < PROCESS_MAX) {
int pid;
pid = fork(); // fork システムコールで、子プロセス生成
switch(pid) {
case 0: // 子プロセスの場合に実行する
transport(sem);
exit(0); // 子プロセスはここで終了する
break;
}
// 親プロセスは処理を続行する
i++;
}
}
int main() {
acceptor();
}
本来は、acceptorの中で、前の子プロセスによるtransport処理が終了するまで、fork()システムコールの実行を待ち合わせるようにしたかったのですが、セマフォ操作を実行するプロセスが異なると、様々な制約が発生し、難しくなります。ある意味セマフォは一般的な同期処理を行なうために、安全な手法を提供してくれるのかもしれませんが、少し凝ったことを考えると、使いにくい印象を受けました。それこそ、共有メモリを使った方が、開発者の管理責任は増すものの、明らかに自由度は高くなります。
とにかく今回二日ほど試行してみて感じたことは、セマフォは特定処理の排他制御以外への利用は、なかなか難しいということです。