mcommit's message

ソフトウェア開発の仕事をしているsimotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

yoctoで共有ライブラリ(.so)を作るレシピを書いてみた

以前書いた記事、
mcommit.hatenadiary.com

ではBeagleBoneBlack用のイメージを作るのを試してみましたが今回はyocto を使って共有ライブラリ(.so)と共有ライブラリを参照する実行モジュールのビルド&イメージ化を試してみました。
組込Linuxの開発では(組込じゃなくても)共有ライブラリ(.so)を作って各実行モジュールでそれを呼び出すようなアプリケーションを開発することはよくあります。
ホスト環境で開発する場合は割と簡単ですが、できたプロジェクト一式をyoctoを使ってクロスコンパイルし、イメージ化する方法はあまり情報がなく、試行錯誤するのに割と時間がとられたので記事として挙げておきます。

とりあえず試したいという人はここを読んでください

この記事の内容を3分クッキング的におまとめしたレポジトリをGitHubに挙げております。

以下の手順で、環境構築~追加したレシピ(ライブラリ・実行モジュール)のビルド、イメージ作成までを試して頂けます。
※イメージでは追加した共有ライブラリは /usr/libに、実行モジュールは/usr/binに配置されます。

# yoctoで使用するツールのインストール
$ sudo apt install -y gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath libsdl1.2-dev xterm autoconf automake libtool libglib2.0-dev

# ホームディレクトリ/yocto に作業用ディレクトリを作成
$ cd
$ mkdir yocto
$ cd yocto

# 3.0 zeusのpokyを取得
$ git clone -b zeus git://git.yoctoproject.org/poky.git

# サンプルレイヤを取得
$ git clone git@github.com:simotin13/yocto-mysample-layers.git
   
# ビルド用ディレクトリ作成・bitbake 環境変数初期化
$ source ./poky/oe-init-build-env build_bbb

# bblayers.conf, local.conf の更新 
# bblayers.conf ではサンプルレイヤ(meta-calc)をbblayers.confに追加しています
# レイヤのパスは $HOME/yocto/octo-mysample-layers/meta-calcとしています 必要に応じて変更してください。
$ cp ../../yocto/yocto-mysample-layers/meta-calc/build_conf_sample/bblayers.conf conf/

# local.conf では ターゲットを MACHINE ?= "beaglebone-yocto" とし、実行モジュール(calc)をイメージに追加しています。
$ cp ../../yocto/yocto-mysample-layers/meta-calc/build_conf_sample/local.conf conf/

# 最小構成のイメージである core-image-minimalに組み込む
$ bitbake core-image-minimal

環境構築

yoctoを利用するには必要なツール類をインストールする必要があります。

ツールのインストールが完了したら、続けてyoctoのレポジトリを入手します。
本記事では Yocto Project Version 3.0 Zeusを使って説明します。
レシピの追加作業はターゲットとはあまり関係ありませんが local.confでは MACHINE ?= "beaglebone-yocto" を指定することを想定しています。
source ./poky/oe-init-build-env ディレクトリ名 でビルド用のディレクトリが作成され、bitbakeを使うための諸々の環境変数が設定されます。

ここでは 作業用ディレクトリとしてホームディレクトリ以下に yocto というディレクトリを作成して作業します。
また、ビルド用ディレクトリは build_bbb とします。

# yoctoで使用するツールのインストール
$ sudo apt install -y gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath libsdl1.2-dev xterm autoconf automake libtool libglib2.0-dev

# ホームディレクトリ/yocto に作業用ディレクトリを作成
$ cd
$ mkdir yocto
$ cd yocto

# 3.0 zeusのpokyを取得
$ git clone -b zeus git://git.yoctoproject.org/poky.git

# ビルド用ディレクトリ作成・bitbake 環境変数初期化
$ source ./poky/oe-init-build-env build_bbb

今回試すプロジェクトの構成

例としてint型の四則演算をするライブラリ(libcalc)とそれを使用するモジュール(calc)を例にイメージを作るために必要なことを説明していきます。
1ファイルずつのシンプルな構成ですが、多くのアプリケーションでもこのように

$ tree
.
├── calc
│   ├── Makefile
│   └── main.c
├── include
│   └── libcalc.h
└── libcalc
    ├── Makefile
    └── libcalc.c

のような構成で開発することが多いと思いますのでこの構成で基本をマスターすれば応用を利かせられるのではないかと思います。

各ファイルについて説明しておきます。

libcalcディレクト

このフォルダには計算ライブラリ用のソースコードMakefileを配置しています。

libcalc/libcalc.c
足し算・引き算・掛け算・割り算の関数を実装しています。

int add(int a, int b)
{
	return a + b;
}

int sub(int a, int b)
{
    return a - b;
}

int mul(int a, int b)
{
	return a * b;
}

int div(int a, int b)
{
	return a / b;
}


libcalc/Makefile

TARGET=libcalc.so
SRCS=libcalc.c

all:
	${CC} ${SRCS} -shared -o ${TARGET}

install:
	sudo cp ${TARGET} /usr/local/lib;
	sudo ldconfig;

clean:
	rm -f ${TARGET}

calcディレクト

このフォルダにはライブラリを呼び出す側(main関数)のソースコードMakefileを配置しています。
calc/main.c

#include <stdio.h>
#include "libcalc.h"

int main(int argc, char **argv)
{
	printf("1 + 2 = %d\n", add(1, 2));
	printf("3 - 1 = %d\n", sub(3, 1));
	printf("2 * 5 = %d\n", mul(2, 5));
	printf("9 / 3 = %d\n", div(9, 3));

	return 0;
}

calc/Makefile

TARGET=calc
SRCS=main.c

