セマフォの謎:sem_openの予期せぬ挙動について
S・F・O
セマフォとUFOって何となく名前似ていますよね。 SFって入ってるし。
だから謎が多いのでしょうか......。
でもそういうの、嫌いじゃないですよね?
少なくとも私は.....。
目次
1. そもそもセマフォとは
2. 同じ名前の文字列をsem_openした時の挙動
3. おわりに
1. そもそもセマフォとは
セマフォとは
セマフォとは、コンピュータプログラムにおいて、複数のプロセスやスレッドが同時に実行されるときに、共有リソース(例えばファイルやデータ)へのアクセスを安全にコントロールするための仕組みです。
セマフォは日本語で「手旗信号」という意味を持ちますが、その動きは信号機ににています。
車が交差点で安全に通過するために、赤信号青信号で制御するような仕組みに似ています。
C言語のセマフォの関数
セマフォを操作するための基本的な関数は通常、以下の4つです。
1.sem_init: セマフォを初期化します。
2.sem_wait: セマフォを減らし(ロックし)、リソースを取得します。
3.sem_post: セマフォを増やし(アンロックし)、リソースの使用が終わったことを示します。
4.sem_destroy: セマフォを破棄し、関連するリソースを解放します。
まとめると
関数名 | 説明 |
---|---|
sem_init | セマフォを初期化し、指定された値でカウンタを設定します。 |
sem_wait | セマフォのカウンタを1減らします。カウンタが0の場合、リソースが解放されるまでブロックします。 |
sem_post | セマフォのカウンタを1増やし、他のプロセス/スレッドがリソースを使用できるようにします。 |
sem_destroy | セマフォを破棄し、それに関連するすべてのリソースをクリーンアップします。 |
また、セマフォの作成、削除については以下の関数があります。
関数名 | 説明 |
---|---|
sem_open | 新しいセマフォを作成するか、既存のセマフォをオープンします。 |
sem_close | セマフォをクローズしますが、システムからは削除しません。 |
sem_unlink | セマフォの名前をシステムから削除し、完全に破棄します。 |
詳しくは以下のmanに
sem_init
sem_wait
sem_post
sem_destroy
sem_open
sem_close
sem_unlink
セマフォの関数を使ったコード例
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
const char* semName = "/mySemaphore";
sem_t* sem;
// セマフォをオープン(存在しない場合は作成)
sem = sem_open(semName, O_CREAT, 0666, 1);
if (sem == SEM_FAILED) {
perror("sem_open failed");
exit(EXIT_FAILURE);
}
// セマフォを待機(デクリメント)。リソースが利用可能になるまで待機する
if (sem_wait(sem) < 0) {
perror("sem_wait failed");
exit(EXIT_FAILURE);
}
printf("リソースを安全に使用中...\n");
sleep(2); // リソース使用のシミュレーション
// セマフォを解放(インクリメント)
if (sem_post(sem) < 0) {
perror("sem_post failed");
exit(EXIT_FAILURE);
}
// セマフォをクローズ
if (sem_close(sem) < 0) {
perror("sem_close failed");
exit(EXIT_FAILURE);
}
// セマフォをシステムから完全に削除
if (sem_unlink(semName) < 0) {
perror("sem_unlink failed");
exit(EXIT_FAILURE);
}
return 0;
}
2. 同じ名前の文字列をsem_openした時の挙動
sem_open
sem_openは上のコードの通り、sem_waitやsem_post等の関数の引数であるセマフォを作るためにありますが、セマフォの作成にはsem_openを使う名前付きとsem_initを使う名前無しの二つのパターンがあります。
名前付きセマフォ
特徴: 名前付きセマフォは、システム全体で一意の名前を持ち、プロセス間で共有できます。
使用例: プロセスAがリソースを使用している間、プロセスBがそのリソースへのアクセスを待機する必要がある場合に使用します。
名前なしセマフォ
特徴: 名前なしセマフォは特定のプロセス内でのみ存在し、そのプロセスのスレッド間で共有されます。
使用例: 同一プロセス内の複数スレッドが同じメモリ領域にアクセスする際に使用し、データの整合性を保つために同期します。
ここで問題なのが、sem_openは名前で識別するため、名前が既に使われていたら、その既存のセマフォのオープンを行う、という点です。
つまり名前が同じなら、同じセマフォが使われるはずなのですが......。
以下のコードを見て下さい。これは自作した、食事する哲学者の問題
のコードの一部です。
while (i < number_of_philosophers)
{
// sem_name = ft_snprintf("/fs", i);
sem_name = ft_snprintf("/fs", 1);
printf("sem_name: %s\n", sem_name);
fork_sem = sem_open(sem_name, O_CREAT, 0666, 1);
if (fork_sem == SEM_FAILED)
exit(EXIT_FAILURE);
(*forks)[i] = fork_sem;
printf("fork_sem: %p\n", fork_sem);
sem_unlink(sem_name);
i++;
}
ft_snprintfはstrlcatのような関数です。
出力結果は
sem_name: /fs1
fork_sem: 0x3
sem_name: /fs1
fork_sem: 0x4
sem_name: /fs1
fork_sem: 0x5
sem_name: /fs1
fork_sem: 0x6
となります。
これは、sem_name = ft_snprintf("/fs", i);でも同じでした。
つまり名前が同じでも、異なる場合でも、作られるセマフォは増やされているのでした。
これはsem_unlinkの有無に関係ありませんでした。
また、sem_open の第4引数はセマフォの初期値(許容数)を指定し、これはセマフォが同時にアクセスできるリソースの数を表すのですが、これを1から2に変えると、制御できていたモノが出来なくなります。
結論
結果として、セマフォの名前が同じでも異なるアドレスが割り当てられ、許容数の変更が同期の動作に大きな影響を及ぼすことが観察されました。
これは、man(マニュアル)に書かれている事とから容易に創造出来る動作とは異なる動作で、セマフォの挙動とプログラムの同期メカニズムの理解は、実際に動かしてみたり、その中身を見てみないと分からないと思いました。
3. おわりに
プログラミングをやっていて、想定とは異なる場面に遭遇することがしばしばあります。
それが苦しさでもあり、楽しさでもあるのかも知れません。
狐の嫁入り、お天気雨かな。