Introduction of Communication
agenda
WHOAMI
IPFactory 1年 @n01e0
普段はLinuxのカーネルモジュールを作ったりしています。
WHAT IS IPC
IPCとは Inter Process Communication の略称である。
プロセス間でのデータのやり取りを意味し、Linuxにおいては、様々な種類のIPCが用意されている。
本記事ではそれぞれの軽い紹介程度に留め、実装について詳細な説明は行わない。
各機能のおおまかな特徴を理解し、読者にとっての最適な選択が出来ることを目標としている為である。
IPCの分類
IPCはその機能により大きく3つに分類される
- 通信
- プロセス間のデータ通信、交換に関する機構
- 同期
- プロセス、スレッド間の同期に関する機構
- シグナル
- シグナルは特殊で、条件によっては同期にも通信にも用いることが出来る
IPCの仕様
IPCはUNIXの長い歴史の中でいくつかの規格に沿って実装されており、混在している。
主にSystem Vの実装とPOSIXの実装がある
全体的にPOSIXの方が扱いやすくなってはいるが、POSIXのIPCはカーネルバージョン2.6から実装されている為、可搬性には劣る。
SYSTEM V IPC と POSIX IPC
System Vにおいて、各IPCのデータ構造は、カーネル空間にあり、
プロセスがIPC資源(セマフォ、メッセージキュー、共有メモリリージョン)を要求した時に動的に生成される。
各IPC資源は固有の識別子を持ち。ユーザーは各IPCをそれとは異なるIPCキーで区別出来る。
この関係はファイルとファイルディスクリプタの関係に似ている。
しかし、ファイル異なる点として、プロセスが明示的に解放しない限り、メモリ上に存在し続ける。
同様の機能を提供するPOSIX IPCがあり、こちらはマルチスレッドセーフな設計になっている。
もう1つの大きな差異としては、POSIXでは各IPCに名前を付けて区別できる点がある。
命名には規則があり、/で始まり、/以外の1文字以上の長さのnull終端の文字列でなければいけない。
仕様に細かな違いはあるが、各IPCの概念で大きく異なるのは各オブジェクト(資源)の管理方法である
IPCの比較
System VとPOSIXの大きな違いとしてIPCオブジェクトの識別子、ハンドラがあり、これらを列挙する。
| IPC種別 | 識別子 | ハンドラ |
|---|---|---|
| パイプ | なし | ファイルディスクリプタ |
| FIFO | パス | ファイルディスクリプタ |
| System Vセマフォ | System V IPCキー | System V IPC ID |
| POSIX 無名セマフォ | なし | セマフォポインタ |
| POSIX 名前付きセマフォ | POSIX IPCパス | セマフォポインタ |
| System Vメッセージ | System V IPCキー | System V IPC ID |
| POSIXメッセージ | POSIX IPCパス | メッセージキューディスクリプタ |
| System V共有メモリ | System V IPCキー | System V IPC ID |
| POSIX共有メモリ | POSIX IPCパス | ファイルディスクリプタ |
| UNIXドメインソケット | パス | ファイルディスクリプタ |
| インターネットドメインソケット | IPアドレス ポート | ファイルディスクリプタ |
本記事で紹介するLinuxにおけるIPC
PIPE
パイプとは
日常、シェルで生活をしている読者にとっては非常に慣れ親しんだ概念だろう。
シェルにおけるパイプ(|)とは何なのか、改めて簡単に記すと
あるプロセスの出力を別のプロセスの入力につなげる
といった意味になる。
IPCとしてのパイプも同様、データの出力と入力を繋げる為に用いられる事が多い。
パイプのデータ構造について、何かを例に説明しようと思ったが、パイプ以外に良い例が浮かばない。
筒なので、入出力は先入れ先出しとなる。
まとめると、パイプとは単方向の先入れ先出し型データ構造である。
実装
unistd.hで定義されているpipe()システムコールは2つのファイルディスクリプタを生成する。
一方は読み出し用、もう一方は書き込み用となる。
これらは、ファイルディスクリプタではあるが、対応するパスは無い。
パイプに書き込まれたデータは、読み出されるまでカーネル内のデータ構造によってバッファリングされる。
通常、空のパイプに対してプロセスが読み込み(read())を行うと、そのパイプにデータが書き込まれるまで、読み込みは停止する。
FIFO
類似した機能としてFIFOが存在する。
名前付きパイプとも呼ばれ、また名前の通り先入れ先出しのデータ構造である
パイプと異なる点は、作られるファイルディスクリプタに名前(パス)があるという点くらい。
SIGNAL
これもまた、慣れ親しんだ概念だろう。
シグナルもIPCの一種であると言える。
シグナルには多くの種類があるので、一部を例にとって挙げると
SIGHUPSIGINTSIGQUITSIGKILLSIGSEGV
などがある。
シグナルは主に、プロセスに対し何かが発生したことを伝える時、プロセスに特定の動作をさせる時に用いられている。
シグナルの持つ情報量は少なく、通常は番号(種類)以外の情報は持たない。
また、受け取ったシグナルをどう扱うかはプロセス次第である。
対応
シグナルへの対応にはいくつかパターンがある
無視
一部を除き、受け取ったシグナルを無視する事もできる。
無視できない一部のシグナルとは
SIGKILLSIGSTOP
であり、これらのシグナルを受け取ったプロセスは、必ず標準設定動作を実行する。
標準設定動作
その標準設定動作とは、名前の通りLinuxにおいて標準で設定されている各シグナルに対する反応である。
プロセスの終了やダンプ、停止、標準で無視が設定されているシグナルもある。
シグナルハンドラ
標準設定動作以外にも、signal()を用いてユーザーが各シグナル毎の対応を定義できる。
実装
プロセスがプロセスに対し、シグナルを発行すると、シグナルはカーネルによって受け取られる。
カーネルは対象のプロセスが実行中かどうかを調べ、実行中でなかった場合にはシグナルを保留状態にする。
signal()システムコールは引数にシグナルの種類と対応するシグナルハンドラとなる関数のポインタを持ち、それらを対応づける。
SEMAPHORE
セマフォはIPCの分類の中でも、同期機構にあたる。
セマフォの実態は整数値である。
複数プロセスで共有する資源の保護の為、アクセス制御のカウンタとして割り当てられ
で構成される。
構成
整数型の変数
カウンタとなる。この値が資源の状態(使用可能か否か)を示す
操作待ち状態にあるプロセスのリスト
資源が使用可能になるまで待機しているプロセスのリスト
2つのアトミックなメソッド
セマフォのカウントアップ、ダウンを行う。
セマフォが保護している資源にプロセスがアクセスする時、まず対象資源に紐付けられたセマフォをカウントダウンする。
もしそのセマフォのカウントが負になった場合、プロセスの実行は中断され、リストに加えられる。
資源へのアクセスが終わると、プロセスはセマフォをカウントアップする。
これによりセマフォの値が正になると、リストのプロセスが再開される。
実装
セマフォ自身がプロセスの動作を制限する訳ではなく、セマフォに意味を持たせるのはあくまでユーザーである。
また、セマフォは同期機構である為、非常に複雑な操作を必要とする。
System V
semget()によりセマフォを作成、または開く。
semop()によってセマフォの操作を行う。
POSIX
POSIXでは、セマフォは2種類存在する
名前付きセマフォ
POSIXの他のIPC同様、セマフォに名前を付けて管理出来る。
名前を共有する事で、プロセス間でセマフォを共有する
メモリベースドセマフォ
共有メモリにマッピングされる。
名前を持たず、プロセス、スレッド間でメモリの共有によってセマフォを共有する。
MESSAGE
IPCメッセージキューは名前の通りキューである。
プロセスによって送信されたメッセージは、他のプロセスが読み込むまでメッセージキューに置かれる。
他のプロセスがメッセージを読み込むと、カーネルはメッセージをキューから削除する。
そのため、プロセス間でのメッセージのやり取りは必ず一対一となる。
実装
System V
メッセージとして送信されるデータは、必ずメッセージの優先度を整数値で指定する必要がある。
受信側は、優先度を指定してメッセージを受け取る。
(となると、先入れ先出しではなくなることもあるのでキューと呼んで良いものなのか)
msgget()を用いてキューを取得し、messnd()で送信、msgrcv()で受信する。
POSIX
POSIXメッセージキューも名前を付けて管理する。
受信の際、メッセージの優先度を指定する事は出来ない。
最も高い優先度の、最も古いメッセージが読み出される。
mq_open()でキューを開き、mq_sendで送信、mq_receiveで受信を行う。
SHARED MEMORY
IPC共有メモリ
名前の通り、共有するメモリ空間に特定のデータ構造を配置する。
また、他のIPCは実行時にユーザー空間からカーネル空間のデータ構造へデータを転送していたのに対し、共有しているメモリ空間へマッピングするだけなのでオーバーヘッドも小さい。
しかし、カーネルが介していないという点には留意すべき問題もある。
複数プロセスが同時にアクセスするのを防ぐ必要性があり、その為には先述のセマフォを用いる事が出来る。
実装
System V
-
shmget()により、共有メモリセグメントを作成、または既存セグメントのIDを取得 -
shmat()により、自プロセスのメモリ空間に共有メモリセグメントをアタッチ -
shmdt()により、デタッチする。
アタッチされた共有メモリセグメントはプログラム内の通常のメモリと何ら違いなく扱える。
共有メモリオブジェクトが破棄されるのは、すべてのプロセスがデタッチしshmctl()により削除された時である。
POSIX
-
shm_open()によって共有メモリオブジェクトをオープンする。また、この戻り値はファイルディスクリプタである。 - 得られたファイルディスクリプタに対し、
MAP_SHAREDフラグをセットしてmmap()する
SOCKET
ソケット。
ソケットはデータの転送方式によって2つに分類でき、
- ストリームソケット
- データグラムソケット
と呼ばれる。
ストリームソケットは名前の通りストリームとして扱え、ファイル等と同様、書き込んだサイズに関わらず任意のバイト数読み取れる。
データグラムソケットはメッセージ等と似ており、データに特定の区切りがあり、読み取りではデータ全体を読み取る必要がある。
また、ソケットのもう一つの分類として、通信先での分類もある
- UNIXドメインソケット
- インターネットドメインソケット
と呼ばれ、
UNIXドメインソケットは同一ホスト内でのアプリケーション間の通信を、インターネットドメインソケットではインターネットプロトコルで接続されたアプリケーション間の通信をサポートしている。
実装
ソケットの操作に関するシステムコールは
-
socket()- 新規ソケットを作成する
-
bind()- ソケットをアドレスに結びつける
-
listen()- ストリームソケットを他のソケットからの接続を受け付ける状態にする
-
accept()- 受け付け可能な状態のソケットに対する接続要求を取り出す
-
connect()- 他ソケットとの接続を確立する
ソケットについて、ここではごく一部しか紹介出来ないが、非常に多くの事が可能な概念である。
IPCのまとめとしての記事ではこの程度に留まるが、ソケットに関する記事は豊富にあるので必要になればそちらを参照していただきたい。
まとめ
それぞれの特徴、長所、短所を見極め、最適なIPCを用いられると便利。
自分の場合、ファイルパス等のやり取りにはPOSIXメッセージキューを用いている。
ダラダラと駄文を書き連ねてしまったが、必要に応じてそれぞれ実装のサンプルを含めてまた別でまとめたい。