all:
	${CC} ${SRCS} -I../include -lcalc -o ${TARGET}

clean:
	rm -f ${TARGET}

includeディレクト

ライブラリ関数の公開用ヘッダファイルを配置しています。

include/libcalc.h

#ifndef _LIBCALC_H_
#define _LIBCALC_H_

extern int add(int a, int b);
extern int sub(int a, int b);
extern int mul(int a, int b);
extern int div(int a, int b);

#endif	// _LIBCALC_H_

注意すべきポイント

結論ですが、yoctoでビルドする場合はMakefileの書き方に注意が必要です。

Makefileを制する者がyoctoを制する

と言っても過言ではないのではないかと個人的には思っています。

bitbakeによるビルドプロセスは、終了コードが正常でなければ止まり、コンソール画面が真っ赤なエラー出力で埋め尽くされます。
セルフ開発用に書かれたMakefileだと恐らくそのままではyoctoで使えなくて、Makefileを書き直すか .bb側で回避する手順を書く必要が出てきます。

なのでMakefileでは想定外のエラーが起こらないような書き方をしておく必要があります。

具体的な例ですが、

  • clean ターゲットが do_configureで呼ばれるので 必ず clean は定義しておく必要がある
  • clean するときは rm -f hoge のように-fオプションをつけておかないと未ビルドの状態でcleanが走ってビルドがこける
  • ビルド手順は gcc hoge.c -o hoge みたいに書いちゃだめ。 ${CC} hoge.c -o hoge とか ${CXX} hoge.cpp -o hoge と書くこと*1
  • サフィックスルール使うとCFLAGSとかがうまく機能しないことあるので注意。*2

レイヤーの作成

レシピ化したいプロジェクトが決まれば、まずはレイヤーを作成します。
レイヤはどこのディレクトリで作成しても問題ないのですが、ビルド用ディレクトリ(ここではbuild_bbb)には含めないのが一般的のようです。
とりあえずyoctoディレクトリ直下に作成します。

# yocto ディレクトリに移動
$ cd ~/yocto
$ bitbake-layers create-layer meta-calc
NOTE: Starting bitbake server...
Add your new layer with 'bitbake-layers add-layer ../meta-calc'

bitbake-layersコマンドは 環境変数が設定された状態、つまり

>>||
source ./poky/oe-init-build-env ディレクトリ名
|

をたたいた後でないと実行できないので注意してください。


作成されたレイヤーは以下のような構成になっています。

$ tree meta-calc/
meta-calc/
├── COPYING.MIT
├── README
├── conf
│   └── layer.conf
└── recipes-example
    └── example
        └── example_0.1.bb

3 directories, 4 files

補足(古いのバージョンのyoctoでのレシピの追加)

以前のyoctoでは ./poky/scripts/yocto-layer create レイヤ名 でレイヤとhelloworldのサンプルプログラムを作ることができていたのですが最近のバージョンでは廃止されたようです。
yocto-layer createの方式がどのバージョンで廃止されたのか分かりませんが、2.0(Jethro)では作成できました。

$ ./poky/scripts/yocto-layer create sample
Please enter the layer priority you'd like to use for the layer: [default: 6] 
Would you like to have an example recipe created? (y/n) [default: n] y
Please enter the name you'd like to use for your example recipe: [default: example] 
Would you like to have an example bbappend file created? (y/n) [default: n] n

New layer created in meta-sample.

Don't forget to add it to your BBLAYERS (for details see meta-sample\README).

bitbakeがレイヤとレシピを認識する流れ

レイヤを作成しただけではbitbakeのビルド対象に含まれません。
イメージのビルド用ディレクトリ(build_bbb)内の conf/bblayers.conf の BBLAYERS変数にレイヤのパスを追加する必要があります。

以下はBBLAYERS に meta-calc を追加した conf/bblayers.confの例です。

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
POKY_BBLAYERS_CONF_VERSION = "2"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  /home/simotin13/yocto/poky/meta \
  /home/simotin13/yocto/poky/meta-poky \
  /home/simotin13/yocto/poky/meta-yocto-bsp \
  /home/simotin13/yocto/meta-calc \
  "

サンプルのレポジトリの設定では /home/simotin13/ を ${HOME}としています。


bblayers.confを編集することで、meta-calcレイヤを追加することができました。

正しく追加できたかどうかは bitbake-layers show-layers コマンドで確認できます。
レイヤのパスが間違ってたりするとエラーメッセージ表示されます。

$ bitbake-layers show-layers
NOTE: Starting bitbake server...
layer                 path                                      priority
==========================================================================
meta                  /home/simotin13/yocto/poky/meta            5
meta-poky             /home/simotin13/yocto/poky/meta-poky       5
meta-yocto-bsp        /home/simotin13/yocto/poky/meta-yocto-bsp  5
meta-calc             /home/simotin13/yocto/meta-calc            6

レシピをビルドしてみる

新規に作成したレイヤではexampleというレシピが含まれています。
レシピのビルドは bitbake レシピ名 でできます。
exampleレシピは何も生成しないのですが、bitbakeの標準の動作に従ってビルドプロセスが走るようです。

layer.conf について

bblayers.conf を編集してレイヤを追加する手順は分かりました。
ではレシピはどのようにbitbakeに認識されているのでしょうか?

レシピディレクトリ直下の conf/layer.conf にレシピを認識させるため設定が記述されています。

meta-calc/conf/layer.conf

# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"

# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
            ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "meta-calc"
BBFILE_PATTERN_meta-calc = "^${LAYERDIR}/"
BBFILE_PRIORITY_meta-calc = "6"

LAYERDEPENDS_meta-calc = "core"
LAYERSERIES_COMPAT_meta-calc = "warrior zeus"

