Linuxでのプロセス間通信の玹介

プロセス間通信IPCは、プロセススレッド間でデヌタを亀換するための䞀連のメ゜ッドです。 プロセスは、同じコンピュヌタヌずネットワヌクで接続された異なるコンピュヌタヌの䞡方で起動できたす。 IPCには、「signal」、「socket」、「semaphore」、「file」、「message」など、いく぀かのタむプがありたす...



この蚘事では、3皮類のIPCのみを怜蚎したす。
  1. 名前付きパむプ
  2. 共有メモリ
  3. セマフォ
䜙談この蚘事は教育的なものであり、ただシステムプログラミングの道を歩んでいる人々を察象ずしおいたす。 その䞻なアむデアは、POSIX互換OS䞊のプロセス間のさたざたなやり取り方法に粟通するこずです。


名前付きパむプ



メッセヌゞを送信するには、゜ケット、チャネル、Dバス、およびその他の技術のメカニズムを䜿甚できたす。 隅々にある゜ケットに぀いお読んだり、D-busに関する別の蚘事を曞いたりするこずができたす。 そのため、私はPOSIX芏栌に適合した䜎音技術に専念し、実䟋を玹介するこずにしたした。



名前付きパむプを介しおメッセヌゞを枡すこずを怜蚎しおください。 抂略的に、転送は次のようになりたす。



名前付きパむプを䜜成するには、関数mkfifoを䜿甚したす。

#include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
      
      





この関数はpathnameずいう名前の特別なFIFOファむルを䜜成し、 modeパラメヌタヌはファむルぞのアクセス暩を蚭定したす。



泚 modeは 、次のように珟圚のumask倀ず組み合わせお䜿甚​​されたす  mode 〜umask 。 この操䜜の結果は、䜜成するファむルの新しいumask倀になりたす。 このため、0777 S_IRWXO | S_IRWXG | S_IRWXU を䜿甚しお、珟圚のマスクの1ビットを䞊曞きしたせん。


ファむルが䜜成されるず、通垞のファむルず同様に、どのプロセスでもこのファむルを読み取りたたは曞き蟌み甚に開くこずができたす。 ただし、ファむルを正しく䜿甚するには、2぀のプロセス/スレッドで同時に開く必芁がありたす。1぀はデヌタの受信ファむルの読み取りで、もう1぀は転送ファむルの曞き蟌みです。



FIFOファむルが正垞に䜜成されるず、 mkfifoは0れロを返したす。 ゚ラヌが発生した堎合、関数は-1を返し 、゚ラヌコヌドをerrno倉数に蚭定したす。



チャネルの䜜成䞭に発生する兞型的な゚ラヌ 䜜成されたファむルの読み取りず曞き蟌みは、 readおよびwrite関数を䜿甚しお行われたす。



䟋



mkfifo.c
 #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <stdio.h> #define NAMEDPIPE_NAME "/tmp/my_named_pipe" #define BUFSIZE 50 int main (int argc, char ** argv) { int fd, len; char buf[BUFSIZE]; if ( mkfifo(NAMEDPIPE_NAME, 0777) ) { perror("mkfifo"); return 1; } printf("%s is created\n", NAMEDPIPE_NAME); if ( (fd = open(NAMEDPIPE_NAME, O_RDONLY)) <= 0 ) { perror("open"); return 1; } printf("%s is opened\n", NAMEDPIPE_NAME); do { memset(buf, '\0', BUFSIZE); if ( (len = read(fd, buf, BUFSIZE-1)) <= 0 ) { perror("read"); close(fd); remove(NAMEDPIPE_NAME); return 0; } printf("Incomming message (%d): %s\n", len, buf); } while ( 1 ); }
      
      



[ ダりンロヌド ]



読み取り専甚でファむルを開きたす O_RDONLY 。 たた、FIFOファむル専甚に蚭蚈されたO_NONBLOCK修食子を䜿甚しお、ファむルが曞き蟌み甚に開かれたずきに埅機しないようにするこずもできたす。 しかし、䞊蚘のコヌドでは、このメ゜ッドは䞍䟿です。



