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になっていることが一般的です

RV32C ~圧縮命令ってなんだ?~

モナリザ本の第7章を読んでみました。

RISC-V原典  オープンアーキテクチャのススメ

RISC-V原典 オープンアーキテクチャのススメ

前回の記事でRISC-Vのアセンブラについて調べていて気になっていた圧縮命令について書かれた章です。章といいつつこの章は実は参考文献のリストなどをあわせても7ページしかありません。

RISCのデメリット

RISC系のCPUは命令セットがシンプルな分アセンブラのコード量が多くなりがちです。シンプルな命令の組み合わせつつ、パイプラインを有効活用する事で命令あたりのクロックサイクルを少なくすることがRISCの戦略ということになりますが、命令を組み合わせる分、使用する命令数(サイズ)はCISC系と比べて多くなります。*1

使用する命令数(サイズ)が多くなるということは、テキストセクションのサイズが大きくなるということで、これは組込ソフトにおいてプログラム使用するROMのサイズが大きくなります。

マイコンの値段は内蔵ROMのサイズで変わってきますので、プログラム領域もデータ領域も(もちろん使用するメモリサイズも)小さいに越したことはありません。

前置きが長くなりましたが、ようするにRISCではプログラムサイズが多くなるというのがデメリットとして存在するわけですが、この問題に対するRISC-Vとしての解答がRV32Cの圧縮命令ということのようです。

圧縮命令の特徴

命令長が短い版の命令セットがあるという点ではARMのThumb命令も同じですのであまり珍しく感じませんでした。

RISC-Vの圧縮命令の特徴は何かあるのでしょうか。
7章を読んでみて分かったのですが、この圧縮命令はアセンブラとリンカだけが意識し、コンパイラ開発者やアセンブリ言語プログラマは圧縮命令を意識する必要がないそうです。

お前は何を言っているんだ?


という気持ちになったのですが、要するに1つの命令のニーモニックは通常のRV32IでもRV32C(圧縮命令)でも共通になるということです。

気になったので改めて確認してみました。

実験

前回の記事、
mcommit.hatenadiary.com

でも少し触れましたが、同じli(load immediate)命令であってもエンコードのされ方に違いがあります。
asコマンドでアセンブリした場合、


$ cat tmp.s
li a0,0
$ riscv64-unknown-elf-as tmp.s # a.out が生成される
$ riscv64-unknown-elf-objdump.exe -S a.out
a.out: file format elf64-littleriscv

Disassembly of section .text:

0000000000000000 <.text>:
0: 00000513 li a0,0

00000513という32bitエンコードされています。

では、C言語で同様のアセンブリを期待したコードを書いて試してみます。


$ cat tmp.c
int return_zero(void)
{
return 0;
}

$ riscv64-unknown-elf-gcc tmp.c -c -O1
$ riscv64-unknown-elf-objdump.exe -S tmp.o

tmp.o: file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 :
0: 4501 li a0,0
2: 8082 ret

C言語コンパイラを通すとなんと16bitエンコードされています。
*2

コンパイラ開発者・アセンブリプログラマは圧縮命令を意識しなくてよい

というのはつまりこういうことなんだと思います。
ここでは同じ li a0, 0という命令が16bitにも32bitにもエンコードされました。

これはありがたい反面、コンパイラ(アセンブラ)の実装によっては思わぬトラブルを生み出しそうな気もします。
組込ソフト開発の場合、書いたコードがどういったバイナリになっているかをシビアに意識しないといけないケースもありますので出来れば明示的にコントロールしたいところです。

一般的にはこういった場合コンパイラに対するオプションとかで指定できたりすると思うのですが、gccの場合どう対応しているのでしょうか。まだそのあたりは調べれていませんが気になります。

*1:あと速度最適化の選択肢が少なくなると思います

*2:コンパイル時に-O1をつけましたがこれは最適化を付けないと冗長なアセンブラが吐かれるためです。-O1なしでも16bitでエンコードされます

RISC-Vのアセンブラについて調べてみた

昨日はFreedom Studioでデバッグする方法について書きましたが今日はRICV-Vに慣れるためにレジスタアセンブラについて少し調べてみました。
mcommit.hatenadiary.com