BBFILES += に.bbや .bbappend ファイルを追加していますが、
要するにレイヤのディレクトリ以下のrecipes-xxx というディレクトリから2階層下のディレクトリ内にある拡張子が .bb というファイルをレシピとして追加しています。
実際に、ひな形で生成されるサンプルのレシピ(example)も /meta-calc/recipes-example/example/example_0.1.bb として作成されています。

layer.conf の記載は自由に変更できますが、ディレクトリの構成は基本的にこの構成に従って作成していきます。

レシピフォルダ名を変更する

recipes-example では何のレシピか分かりつらいのでディレクトリ名を recipes-exampleに変更します。(recipes-xxxの形であれば.bbファイルは読み込まれる)
ひな形として作成されているexampleディレクトリ以下のディレクトリも削除します。

cd meta-calc
mv recipes-example/ recipes-calc
rm  -rf recipes-example/example

続けて、calc以下に今回ビルドするプロジェクト一式を配置します。
meta-calc以下のディレクトリ構成は以下のようになります。

$ tree
.
├── COPYING.MIT
├── README
├── conf
│   └── layer.conf
└── recipes-calc
    └── calc
        ├── calc
        │   ├── Makefile
        │   └── main.c
        ├── include
        │   └── libcalc.h
        └── libcalc
            ├── Makefile
            └── libcalc.c

レシピファイル(.bbファイルを作る)

ディレクトリ構成はひとまずこれで準備できました。
後は.bbファイルを作成していくだけです。zeusで作成される.bbファイルは実質的に何もしないので参考になりません。
ひな形としては、yoctoの以前のバージョンで作成できていたhelloworldレシピの .bb ファイルが参考になります。

レシピファイル名のルール

レシピファイルには、

  • 拡張子は.bb
  • レシピ名の後に_0.1.bb のようにバージョンをつける*3

など基本的なルールがあるようです。
またパッチを充てる時などには.bbapendというファイルを別途書くのが作法らしいですが今回は触れません。

出来上がったレシピファイルを挙げておきます。

ライブラリ用レシピ libcalc_1.0.bb

SUMMARY = "Simple calc library"
SECTION = "libs"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

PACKAGES = "${PN}"
PROVIDES = "${PN}"

FILES_${PN} += "${libdir}/libcalc.so"
INHIBIT_PACKAGE_DEBUG_SPLIT = "1"

SRC_URI = "file://."
S = "${WORKDIR}"
TARGET_CC_ARCH += "${LDFLAGS}"

do_compile() {
        echo "WORKDIR:${WORKDIR}" >> ~/build.log
        echo "D:${D}" >> ~/build.log
        echo "S:${S}" >> ~/build.log
        oe_runmake
}

do_install() {
             install -d ${D}${libdir}
             install -m 0755 libcalc.so ${D}${libdir}
}

実行モジュール用レシピ calc_1.0.bb

SUMMARY = "Simple calc application"
SECTION = "applications"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://.."

S = "${WORKDIR}/calc"

DEPENDS = "libcalc"
RDEPENDS_${PN} = "libcalc"

TARGET_CC_ARCH += "${LDFLAGS}"

do_compile() {
        oe_runmake
}

do_install() {
        install -d ${D}${bindir}
        install -m 0755 calc ${D}${bindir}
}

レシピファイルの解説~正しく書かないとエラーの群れが襲ってくるよ~

レシピファイルの内容について説明してみます。
まずはライブラリ用のレシピファイルから順に中身を説明していきたいと思います。

SECTIONとLICENSE

SUMMARY は レシピのsummaryです。
SECTION は レシピの分類です。一応共有ライブラリは libs、実行モジュールは applications としてみましたがいまだにこの分類が何の役に立つのかよくわかりません。

LICENSE,LIC_FILES_CHKSUMは文字通りライセンス関係の変数です。
これらはきちんと定義されていないとビルドは通りません。とりあえずひな形のままとしています。

SRC_URI

SRC_URI はソースファイルあるいはフォルダのパスです。
yoctoではネット上からソースをダウンロードしてビルドしたりできるので git://hogeとか http://hoge みたいなスキームがあります。

ローカルにソース一式がある場合は file:// を使います。
パスの指定は、レシピ名に対応するディレクトリが基準になるようです。
yoctoのひな形レシピでは、

└── example
    ├── example-0.1
    │   ├── example.patch
    │   └── helloworld.c
    └── example_0.1.bb

のような構成上で、

SRC_URI = "file://helloworld.c"

と書かれています。

SRC_URI = "file://."

とするとexample-0.1ディレクトリ以下がビルド対象になります。
今回はローカル上にソースファイルを配置した状態でビルドするのでfile://のスキームで記述します。

ビルド時の動作とビルド対象のファイルについて

bitbakeでのビルドはどのように行われるのでしょうか?ここまでの手順でmeta-calcというレイヤディレクトリを作成し、ビルド対象のソースファイル等を配置しました。
では、ビルドはこのmeta-calc以下で行われて、ここに成果物が生成されるかというとそうではありません。

bitbakeでビルドが行われるとき、SRC_URIで指定されたディレクトリやファイルがビルド用の一時ディレクトリ(tmp/work以下にできる)にコピーされ、そこでビルドが行われます。*4

従って、SRC_URIの指定に含まれないディレクトリやファイルはビルド時には見つかりません。
※ただし標準ライブラリや標準インクルードヘッダは除く。これらはビルド用のsysroot以下に配置されていてビルド時には適切に参照されます。

話をlibcalcに戻すと、SRC_URI = "file://."と書くことで

の2ファイルがビルド実行時にビルド用ディレクトリにコピーされます。

SとmakeとWORKDIR

S = "${WORKDIR}"