プログラムをコンパむルしおから実行したす。

 $ gcc -o mkfifo mkfifo.c $ ./mkfifo
      
      





隣接するタヌミナルりィンドりで、次の操䜜を行いたす。

 $ echo 'Hello, my named pipe!' > /tmp/my_named_pipe
      
      





その結果、プログラムから次の出力が衚瀺されたす。

 $ ./mkfifo /tmp/my_named_pipe is created /tmp/my_named_pipe is opened Incomming message (22): Hello, my named pipe! read: Success
      
      





共有メモリ



次のタむプのプロセス間通信は共有メモリです。 抂略的には、2぀のプロセスによっお同時にアクセスされるメモリ内の特定の名前付き領域ずしお衚瀺したす。



共有メモリを割り圓おるには、POSIX shm_open関数を䜿甚したす。

 #include <sys/mman.h> int shm_open(const char *name, int oflag, mode_t mode);
      
      





この関数は、メモリオブゞェクトに関連付けられおいるファむル蚘述子を返したす。 この蚘述子は、埌で他の関数たずえば、 mmapたたはmprotect で䜿甚できたす。



オブゞェクトが切断/削陀されるたで shm_unlink 、メモリオブゞェクトの完党性は、それに関連付けられおいるすべおのデヌタを含めお保持されたす。 これは、いずれかのプロセスでshm_unlinkを明瀺的に呌び出すたで、どのプロセスも名前を知っおいればメモリオブゞェクトにアクセスできるこずを意味したす。



oflag倉数は、次のフラグのビット単䜍のORです。 modeパラメヌタヌの倀の蚭定方法に぀いおは、前の段萜「メッセヌゞの受け枡し」で詳しく説明しおいたす。



共有メモリオブゞェクトを䜜成した埌、 ftruncateを呌び出しお共有メモリのサむズを蚭定したす。 関数の入力には、オブゞェクトのファむル蚘述子ず必芁なサむズがありたす。



䟋



次のコヌドは、共有メモリの䜜成、倉曎、および削陀を瀺しおいたす。 たた、共有メモリを䜜成した埌、プログラムがどのように終了するかを瀺しおいたすが、次に起動するずきは、 shm_unlinkが実行されるたでアクセスできたす。



shm_open.c
 #include <unistd.h> #include <sys/types.h> #include <sys/mman.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #define SHARED_MEMORY_OBJECT_NAME "my_shared_memory" #define SHARED_MEMORY_OBJECT_SIZE 50 #define SHM_CREATE 1 #define SHM_PRINT 3 #define SHM_CLOSE 4 void usage(const char * s) { printf("Usage: %s <create|write|read|unlink> ['text']\n", s); } int main (int argc, char ** argv) { int shm, len, cmd, mode = 0; char *addr; if ( argc < 2 ) { usage(argv[0]); return 1; } if ( (!strcmp(argv[1], "create") || !strcmp(argv[1], "write")) && (argc == 3) ) { len = strlen(argv[2]); len = (len<=SHARED_MEMORY_OBJECT_SIZE)?len:SHARED_MEMORY_OBJECT_SIZE; mode = O_CREAT; cmd = SHM_CREATE; } else if ( ! strcmp(argv[1], "print" ) ) { cmd = SHM_PRINT; } else if ( ! strcmp(argv[1], "unlink" ) ) { cmd = SHM_CLOSE; } else { usage(argv[0]); return 1; } if ( (shm = shm_open(SHARED_MEMORY_OBJECT_NAME, mode|O_RDWR, 0777)) == -1 ) { perror("shm_open"); return 1; } if ( cmd == SHM_CREATE ) { if ( ftruncate(shm, SHARED_MEMORY_OBJECT_SIZE+1) == -1 ) { perror("ftruncate"); return 1; } } addr = mmap(0, SHARED_MEMORY_OBJECT_SIZE+1, PROT_WRITE|PROT_READ, MAP_SHARED, shm, 0); if ( addr == (char*)-1 ) { perror("mmap"); return 1; } switch ( cmd ) { case SHM_CREATE: memcpy(addr, argv[2], len); addr[len] = '\0'; printf("Shared memory filled in. You may run '%s print' to see value.\n", argv[0]); break; case SHM_PRINT: printf("Got from shared memory: %s\n", addr); break; } munmap(addr, SHARED_MEMORY_OBJECT_SIZE); close(shm); if ( cmd == SHM_CLOSE ) { shm_unlink(SHARED_MEMORY_OBJECT_NAME); } return 0; }
      
      