概要

HiFive1 Rev.Bで使用されているCPUはFE310-G002。命令セットはRV32IMACというISAらしい。
名前を知ったところで現時点では何も分からない。

よく出てくる命令

とりあえずCのコードの逆アセを眺めて出てきた命令を列挙してみました。

命令 意味(英語) 処理
jal Jump and Link 関数呼び出し
add Add 加算(レジスタ)
addi Add Immediate 加算(直値)
sw store word メモリへのストア(word)
sd store double メモリへのストア(double)
li load Immediate レジスタへのロード

jal

X86のCALL、ARMのBL(Branch with Link)命令相当。
RISC-Vでは$raがARMのリンクレジスタに相当するらしい。

add, addi

レジスタを使った加算はadd。直値を使う場合はaddiになる。
レジスタか直値で命令が違うのはあまりみたことがなかった。
デコードするときに扱える値の範囲が変わるというメリットはあるかもしれない。

sw,sd

オフセット(レジスタ名)
例). 8($sp)
のような表記になります。一度覚えてしまうとわかりやすい表現方法のような気がします。

gcc の冗長さ

最適化をかけないと関数の入り口と出口で、使おうが使うまいが愚直にプロローグコードとエピローグコード(ローカル変数用領域の確保と解放)を出力しています。
また reutrn 0 や return 1 のような直値を返す関数であっても一度a5レジスタを介してa0に書き込みをしています...
MIPS流のお作法なのかもしれないがさすがに冗長ですよね...

なお、この状況なコードは -O1 の最適化をつけると一瞬にして素直なコードになりました。


直値のエンコードの謎

Cで0を返すコード(return 0;)をコンパイルしてみました。
0を返す機械語が4501になっていたので不思議に思い0~5までの直値を返す関数のアセンブラを見てみました。

int return_zero(void)
{
    return 0;
}

int return_one(void)
{
    return 1;
}

int return_two(void)
{
    return 2;
}

int return_three(void)
{
    return 3;
}

int return_four(void)
{
    return 4;
}

int return_five(void)
{
    return 5;
}

結果、

0000000000000002 <return_zero>:
   2:	4501                	li	a0,0
   4:	8082                	ret

0000000000000006 <return_one>:
   6:	4505                	li	a0,1
   8:	8082                	ret

000000000000000a <return_two>:
   a:	4509                	li	a0,2
   c:	8082                	ret

000000000000000e <return_three>:
   e:	450d                	li	a0,3
  10:	8082                	ret

0000000000000012 <return_four>:
  12:	4511                	li	a0,4
  14:	8082                	ret

0000000000000016 <return_five>:
  16:	4515                	li	a0,5
  18:	8082                	ret

0→4501を開始として、1:4505 2:4509 3:450d ... と直値は4ずつ増えてエンコードされているようです。
どこまでつづくのかは分かりませんが、ぱっと見た限りでは、4 * n + 1 がエンコードされた値になります。

ちなみに、関数の戻り値は a0 レジスタで受け渡しするようです。

圧縮命令ってなんだ?


li a0, 1

を tmp.sというファイルに保存して、riscv64-unknown-elf-asでアセンブルしてみました。objdumpで見てみると、上記のC言語からコンパイルしたコードとは違うコードが生成されました。
上記のC言語からコンパイルしたアセンブラでは16bitでエンコードされています。
ところがアセンブラエンコードされたコードは同じニーモニックにも関わらず 4byteでエンコードされています。

調べてみるとRISC-Vには圧縮命令という命令セットがあり、短い命令長でエンコードされるようです。雰囲気的にはARMのThumb命令と似たような感じでしょうか...
ただし、ARMの場合は命令のアラインメント位置でThumb命令かどうかの判別ができますがRISC-Vの場合ぱっと見では32bit命令なのか圧縮命令なのか分かりませんね。

つまり、上記の

0000000000000002 <return_zero>:
   2:	4501                	li	a0,0
   4:	8082                	ret

のような2byteでエンコードされた命令は圧縮命令になります。