の行がありますが、S は bitbakeがコンパイルを行うディレクトリです。
makeでコンパイルするプロジェクトの場合はMakefileが配置されている必要があります。

が設定されています。

WORKDIR はビルド用の一時ディレクトリです。
今回のケースではSRC_URIディレクトリとMakefileを配置しているディレクトリは一致していますが、もう少しディレクトリ階層が複雑な場合は指定してあげる必要があります。

ビルド時の手元の環境では、WORKDIRは

/home/simotin13/yocto/build_bbb/tmp/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0

になっていました。

PACKAGESとPROVIDES

PACKAGES = "${PN}"
PROVIDES = "${PN}"

パッケージ名・プロバイダ名の設定です。${PN}はレシピ名が設定されています。(ここではlibcalc)

PACKAGESとPROVIDESは指定しなくても標準的にレシピ名が設定されるようですが、ライブラリ側ではこのような指定しておかないと実行モジュール側から見つけてもらえないようなエラーが出ました。

ERROR: libcalc-1.0-r0 do_package_qa: QA Issue: -dev package contains non-symlink .so: libcalc-dev path '/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0/packages-split/libcalc-dev/usr/lib/libcalc.so' [dev-elf]
ERROR: libcalc-1.0-r0 do_package_qa: QA run found fatal errors. Please consider fixing them.
ERROR: Logfile of failure stored in: /home/simotin13/yocto/build_bbb/tmp/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0/temp/log.do_package_qa.13360
ERROR: Task (/home/simotin13/yocto/yocto-mysample-layers/meta-calc/recipes-calc/calc/libcalc_1.0.bb:do_package_qa) failed with exit code '1'

FILES

FILES_${PN} += "${libdir}/libcalc.so"

FILES は パッケージ化対象のファイルを指定する変数です。サフィックスとして${PN}を指定するのが一般的なようです。

指定しない場合、

ERROR: libcalc-1.0-r0 do_package: QA Issue: libcalc: Files/directories were installed but not shipped in any package:
  /usr
  /usr/lib
  /usr/lib/libcalc.so
Please set FILES such that these items are packaged. Alternatively if they are unneeded, avoid installing them or delete them within do_install.
libcalc: 3 installed and not shipped files. [installed-vs-shipped]
ERROR: libcalc-1.0-r0 do_package: Fatal QA errors found, failing task.
ERROR: Logfile of failure stored in: /home/simotin13/yocto/build_bbb/tmp/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0/temp/log.do_package.13887
ERROR: Task (/home/simotin13/yocto/yocto-mysample-layers/meta-calc/recipes-calc/calc/libcalc_1.0.bb:do_package) failed with exit code '1'

のようなエラーが出ます。

bitbakeのビルドプロセスではビルドが正常に通った後は、レシピをパッケージにするプロセス(do_package)が走ります。
このとき do_install でコピーしたファイルが指定されていないということでこのようなエラーが発生するようです。

bitbake「ビルドしたけどパッケージ化しなくていいの?とりあえずエラーにしとくね」
simotin13「あっ、ごめん FILES_${PN} 書くの忘れてた!」

的なことでしょう。

指定しないとエラーになるやつ(TARGET_CC_ARCHとINHIBIT_PACKAGE_DEBUG_SPLIT )

  • TARGET_CC_ARCH
TARGET_CC_ARCH += "${LDFLAGS}"

よくわかりませんが、この指定がないとエラーがでました。

ERROR: libcalc-1.0-r0 do_package_qa: QA Issue: No GNU_HASH in the ELF binary /home/simotin13/yocto/build_bbb/tmp/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0/packages-split/libcalc/usr/lib/libcalc.so, didn't pass LDFLAGS? [ldflags]
ERROR: libcalc-1.0-r0 do_package_qa: QA run found fatal errors. Please consider fixing them.
ERROR: Logfile of failure stored in: /home/simotin13/yocto/build_bbb/tmp/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0/temp/log.do_package_qa.12896
ERROR: Task (/home/simotin13/yocto/yocto-mysample-layers/meta-calc/recipes-calc/calc/libcalc_1.0.bb:do_package_qa) failed with exit code '1'

指定しておかないとLDFLAGSがちゃんと渡されないっぽいです。

  • INHIBIT_PACKAGE_DEBUG_SPLIT
INHIBIT_PACKAGE_DEBUG_SPLIT = "1"

こちらも指定しておかないとエラーが出ます。

ERROR: libcalc-1.0-r0 do_package: QA Issue: libcalc: Files/directories were installed but not shipped in any package:
  /usr/lib/.debug
  /usr/lib/.debug/libcalc.so
Please set FILES such that these items are packaged. Alternatively if they are unneeded, avoid installing them or delete them within do_install.
libcalc: 2 installed and not shipped files. [installed-vs-shipped]
ERROR: libcalc-1.0-r0 do_package: Fatal QA errors found, failing task.
ERROR: Logfile of failure stored in: /home/simotin13/yocto/build_bbb/tmp/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0/temp/log.do_package.13106
ERROR: Task (/home/simotin13/yocto/yocto-mysample-layers/meta-calc/recipes-calc/calc/libcalc_1.0.bb:do_package) failed with exit code '1'

stackoverflow.com

を読むと .debug というデバッグ関連のディレクトリが生成されるようです。
記事では do_installで生成されると書いてありますが、 do_installではなく、do_packageあたりのビルド成果物をimages ディレクトリに配置するタイミングっぽいです。*5

ということで .debugの生成を抑制するため指定しておきましょう。

タスクの処理の書き方(do_compile, do_install)

do_compile, do_install

do_compile, do_installはレシピ内で実行されるタスクです。

do_compileは、

do_compile() {
        oe_runmake
}