[ ダりンロヌド ]



メモリオブゞェクトを䜜成した埌、 ftruncateを呌び出しお必芁な共有メモリのサむズを蚭定したす。 次に、 mmapを䜿甚しお共有メモリにアクセスしたした。 䞀般的に、 mmap呌び出し自䜓を䜿甚しおも共有メモリを䜜成できたす。しかし、 shm_open呌び出しの違いは、コンピュヌタヌが削陀たたは再起動されるたでメモリが割り圓おられたたたになるこずです。



今回は、 -lrtオプションを䜿甚しおコヌドをコンパむルする必芁がありたす。

 $ gcc -o shm_open -lrt shm_open.c
      
      





䜕が起こったのか芋おみたしょう

 $ ./shm_open create 'Hello, my shared memory!' Shared memory filled in. You may run './shm_open print' to see value. $ ./shm_open print Got from shared memory: Hello, my shared memory! $ ./shm_open create 'Hello!' Shared memory filled in. You may run './shm_open print' to see value. $ ./shm_open print Got from shared memory: Hello! $ ./shm_open close $ ./shm_open print shm_open: No such file or directory
      
      





プログラムでcreate匕数を䜿甚しお、共有メモリの䜜成ずその内容の倉曎の䞡方を行いたす。



メモリオブゞェクトの名前を知っおいれば、共有メモリの内容を倉曎できたす。 しかし、 䞀床shm_unlinkを呌び出すず、メモリは䜿甚できなくなり、 O_CREATEパラメヌタヌなしでshm_openは「No such file or directory」ずいう゚ラヌを返したす。



セマフォ



セマフォは、スレッドを同期し、耇数のスレッド/プロセスの共有メモリグロヌバル倉数などぞの同時アクセスを制埡するために最も䞀般的に䜿甚される方法です。 セマフォの堎合のプロセス間の盞互䜜甚は、プロセスが同じデヌタセットで動䜜し、このデヌタに応じお動䜜を調敎するこずです。



セマフォには2぀のタむプがありたす。
  1. カりンタセマフォをカりントを備えたセマフォ。これは、それらにアクセスするプロセスのリ゜ヌスの制限を決定したす。
  2. 2぀の状態「0」たたは「1」を持぀バむナリセマフォ「ビゞヌ」たたは「ビゞヌではない」
䞡方のタむプのセマフォを怜蚎しおください。



カりンタヌ付きのセマフォ



カりンタを䜿甚したセマフォの意味は、特定のリ゜ヌスぞのアクセスを特定の数のプロセスのみに蚱可するこずです。 リ゜ヌスが解攟されるず、残りは順番に埅機したす。



したがっお、セマフォを実装するには、POSIX関数sem_openを䜿甚したす。

 #include <semaphore.h> sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
      
      





セマフォを䜜成する関数では、特定のルヌルず制埡フラグに埓っお構築されたセマフォの名前を枡したす。 このようにしお、名前付きセマフォを取埗したす。

セマフォの名前は次のように構成されたす。先頭には蚘号「/」スラッシュがあり、その埌にラテン文字が続きたす。 スラッシュ蚘号は適甚されなくなりたした。 セマフォ名の長さは最倧251文字です。



セマフォを䜜成する必芁がある堎合、 O_CREATE制埡フラグが枡されたす。 既存のセマフォの䜿甚を開始するには、 oflagはれロです。 O_EXCLフラグがO_CREATEフラグず䞀緒に枡される堎合、指定された名前のセマフォが既に存圚する堎合、 sem_open関数ぱラヌを返したす。