見比べてみた限りでは、li命令は 32bit命令では 0513 が下位ワードに来るようですので1ワードの特定のビットでスムーズにデコードできるようになっているんだと思います。
圧縮命令は4501や4505なので14bit目のようにも見えます。この辺は真面目に命令セットの仕様を見てみないとよくわかりません。

直値が4ずつ増えるのも圧縮命令と関係がありそうな気もします。

とりあえず手元のモナリザ本を開いてみると7章にRV32C:圧縮命令の章がありました。

RISC-V原典  オープンアーキテクチャのススメ

RISC-V原典 オープンアーキテクチャのススメ

今日は疲れたので、明日はこの章と第2章のRV32I:RISC-V基本整数ISAの章を読んでみたいと思います。

HiFive1 RevB を買った

SiFive社から販売されているHiFive1 RevBのボードを買いました。

f:id:simotin13:20190527001529j:plain
Hifive1 Rev.B

目次

  • 目次
  • 注文してから届くまで
    • 郵送について
  • 開発の始め方
  • デバッグ
    • フォーラムについて
  • 感想

注文してから届くまで

GW前になんとなく欲しくなってポチっていました。元々HiFive1の存在は知っていましたが単純にArduino互換ボードで開発の自由度が高そうではなかったのであまり興味がもてませんでした。
今回購入したHiFive1 Rev.Bはオンボードでデバッガが搭載されているのでRISC-Vアーキテクチャの勉強に使えそうです。

CrowdSupply order detail
CrowdSupplyの注文明細

注文履歴を見ると4/25に注文しています。届いたのは5/22なのでほぼ1か月かかっています。
といっても入荷日が5/9だったようなので実質的には2週間もあれば届くようですね。

郵送について

商品はUSPSで発送されます。
発送後はネットで最新の情報が見られるので、自分が注文した商品がいまどこにあるのかワクワクしながら届くのを待つことができます。
履歴を見ているとアメリカのオレゴン州ポートランドを出発して、成田に到着するまで約3日程度でした。*1
ここまではかなりスムーズに配達されているように感じたのですが、関税の関係のためか成田についてから何日も待たされます。

開発の始め方

ドキュメント類の入手

とりあえずマニュアルやら回路図やらを公式サイトからダウンロードします。
www.sifive.com

Getting Started Guide が提供されているのでまずはこれを読むのがよさそうです。
https://sifive.cdn.prismic.io/sifive%2F8d7b8385-64e3-4914-8608-8568412c8aae_hifive1b-getting-started-guide.pdf

この Getting Started Guide は開発環境や基板の概要について説明してくれています。全部で26ページとボリュームはないので一読しましょう。

Freedom E310-G002 Manualはチップのハードウェアマニュアルですね。開発環境の構築が終わったらじっくり読んでいきたいですね。*2
https://sifive.cdn.prismic.io/sifive%2F9ecbb623-7c7f-4acc-966f-9bb10ecdb62e_fe310-g002.pdf

*1:なぜか途中で逆方向のダラスに到着しているのを見たときは少し不安になりました

*2:116PしかないのでこちらもH/Wマニュアルにしては簡潔な気がしますが

続きを読む

MySQLサーバに外部から接続できないとき

自分用メモ。

MySQLサーバにホスト外から接続できず調べるのに時間がかかりましたのでメモを残しておきます。
ただし、これは開発環境として利用する場合の設定ですので、本番の環境としてはこのような設定はしないでください

ユーザー権限の設定

例えばrootユーザでどこからでも接続されたい場合は


grant all privileges on *.* to root@"%" identified by 'rooのpassword' with grant option;

mysqlにログインして実行します。

bind-addressの設定

さて、ユーザ設定をしたのはいいのですが外部から接続できません。
色々調べた結果、/etc/mysql/mysql.conf.d/mysqld.cnf に*1


bind-address = 127.0.0.1
の記載を変更してあげる必要がありました。

bind-address = 0.0.0.0

う~ん...bindアドレスも設定ファイルで制御できるようになっているんですね。
知りませんでした。


詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)

詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)

*1:/etc/mysql/mysql.conf.d/mysqld.cnfは私の使っている環境Linux Mint(Ubuntu系での例です)