simotin13's message

simotin13といいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

POSIX メッセージキューについて調べてみた

Unix系OSでのIPCの手段として提供されている機能の中にメッセージキューという機能があります。
この機能はプロセス間のデータの受け渡しに便利ですが、そもそもプロセス間通信とかたまにしか使わないので備忘のためまとめておきたいと思います。

メッセージキューとは

プロセス間通信の手法の1つです。

メッセージキューに関する注意点として、SystemVとPOSIXで2修理のメッセージキューのAPIが存在します。
ちなみにSystem-V版のメッセージキュー(msgget, msgrcv, msgsnd)は、詳解Unixプログラミング(第3版)では

「遅いしこれからは使わない方がいいぞ」

という扱いを受けています。

今回はPOSIXのメッセージキューについて紹介します。
RTOSitronだと任意のデータの受け渡しはメールボックス(snd_mbx,rcv_mbx)がよく使われると思いますが、POSIXのメッセージキューは似たような感じで使えます。

APIの簡単な説明

初期化

mq_open関数を使います。
注意点として O_CREAT を指定して作成する際はキューのパラメータ(キューサイズ、1メッセージのサイズ)を指定可能です。
指定しなかった場合は/procに記載されているデフォルト値で動作します。

送受信

mq_send, mq_receive 関数を使います。

キューの削除

ディスクリプタは mq_close でクローズします。

注意点として、mq_closeしただけではキューは削除されません。

使わなくなったキューはmq_unlinkの呼び出しによって削除する必要があります。
キューはカーネルが管理するので mq_unlink しなければメモリリークの要因になるので注意が必要です。
逆に言うと unlink しなければ同じキューを再利用できます。

メリットとデメリット

メリット

キューであること

文字通り「メッセージ」の「キュー」であることが保証されていることでしょう。
プロセス間通信の方法として提供されている、名前付きパイプやとsocketでも任意のデータの受け渡しが可能ですのでまぁこの点はメリットとしては少し弱い気もします。

優先度が指定できる

優先度付きキューであるので、他のプロセス間通信と違って優先度が必要な場合作りこみが不要*1

送信と受信の呼び出し順を意識しなくてよい

unlink されない限りはキューにメッセージが残ります。
カーネルでキューを管理しているので、送信側プロセスで送信してから、受信側プロセスを起動してもメッセージを受信できます。
ただしキュー自体が存在することが前提なので誰がキューを作成するのか(mq_open時のO_CREATの指定)は考えておく必要がある

送信側が待たされない

例えば名前付きパイプ(FIFO)は受信側のプロセスが送信側のwriteするまで待たされます。
本当に同期的に動作するプログラムなら名前付きパイプで問題ないかもしれませんがそのような動作を期待するケースというはあまりないように思います。
メッセージキューであれば送信側はブロックされないので非同期的に何かを実行したい場合は便利です。

送受信ともタイムアウトが指定できる

受信を行う関数には、 mq_receive, mq_timedreceive の2関数がありますが、 mq_timedreceive 関数では timespec 構造体によるタイムアウト時間の指定ができます。
特に受信側でタイムアウトが指定できるのはありがたいですね。

例えば、常駐プロセスで

  • 要求があれば要求に従って処理をする。
  • 要求がなければ定期的に何か別のことをする。

みたいなプロセスを作りたい場合に受信待ちをタイムアウトさせて実装することができます。*2

socketより気軽に使える

双方向のIPCはsocketでもできますが上記の通り、優先度の概念とタイムアウトの機能があるのでユーザーが実装するコードは減ります。
タイムアウトのために recv を select するのだるくないですか...

ソケットはストリームですが、メッセージキューはメッセージ(電文)なので事前にフォーマットを決めておけば終了コードの探索や受信データ長さのチェックは必要ないので気軽に扱うことができます。

デメリット

パフォーマンスの比較は他のIPCと試していないので何とも言えません。

C言語以外での実装があまりされていない

POSIXメッセージキューのAPIRubyPHPでは実装されていません。使いたくなったら自分で実装する必要があります。
Webシステムでアプリケーションサーバとなるプロセスとの連携とかには少し不便かもしれません。
*3

いろいろと見てるとGo言語だと実装されていました。*4
godoc.org

双方向性がない

メッセージキューはただのキューなので双方向性はありません。
要求の結果を受け取る場合などは送信した側でも受信を行うようなキューを用意することで解決する必要があります。

システムコールがデフォルトで有効化されていないことがある

WSLのUbuntuでは有効化されていませんでした。あと組み込みLinuxの標準BSPでは無効化されていることが多いようです。
有効化するにはカーネルコンフィグレーションが必要になりますので少し敷居が高くなってしまいます。

サンプルコード

送信するプログラムと受信するプログラムの例を挙げておきます。
サンプルでは、受信側でキューの作成を行っているので受信側プログラム(mq_recv_sample.c)を先に起動する必要があります。
*5

Linuxでの実装

Linuxでは、ipc/mqueue.c, ipc/mqueue.h で実装されています。
Linuxカーネルの機能として実装されているので、glibcではmq_xxx関数は未サポートのシステムコールエラーを返すような実装になっていますね。

ちらっと実装を見てみましたが、受信側プロセスが待ち状態にある場合はエンキューせずに直接メッセージを渡すという工夫(pipelined_send)がされていました。
同時に1メッセージしか受け取らない場合はパフォーマンスがよいのかもしれません。

まとめ

ということで、POSIXメッセージキューはプロセス間で相手方にシーケンシャルな要求を行うのに便利な機能です。

POSIXメッセージキューのようなプロセス間通信はカーネルが間に介在するため正しい実装方法が分かりにくいことが多いですが、Linuxプログラミングインターフェースには詳しい説明が記載されています。
追記したumaskについても触れられているのでLinuxで開発する人は手元に一冊置いておくと安心感があります。

追記

POSIXメッセージキューですがキュー作成するときにumaskによって権限が制約され、他のユーザー権限で動作するプロセスからはアクセスできない場合があります。*6その場合はmq_openする前に umask(0)することで回避できます。キュー作成後はマスク値はもとに戻しましょう。

追記2

キューのcloseをし忘れた状態で同じキューにmq_openし続けているとerrno 24が発生します。
このエラーはmq_open/mq_closeするプロセスが別であっても発生しきづきにくいです。

*1:優先度を作りこむのは多少なり手間はかかる

*2:itronのrcv_mbxでもこの手のことはよくやりますね

*3:PHPではなぜかSystemV版のメッセージキューが実装されています

*4:さすがGo言語、システムコールのサポートが充実してますね

*5:キューが既に作成されている状態であれば順番を意識する必要はありません

*6:通常umaskの初期値は2になっていることが一般的です