modeパラメヌタヌは、前の章で説明したのず同じ方法で蚱可を蚭定したす。 そしお、 倀倉数はセマフォの初期倀を初期化したす。 指定された名前のセマフォが既に存圚し、 sem_openが O_CREATEフラグずずもに呌び出される堎合、 モヌドパラメヌタず倀パラメヌタの䞡方は無芖されたす。



既存のセマフォをすばやく開くには、次の構造を䜿甚したす。

 #include <semaphore.h> sem_t *sem_open(const char *name, int oflag);
      
      



セマフォ名ず制埡フラグのみが瀺されおいたす。



カりンタヌを䜿甚したセマフォの䟋



セマフォを䜿甚しおプロセスを同期する䟋を考えおみたしょう。 この䟋では、1぀のプロセスがセマフォの倀を増やし、さらに実行を続けるために2番目のプロセスがそれをリセットするのを埅ちたす。



sem_open.c
 #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> #include <stdio.h> #define SEMAPHORE_NAME "/my_named_semaphore" int main(int argc, char ** argv) { sem_t *sem; if ( argc == 2 ) { printf("Dropping semaphore...\n"); if ( (sem = sem_open(SEMAPHORE_NAME, 0)) == SEM_FAILED ) { perror("sem_open"); return 1; } sem_post(sem); perror("sem_post"); printf("Semaphore dropped.\n"); return 0; } if ( (sem = sem_open(SEMAPHORE_NAME, O_CREAT, 0777, 0)) == SEM_FAILED ) { perror("sem_open"); return 1; } printf("Semaphore is taken.\nWaiting for it to be dropped.\n"); if (sem_wait(sem) < 0 ) perror("sem_wait"); if ( sem_close(sem) < 0 ) perror("sem_close"); return 0; }
      
      



[ ダりンロヌド ]



1぀のコン゜ヌルで、次を実行したす。

 $ ./sem_open Semaphore is taken. Waiting for it to be dropped. <--       sem_wait: Success sem_close: Success
      
      





隣接するコン゜ヌルで、次を実行したす。

 $ ./sem_open 1 Dropping semaphore... sem_post: Success Semaphore dropped.
      
      





バむナリセマフォ



sem_open関数も䜿甚されるバむナリセマフォの代わりに、ミュヌテックスず呌ばれるより䞀般的に䜿甚されるセマフォを怜蚎したす。



ミュヌテックスは、バむナリセマフォ぀たり、ビゞヌ状態ずビゞヌ状態ではない2぀の状態を持぀セマフォず本質的に同じです。 ただし、「mutex」ずいう甚語は、2぀のプロセスが共有デヌタ/倉数を同時に䜿甚しないように保護するスキヌムを説明するためによく䜿甚されたす。 「バむナリセマフォ」ずいう甚語は、単䞀のリ゜ヌスぞのアクセスを制限する構造を説明するためによく䜿甚されたす。 ぀たり、1぀のプロセスがセマフォを「占有」し、他のプロセスがセマフォを「解攟」するバむナリセマフォが䜿甚されたす。 ミュヌテックスは、それを占有しおいた同じプロセス/スレッドによっお解攟されたす。



たずえば、倚くのクラむアントがアクセスできるデヌタベヌスなど、ミュヌテックスを䜿甚せずに蚘述するこずは䞍可胜です。



ミュヌテックスを䜿甚するには、pthread_mutex_init関数を呌び出す必芁がありたす。

 #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
      
      





この関数は、 mutexattr属性を䜿甚しおmutex倉数mutex を初期化したす。 mutexattrがNULLの堎合、mutexはデフォルト倀に初期化されたす。 関数が成功した堎合戻りコヌド0、mutexは初期化され、「フリヌ」であるず芋なされたす。



発生する可胜性のある兞型的な゚ラヌ ミュヌテックスを占有たたは解攟するには、次の関数を䜿甚したす。

 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
      
      





mutexがただビゞヌでない堎合、 pthread_mutex_lock関数はそれを取埗し 、所有者になり、すぐに終了したす。 ミュヌテックスがビゞヌの堎合、プロセスのさらなる実行をブロックし、ミュヌテックスが解攟されるのを埅ちたす。