としていますが、bitbakeはデフォルトで Makefileをみつけてmakeを実行してくれるので、libcalcの場合は書かなくてもビルドが通ります。

do_installは

do_install() {
             install -d ${D}${libdir}
             install -m 0755 libcalc.so ${D}${libdir}
}

となっています。
${D}はイメージを作成するためのディレクトリでrootfsの構成になっています。ここにビルド結果をコピーします。
ただし、ルート(/)以下のディレクトリは存在しない状態になっているので

ビルド時の${D}は手元の環境では、

/home/simotin13/yocto/build_bbb/tmp/work/cortexa8hf-neon-poky-linux-gnueabi/libcalc/1.0-r0/image

となっていました。

処理内容ですが、

install -d ${D}${libdir}

ディレクトリを作成してあげています。
install コマンドは知りませんでしたが、文字通りinstallとかする時に使うコマンドです。-d オプションで mkdir -P 的なことをしてくれます。
${libdir} は /usr/lib が標準で設定されているそうです。

続けて実行する

install -m 0755 libcalc.so ${D}${libdir}

でビルド成果物である libcalc.so をコピーします。

libcalc_1.0.bbの内容はこんな感じです。

呼び出し側の実行モジュールのレシピファイルを見てみる

続いて、ライブラリを使う側のレシピファイルで注意すべき箇所を見てみます。

SECTION
SECTION = "applications"

何度もいいますが、このSECTIONの指定がどこでどのように役に立っているのかは謎です。

試しに

SECTION = "ちんちんブラブラ"

としてみましたがエラーにはなりませんでした。そこはエラーになって欲しかった...

SRC_URIとS

calcでのSRC_URIの書き方はlibcalcと異なり、

SRC_URI = "file://.."

としています。
1階層上のディレクトリからなのでcalcディレクトリの他、include,libcalcも含んでWORKDIRにコピーされます。
この書き方では.bbファイルもコピーされてしまいますしビルドには関係ないlibcalcもコピー対象に含んでしまいます。
本来であればもう一階層ディレクトリを分けるべきですが、レシピを作る前のプロジェクトのディレクトリをできるだけ変えないでビルドを通したかったのでとりあえず今回はよしとします。

ここで、SRC_URI = "file://.." としている理由ですが、includeディレクトリを含めたかったからです。
このため S変数の指定は、

S = "${WORKDIR}/calc"

とcalcディレクトリを明示的に指定しています。

DEPENDSとRDEPENDS_${PN}
DEPENDS = "libcalc"
RDEPENDS_${PN} = "libcalc"

の行があります。

あとはだいたいライブラリ側と似たような感じです。

レシピをビルドする

レシピができてbblayers.confにレイヤが追加できていれば

bitbake libcalc

bitbake calc

レシピのビルドが試せます。

イメージへの追加

上記の

bitbake libcalc
bitbake calc

ではレシピのタスクが実行されビルドはされますが、そのままでイメージに組み込まれるわけではありません。
自作したレシピの成果物をイメージに追加するには ビルドディレクトリの local.confに

IMAGE_INSTALL_append = " calc"

と記載する必要があります。

local.confの編集が終わればイメージを作成するレシピであるcore-image-minimalかcore-image-satoを指定すればイメージ作成のレシピの手順が実行されます。
私は手元の環境ではターゲットをMACHINE ?= "beaglebone-yocto"としてBeagleBoneBlack向けのイメージを作成し、SDカードに書き込んで試しています。

# イメージを作成するレシピの実行
bitbake core-image-minimal

参考

LICENSE, SECTIONの一覧について
https://ja.osdn.net/projects/openzaurus-ja/forums/9476/11329/

qiita.com

qiita.com

qiita.com

参考にさせて頂きましてありがとうございます。

参考書籍

Embedded Linux Projects Using Yocto Project Cookbook by Alex Gonzalez(2015-03-31)

Embedded Linux Projects Using Yocto Project Cookbook by Alex Gonzalez(2015-03-31)

  • 作者:Alex Gonzalez
  • 出版社/メーカー: Packt Publishing - ebooks Account
  • 発売日: 1895
  • メディア: 文庫
Cookbook ではChapter 4 Application Development Developing with librariesに共有ライブラリのビルドについて書かれていて参考になりました。
まぁ、書いてある通りに試してもエラーがいっぱい出ましたが...

まとめ

  • スムーズにビルドを通したければbitbakeに愛されるMakefileを書きましょう
  • bitbakeのエラーは意味不明
  • bitbakeのビルドは時間がかかるので人権のある環境で作業しましょう

感想

ソースコードやレシピを挙げて書いたのでかなり長い記事になってしまいました。間違っているところや他のやり方などあれば教えてください。

yoctoはたかだか共有ライブラリと実行モジュールをクロスコンパイルしてイメージを作るだけなのにやたらと覚えることが多いです。学習コストがかなり高くついている気がします。

学習もコストも大嫌いなのですが、組み込みLinuxではyoctoは避けて通れなくなってきている感があるので引き続き勉強していきたいです。

次は時間があればopenembeddedで提供されているレシピの使い方とかについてもまとめておきたいと考えています。

*1:ロスコンパイラが動かないので

*2:yoctoとは直接関係ないかも

*3:でも_0.1のようなバージョンは無くてもビルドできた...

*4:このためyoctoでビルドすると尋常じゃないサイズのディスクの肥やしが生み出される

*5:深くはおっていませんが

Hyper-Vの仮想マシンに"接続"できないときの対処法

タイトルの通りHyper-V仮想マシンに接続できなくなるという謎な現象が起こっていました。
仮想マシンを起動すると確かに起動はして普通に動いてはいるようですが、「接続」を選択しても仮想マシンの画面が表示されないのです。

つまり、仮想マシンのネットワークの設定ができていない状態だと手も足もでないという絶望的な状況です。

色々調べた結果、こちらの記事が参考になりました。

social.technet.microsoft.com

結論として、仮想マシンへの接続画面が表示されない場合は

C:\Windows\System32\vmconnect.exe

を起動すると接続用の画面が表示されるとのことです。
※もちろんHyper-Vを有効化しておく必要があります。

yoctoに入門してみる

ちょっと前にyoctoについて色々と調べる機会があったので理解している範囲で書いておきたいと思います。

yoctoを一言で説明すると?

yoctoはLinuxカーネルとユーザーランドのプログラムを含めてビルドするためのプロジェクトでしょうか。
元々はOpenEmbeddedというプロジェクトからスタートしたようですので、組込用のイメージを簡単にビルドすることをイメージしているんだと思います。

知っておくと何が嬉しいのか?

組み込みLinuxの開発をするときに役に立ちます。近年ではボードベンダーから提供されるBSP(Board Support Package)のビルドがyoctoを前提としていることが増えてきているようですのでむしろ避けては通れなくなるような気がしています。
ただし、自分でユーザーランドのアプリケーションを開発する場合は結局自分の為にレシピを書かないといけないので正直メリットはあまりないのではないかと思う今日この頃です。

概念と用語

基本的な概念

yoctoでは、レイヤーとレシピという概念が存在します。
レシピは文字通り、ビルドを行うのに必要な手順やソースコードの情報になります。ソースコードの場所やビルド時に必要な手順を .bb という拡張子のファイル書いておきます。レイヤーは複数のレシピをまとめたものです。このレイヤーとレシピの依存関係を解決してよしなにイメージをビルドしてくれるるのがyoctoを使うメリットになるそうです。
ユーザーランドのアプリケーションはmake/Makefileを使ってビルドするプロジェクトとして開発をすることが多いと思いますが、makeで生成する実行モジュールやライブラリのビルドをレシピとして管理する形になります。

poky

pokyとは何でしょうか?公式サイトには

Poky is a reference distribution of the Yocto Project®. It contains the OpenEmbedded Build System (BitBake and OpenEmbedded Core) as well as a set of metadata to get you started building your own distro. To use the Yocto Project tools, you can download Poky and use it to bootstrap your own distribution. Note that Poky does not contain binary files – it is a working example of how to build your own custom Linux distribution from source.

と書かれています。要するにビルドに必要なツール類やスクリプト等の一式との認識でよいかと思います。
pokyのレポジトリをcloneして入手します*1

bitbake

組み込み開発のような様々なターゲット向けにビルドを行うことを前提としたツールセットです。BitBakeは元々OpenEmbeddedの一部としてスタートしたそうです。現在の私の認識は、bitbakeはpokyで提供されるビルドに必要なコマンド群というイメージです。

実際にyoctoでビルドしてみよう

なんちゃってうんちくはこれくらいにして、実際に手を動かしてビルドして自作のイメージを作る流れを体験してみましょう!

ターゲット

BeagleBone Black用のイメージをビルドして実機で動作させてみました。
BeagleBone Blackはyoctoプロジェクトのリファレンスターゲットとして含まれています。
ちなみに標準だとQEMU用のイメージがビルドされますが、それだと組み込み感がないので...

環境構築

まずはビルドに必要なツールやライブラリをインストールします。

# essentials
sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath

# need for GUI
sudo apt-get install libsdl1.2-dev xterm

# need for build Application Development kit 
sudo apt-get install autoconf automake libtool libglib2.0-dev

pokyの入手

# ビルド用ディレクトリ作成とpokyの入手
$ mkdir yocto
$ cd yocto
$ git clone -b zeus git://git.yoctoproject.org/poky.git
$ source ./poky/oe-init-build-env build_bbb

yoctoのリリースとバージョンについて

Releases - Yocto Project

を見ると、yoctoは1年に1~2回程度は新しいバージョンがリリースされています。
バージョンによってコマンドなどで非互換というか機能が変わったりするものもあるようです。

私の経験上、ビルドしたいレシピがバージョンによってはすんなりとビルドが通らないバージョンもあるのでエラーにはまって原因調査に時間を取られるよりは違うバージョンを試した方が無難な気がします。

今回試したyoctoのバージョンは記事を書いている時点の最新版で、3.0 Zeus になります。

ちなみにボードベンダーのBSPを使って開発をするときは yoctoのバージョンも決まっていて、

git clone -b jethro git://git.yoctoproject.org/poky.git

のようにバージョンに対応するブランチを指定してpokyを入手することが多いかと思います。

ビルド用初期化

oe-init-build-envはビルド時に使用する環境変数等を設定するスクリプトになっています。

oe-init-build-env ビルド用ディレクトリ名

として実行します。
既に同名のディレクトリがあればそのディレクトリに移動します。
oe-init-build-envを実行すると、指定したディレクトリにビルドに関する設定ファイルが生成されます。
環境変数として、PATH,BUILDDIR,BB_ENV_EXTRAWHITEといった変数が追加・更新されます。
ビルド時にはbitbakeコマンドを実行しますが、bitbakeコマンドがあるディレクトリへのパスの追加もこの oe-init-build-env がやってくれています。

ビルドターゲットの変更

conf/local.conf を編集しビルドターゲットを指定します。

MACHINE ?= "beaglebone-yocto"

の行のコメントアウトを削除して有効にします。
ここでの MACHINE ?= の ?= は代入だけど、最後に指定されたされた ?= が有効になります。
また、 MACHINEに関して、

MACHINE ??= "qemux86-64"