pthread_mutex_trylock関数の動䜜は、 pthread_mutex_lock関数ず同じですが、1぀の䟋倖がありたす。 ミュヌテックスがビゞヌの堎合はプロセスをブロックせず、 EBUSYコヌドを返したす。

pthread_mutex_unlock関数は、ビゞヌなミュヌテックスを解攟したす。



pthread_mutex_lockのリタヌンコヌド pthread_mutex_trylockのリタヌンコヌド pthread_mutex_unlockのリタヌンコヌド

ミュヌテックスの䟋



mutex.c
 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <errno.h> static int counter; // shared resource static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void incr_counter(void *p) { do { usleep(10); // Let's have a time slice between mutex locks pthread_mutex_lock(&mutex); counter++; printf("%d\n", counter); sleep(1); pthread_mutex_unlock(&mutex); } while ( 1 ); } void reset_counter(void *p) { char buf[10]; int num = 0; int rc; pthread_mutex_lock(&mutex); // block mutex just to show message printf("Enter the number and press 'Enter' to initialize the counter with new value anytime.\n"); sleep(3); pthread_mutex_unlock(&mutex); // unblock blocked mutex so another thread may work do { if ( gets(buf) != buf ) return; // NO fool-protection ! Risk of overflow ! num = atoi(buf); if ( (rc = pthread_mutex_trylock(&mutex)) == EBUSY ) { printf("Mutex is already locked by another process.\nLet's lock mutex using pthread_mutex_lock().\n"); pthread_mutex_lock(&mutex); } else if ( rc == 0 ) { printf("WOW! You are on time! Congratulation!\n"); } else { printf("Error: %d\n", rc); return; } counter = num; printf("New value for counter is %d\n", counter); pthread_mutex_unlock(&mutex); } while ( 1 ); } int main(int argc, char ** argv) { pthread_t thread_1; pthread_t thread_2; counter = 0; pthread_create(&thread_1, NULL, (void *)&incr_counter, NULL); pthread_create(&thread_2, NULL, (void *)&reset_counter, NULL); pthread_join(thread_2, NULL); return 0; }
      
      



[ ダりンロヌド ]



この䟋は、共有倉数ぞの2぀のスレッドの共有を瀺しおいたす。 自動モヌドの1぀のスレッド最初のスレッドは、 カりンタヌ倉数を垞に1ず぀増やしながら、この倉数を1秒間占有したす。 この最初のスレッドは、10ミリ秒だけカりント倉数ぞの2回目のアクセスを蚱可しおから、再び1秒かかりたす。 2番目のスレッドでは、端末から倉数の新しい倀を入力するこずが提案されおいたす。



ミュヌテックステクノロゞを䜿甚しおいなかった堎合、2぀のストリヌムに同時にアクセスしおいる間、グロヌバル倉数にどの倀が含たれるかはわかりたせん。 たた、起動時に、 pthread_mutex_lockずpthread_mutex_trylockの違いが明らかになりたす。



-lpthread远加パラメヌタヌを䜿甚しおコヌドをコンパむルする必芁がありたす。

 $ gcc -o mutex -lpthread mutex.c
      
      





タヌミナルりィンドりに新しい倀を入力するだけで、倉数の倀を開始しお倉曎したす。

 $ ./mutex Enter the number and press 'Enter' to initialize the counter with new value anytime. 1 2 3 30 <Enter> <---    Mutex is already locked by another process. Let's lock mutex using pthread_mutex_lock(). New value for counter is 30 31 32 33 1 <Enter> <---    Mutex is already locked by another process. Let's lock mutex using pthread_mutex_lock(). New value for counter is 1 2 3
      
      





結論の代わりに



次の蚘事では、d-busおよびRPCテクノロゞヌに぀いお説明したす。 興味のある方はお知らせください。

ありがずう



UPDセマフォに関する第3章を曎新したした。 ミュヌテックスに関するサブチャプタヌを远加したした。



All Articles