という行がありますが、 ??=はMACHINEに何値が設定されていない場合に ??= で指定された値を設定するという意味です。
要するに、最後に書いておけばデフォルト値として使えるよということだと思います。
QEMUで試してみたいという人はこのconf/local.confの MACHINE を変更する必要はありません。

ビルドの実行

デフォルトのレシピとしていくつかターゲット用イメージをビルドするレシピが含まれていますが、各イメージの詳細はいまいちよくわかりません。

ここでは最小構成のイメージをビルドするcore-image-minimalをビルドしてみます。

bitbake core-image-minimal

実行するとしばらく時間がかかりますので放置しておきましょう。
といっても途中でビルドがエラー終了したりすることはよくあるので、初めてのビルドであれば時々進捗具合をした方がよいかもしれません。

私の手元では、core i7-870の古いPCをSSDへ換装し、メモリも16GBにしてビルドマシンとして使っていますが2~3時間程度でビルドは無事完了しました。*2

ちなみに、yoctoではビルド結果の情報を保持していて、2回目以降のビルドでは依存関係をチェックして必要なレシピのみビルドしてくれます。

ビルドの生成物はどこにできているのか?

ビルドが終わったら何をすればいいのでしょうか。そもそも成果物はどこにできているのでしょうか?
結論でいうと tmpディレクトリの中にできているのですが、大まかなディレクトリ構成についても触れておきたいと思います。

ディレクトリ構成

bitbakeでビルドしたときにできるディレクトリ構成についてみてみます。
ビルド用ディレクトリ(今回の場合 build_bbb)には、

  • cache
  • conf
  • downloads
  • sstate-cache
  • tmp

ディレクトリができています。このうち、confディレクトリとその中身は oe-init-build-env を実行した際に生成されます。

cache

cacheは文字通りビルドに必要な情報のキャッシュ用フォルダのようです。レシピファイルの解析だけでもちょっと時間がかかったりするのでビルド時間を短縮するためにできるだけキャッシュしておこうというのがyoctoのコンセプトのようです。

downloads

yoctoではレシピをビルドする際にビルドに必要なソースコードをインターネットから入手できます。
例えばあるプロジェクトの最新のソースをgithubからダウンロードしたり、特定のバージョンのtar.gzファイルを取ってきたりとかです。
downloadsはそういったネットから入手したファイルを保持しておくディレクトリです。このディレクトリのパスはlocal.confの DL_DIR で定義されており変更することもできます。

sstate-cache

このディレクトリの役割はよく分かっていません。sstateをキャッシュしてくれているんですよ、きっと。

tmp

皆さんお待ちかねのディレクトリです。ビルドプロセスで出来た中間生成物や最終生成物はこのディレクトリ以下に含まれています。
yoctoではクロスコンパイルする際に必要なクロスコンパイラのビルド自体がレシピとして含まれていますが、ビルドされたクロスコンパイラはworkディレクトリにできています。

ビルド成果物の流れとして、

  1. tmp/work以下にとりあえず成果物ができる。
  2. ROM焼きとかブート用SDへの書き込みに必要なファイルはtmp/workからtmp/deploy/images 以下に展開される

という流れになります。

SDカードへの書き込み

話を戻して、SDカードからイメージを起動されるため、ビルドしてできたファイルをSDカードへコピーしていきます。

SDカードの準備

BeagleBoneBlackで使用するSDカードはブートローラ用のパーティションとROOTFS用のパーティションの最低2つのパーティションが必要になります。

以下の用にパーティションを設定します。

パーティションラベル ファイルシステム サイズ 注意事項
BOOT FAT32(LBA) 32MB BOOTフラグを有効にすること
ROOT ext4 残り全てのサイズ -

ここではコマンドの詳細は省略しますが、fdiskを使って適切に設定してください。

パーティションを適切に設定できたら、マウント用ディレクトリを作成し、マウントします。

$ sudo mkdir /media/$USER/BOOT
$ sudo mkdir /media/$USER/ROOT
$ sudo mount /dev/sdd1 /media/$USER/BOOT
$ sudo mount /dev/sdd2 /media/$USER/ROOT

SDカードの2つのパーティションをマウントしたら必要なファイルをコピーしていきます。

BOOTパーティション

まずはBOOTパーティションからです。
BOOTパーティションは文字通りLinuxのブートに必要なファイルを配置するパーティションです。
MLOやU-BOOTのイメージ、デバイスツリー等のファイルをコピーしていきます。

$ cd yocto/build_bbb/tmp/deploy/images/beaglebone-yocto/
$ sudo cp MLO /media/$USER/BOOT
$ sudo cp u-boot.img /media/$USER/BOOT
$ sudo cp zImage /media/$USER/BOOT
$ sudo cp am335x-bone.dtb /media/$USER/BOOT/
$ sudo cp am335x-boneblack.dtb /media/$USER/BOOT/
U-BOOT用ファイルの配置

uEnv.txtをBOOTパーティションにおいておくことで、起動方法を細かく指示できます。このuEnv.txtはyoctoのビルドでは生成されないので自分で書く必要があります。

ブートに必要な情報を書き込んだらSDカードのBOOTパーティションに配置します。

$ echo "bootdir=/boot" >> uEnv.txt
$ echo "bootpart=0:2" >> uEnv.txt
$ sudo cp uEnv.txt /media/USER/BOOT/

これでBOOTパーティションの準備は完了です。

ROOTパーティション

ROOTパーティションLinuxのルート(/)以下として見えるディレクトリ構成です。yoctoのビルドではtar.bz2形式でrootfs一式が圧縮されていますのでROOTパーティション以下に展開します。

sudo tar -xf core-image-minimal-beaglebone-yocto.tar.bz2 -C /media/$USER/ROOT

これでSDカードの準備ができました。

最後にアンマウントします。

$sudo umount /dev/sdd1 
$sudo umount /dev/sdd2

SDカードからの起動

書き込みが完了したSDカードをBeagleBoneBlackに差し込み起動します。
起動中のログはシリアルコンソールに出力されるのでUSB-シリアル変換ケーブルが必要になります。
私は秋月電子で買ったケーブルを愛用しています。

写真のようにつないでください。
ケーブルから見て、オレンジ:TX 黄色:RX 黒:GNDです。
f:id:simotin13:20191104161756j:plain

f:id:simotin13:20191104161747j:plain

シリアルコンソールの通信設定は、

  • ボーレート:115200
  • データビット:8bit
  • ストップビット:1bit
  • パリティ:None

です。

ログを見てみると無事起動できたようなのでrootユーザーでログインしてみます。パスワードはありません。

INIT: Entering runlevel: 5
Configuring network interfaces... cpsw 4a100000.ethernet: initializing cpsw version 1.12 (0)
SMSC LAN8710/LAN8720 4a101000.mdio:00: attached PHY driver [SMSC LAN8710/LAN8720] (mii_bus:phy_addr=4a101000.mdio:00, irq=POLL)
udhcpc: started, v1.31.0
udhcpc: sending discover
udhcpc: sending discover
cpsw 4a100000.ethernet eth0: Link is Up - 100Mbps/Full - flow control off
IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
udhcpc: sending discover
udhcpc: sending select for 192.168.1.15
udhcpc: lease of 192.168.1.15 obtained, lease time 86400
/etc/udhcpc.d/50default: Adding DNS 192.168.1.1
done.
Starting syslogd/klogd: done

Poky (Yocto Project Reference Distro) 3.0 beaglebone-yocto /dev/ttyS0

beaglebone-yocto login: root
root@beaglebone-yocto:~# uname -a                                                                                                      
Linux beaglebone-yocto 5.2.17-yocto-standard #1 PREEMPT Mon Nov 4 01:49:00 UTC 2019 armv7l GNU/Linux
root@beaglebone-yocto:~# 

unameで見るとLinux beaglebone-yocto 5.2.17-yocto-standardとなっているので無事書き込めてそうです。

twitterを見てみると最初にBeagleBoneBlackで試したのは9月26日だったようです。記事を書くまで時間が空いてしまいました...


参考記事など

kiyomi2013.net

www.slideshare.net

qiita.com

上記、参考にさせて頂いた記事の皆様、ありがとうございます。


yoctoに関する日本語の書籍はないようですし、手を動かしてみないとだめですね。英語だと割と書籍が出ていて、私は以下の2冊を購入してみました。

Embedded Linux Projects Using Yocto Project Cookbook by Alex Gonzalez(2015-03-31)

Embedded Linux Projects Using Yocto Project Cookbook by Alex Gonzalez(2015-03-31)

  • 作者:Alex Gonzalez
  • 出版社/メーカー: Packt Publishing - ebooks Account
  • 発売日: 1895
  • メディア: 文庫

Using Yocto Project with BeagleBone Black (English Edition)

Using Yocto Project with BeagleBone Black (English Edition)

  • 作者:H M Irfan Sadiq
  • 出版社/メーカー: Packt Publishing
  • 発売日: 2015/06/30
  • メディア: Kindle

Cookbookの方は割と満遍なく色んな情報が載っています。Using Yocto Project with BeagleBone Blackは分かりやすいですが、少し情報が古いです。実際、最新のpokyではBOOTパーティション用のファイルが現状のpokyの内容と違っているのでこの本に書かれている通りに試すことはできません。

上記はamazonのリンクですが、packt本家のサイトで買った方が安いです。

www.packtpub.com

ちなみに月数ドル払うと、「ダウンロードはできないけど、読み放題」みたいなプランもあります。サブスクの時代ですね。

まとめ

書いてみるとかなり長い記事になりました。yoctoはLinuxのボードベンダーの人達にとってはメリットがあるかもしれませんが
、覚えることが多く学習コストが高いので、組込アプリケーション開発者には正直あまりメリットがないような気がしています。

Linuxを採用する理由って、「サポートはないけどとりあえず全部入りで、無償で簡単に開発が始められるから」というのがあると思いますが、そもそも開発する際の学習コストが高かったり、よく分からないところで頻繁にはまるようだとLinuxを採用する意味ってあまり無くなってくるんじゃないかと思います。

感想

ざっと手順を押さえるだけでも調べること(結構書くこと)があって疲れた...

続けてレシピの自作ネタの方法とMakefileのプロジェクトをレシピ化する記事も書こうと思います。

*1:私の中ではyocto=poxyみたいな認識があります。pokyを使わなかったらどうするんだろう

*2:気がついたら終わっていたので正確な時間が分かりません...

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

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

メッセージキューとは

プロセス間通信の手法の1つです。SystemVとPOSIXで2つメッセージキューが存在します。
ちなみにSystem-V版のメッセージキュー(msgget, msgrcv, msgsnd)は、詳解Unixプログラミング(第3版)では

詳解UNIXプログラミング 第3版

詳解UNIXプログラミング 第3版

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

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

今回はPOSIXのメッセージキューについて紹介します。
itronだと任意のデータの受け渡しはメールボックス(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の指定)は考えておく必要がある

送信側が待たされない

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

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

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

例えば、常駐プロセスで

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

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

socketより気軽に使える

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

デメリット

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

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

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

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

双方向性がない

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

サンプルコード

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

Linuxでの実装

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

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

まとめ

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

追記

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

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

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

*3:私はだるい。どうしても select するなら5000兆円欲しい...

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

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

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

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マニュアルにしては簡潔な気がしますが

続きを読む