みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

YoctoProject STM32MP157F-DK2を試す

はじめに

お年玉でSTM32MP157F-DK2を購入した。

この製品、Cortext-A7とM4ヘテロ構成のSoCでスペック的にはあまり強くなさそうなんだけど、 BSPをみると、なんだかコア間の連携のための仕組みが作り込まれててちょっとおもしろそう。 値段も15000〜16000程度なので、液晶パネル付きでこれなら楽しめるだろうと。

STMicroelectronicsはmeta-st-stm32mpを提供してくれているため、 YoctoProjectでLinuxを作成することができる。

kirkstoneブランチに対応しているので、とりあえず起動確認をしてみる。

環境構築

作業環境

$ mkdir -p ~/yocto/stm32mp15-kirkstone
$ cd ~/yocto/stm32mp15-kirkstone

pokyの取得

$ git clone -b kirkstone git://git.yoctoproject.org/poky.git

環境変数の設定

$ source poky/oe-init-build-env

meta-openembeddedの取得

meta-st-stm32mpはmeta-openembeddedの一部のレイヤに依存するため、先に取得しておく。

$ bitbake-layers layerindex-fetch meta-python

meta-st-stm32mpの取得

gitコマンドで取得する。

$ pushd ../poky
$ git clone https://github.com/STMicroelectronics/meta-st-stm32mp.git -b kirkstone
$ popd
$ bitbake-layers add-layer ../poky/meta-st-stm32mp

local.conf

MACHINEを設定する。STM32MP157F-DK2stm32mp1で行けるらしい。

MACHINE ?= "stm32mp1"

ビルド

まずは最小構成を作成して起動確認する。

$ bitbake core-image-minimal

書き込み

build/tmp/deploy/images/stm32mp1を確認するとwicイメージが生成されていない。 どうやら、build/tmp/deploy/images/stm32mp1/scripts/create_sdcard_from_flashlayout.shを使用して、バラバラにできたファイルシステムのイメージを1つのイメージファイルにまとめるらしい。

そのレイアウト情報はbuild/tmp/deploy/images/stm32mp1/flashlayout_core-image-minimal以下に配置されている、*.tsvファイルらしい。

試しに1つ見てみる。

#Opt  Id  Name    Type    IP  Offset  Binary
-   0x01    fsbl-boot   Binary  none    0x0 arm-trusted-firmware/tf-a-stm32mp157f-dk2-usb.stm32
-   0x03    fip-boot    FIP none    0x0 fip/fip-stm32mp157f-dk2-optee.bin
P   0x04    fsbl1   Binary  mmc0    0x00004400  arm-trusted-firmware/tf-a-stm32mp157f-dk2-sdcard.stm32
P   0x05    fsbl2   Binary  mmc0    0x00044400  arm-trusted-firmware/tf-a-stm32mp157f-dk2-sdcard.stm32
P   0x06    metadata1   Binary  mmc0    0x00084400  arm-trusted-firmware/metadata.bin
P   0x07    metadata2   Binary  mmc0    0x000C4400  arm-trusted-firmware/metadata.bin
P   0x08    fip-a   FIP mmc0    0x00104400  fip/fip-stm32mp157f-dk2-optee.bin
PED 0x09    fip-b   FIP mmc0    0x00504400  none
PED 0x0A    u-boot-env  Binary  mmc0    0x00904400  none
P   0x10    bootfs  System  mmc0    0x00984400  st-image-bootfs-poky-stm32mp1.ext4
P   0x11    vendorfs    FileSystem  mmc0    0x04984400  st-image-vendorfs-poky-stm32mp1.ext4
P   0x12    rootfs  FileSystem  mmc0    0x05984400  core-image-minimal-stm32mp1.ext4

はっきり言ってダサい。wksファイルと何が違うのだろうか。

最初からwicイメージ吐けばいいだろう。こんなものは使用しない。

wicイメージ

IMAGE_FSTYPES

IMAGE_FSTYPESを確認する。

$ bitbake core-image-minimal -e | grep '^IMAGE_FSTYPES='
IMAGE_FSTYPES=" tar.xz ext4 stmultiubi"

wicイメージは出力していない。

wicを出力する

local.confに下記の内容を追加して、wicイメージを吐くようにする。

IMAGE_FSTYPES:append = " wic.bz2 wic.bmap"
WKS_FILE = "sdcard-stm32mp157f-dk2-optee-1GB.wks.in"

bitbakeすると下記の様になる。

| wic.filemap.Error: cannot open image file '/home/mickey/yocto/stm32mp15-kirkstone/build/tmp/deploy/images/stm32mp1/st-image-bootfs-poky-stm32mp1.ext4': [Errno 2] No such file or directory: '/home/mickey/yocto/stm32mp15-kirkstone/build/tmp/deploy/images/stm32mp1/st-image-bootfs-poky-stm32mp1.ext4'
| WARNING: exit code 1 from a shell command.
ERROR: Task (/home/mickey/yocto/stm32mp15-kirkstone/poky/meta-st-stm32mp/recipes-st/images/st-image-userfs.bb:do_image_wic) failed with exit code '1'

エラーの原因を調査

まずは、st-image-userfs.bbのdo_image_wicタスクでエラーになる。 これしつこくbitbakeを繰り返すと、下記の3つのレシピのdo_image_wicでエラーになることがわかる。

  • st-image-userfs.bb
  • st-image-bootfs.bb
  • st-image-vendorfs.bb

この原因は、wksを見てみるとなんとなくわかる。

... (snip) ...
# Bootfs
part bootfs --source rawcopy --sourceparams="file=st-image-bootfs-${DISTRO}-${MACHINE}.ext4" --ondisk mmcblk --fstype=ext4 --label bootfs --active --fixed-size 64M
# Rootfs
part / --source rootfs --ondisk mmcblk --fstype=ext4 --label rootfs --fixed-size 783M --uuid e91c4e10-16e6-4c0e-bd0e-77becf4a3582
# Userfs
part usrfs --source rawcopy --sourceparams="file=st-image-userfs-${DISTRO}-${MACHINE}.ext4" --ondisk mmcblk --fstype=ext4 --label userfs --fixed-size 128M
... (snip) ...

sourceparamsでst-image-XXXfsのext4を要求している。

st-image-XXXfsのwicを吐き出すためにはその他のst-image-YYYfsのext4がほしいので。。。となる。 つまり、st-image-userfsのdo_image_ext4のためにこのレシピが処理されるんだけど、そのついでにdo_image_wicも実行しようとするので、 今度はst-image-XXXfs.ext4が見つからない!とエラーになる。

でもst-image-*.bbのレシピの処理結果としてはext4がほしいのであって、これらでdo_image_wicは実行される必要はない。

ワークアラウンド

まず、ワークアラウンド用のレイヤを作成

$ bitbake-layers create-layre meta-work
$ bitbake-layers add-layer ./meta-work

下記のレシピにbbappendを追加

  • st-image-userfs.bb
  • st-image-bootfs.bb
  • st-image-vendorfs.bb
$ recipetool newappend ./meta-work st-image-userfs -e 
$ recipetool newappend ./meta-work st-image-userfs -e 
$ recipetool newappend ./meta-work st-image-userfs -e 

それぞれのbbappendに下記を追加。

do_image_wic[noexec] = "1"

最終的にはこんな感じになる。

./meta-work/
├── COPYING.MIT
├── README
├── conf
│   └── layer.conf
├── recipes-example
│   └── example
│       └── example_0.1.bb
└── recipes-st
    └── images
        ├── st-image-bootfs.bbappend
        ├── st-image-userfs.bbappend
        └── st-image-vendorfs.bbappend

これで、st-image-*のレシピでdo_image_wicは実行されなくなる。

ビルド確認

これでビルドが通るか試す。

$ bitbake core-image-minimal

wicがちゃんとできているか確認

$ pushd ./tmp/deploy/images/stm32mp1/
$ ls -l | grep 'wic'
-rw-r--r-- 2 mickey mickey      5485  1月  7 18:28 core-image-minimal-stm32mp1-20230107083645.rootfs.wic.bmap
-rw-r--r-- 2 mickey mickey  12637934  1月  7 18:28 core-image-minimal-stm32mp1-20230107083645.rootfs.wic.bz2
-rw-r--r-- 2 mickey mickey      5928  1月  7 20:38 core-image-minimal-stm32mp1-20230107113713.rootfs.wic.bmap
-rw-r--r-- 2 mickey mickey  12641796  1月  7 20:38 core-image-minimal-stm32mp1-20230107113713.rootfs.wic.bz2
lrwxrwxrwx 2 mickey mickey        58  1月  7 20:38 core-image-minimal-stm32mp1.wic.bmap -> core-image-minimal-stm32mp1-20230107113713.rootfs.wic.bmap
lrwxrwxrwx 2 mickey mickey        57  1月  7 20:38 core-image-minimal-stm32mp1.wic.bz2 -> core-image-minimal-stm32mp1-20230107113713.rootfs.wic.bz2

このwicイメージでSDカードを作成して起動確認する。

動作確認

ボードの準備

  1. 箱から出す
  2. マイクロSDを挿す
  3. ST-LINK(CN11)にUSB-Micro-BでPCに挿す
  4. /dev/ttyACM0でminicomを実行する
  5. PWR IN(CN6)にUSB-Cで電源を挿す

ログイン

minicomでログイン画面を確認する。

Poky (Yocto Project Reference Distro) 4.0.6 stm32mp1 /dev/ttySTM0

stm32mp1 login: root
root@stm32mp1:~#

Gotcha!!

おまけ

今回のワークアラウンドは毎回やるのが面倒くさいのと、明らかに実装のバグにみえるのでプルリクエストを出した。 46番としてPR出したら「お前誰よ?」って即クローズされた。CLAの登録をして再度PR提出。

実は、先程bbappendを作成した3つのレシピはすべてst-image-partitions.incをインクルードしているので、こいつにdo_image_wic[noexec]を追加してやるだけのパッチとしてPRを作成した。

まとめ

  • Linuxが動くSTM32のボード(STM32MP157F-DK2)を買った
  • meta-st-stm32mpがあるのでYoctoProjectでも使用可能
  • wicが吐けない
  • ワークアラウンドで乗り切った
  • 実機でcore-image-minimalが動いた
  • ついでだからプルリクエスト作成した

引き続き、このボードで遊んでみたい。

YoctoProject initramfsでOverlayFS

はじめに

YoctoProject ラズベリーパイ4でinitramfsではラスベリーパイ4の実機でinitramfsを試した。ただ、initramfsのシェルにフォールバックするだけだったので、今回は少し実践的な例を示す。

具体的にはYoctoProject kirkstoneでリードオンリーのファイルシステムでやったようなことを、initramfsを使って実現する。

initramfsでOverlayFS

YoctoProjectでinitramfs入門ではきちんと触れなかったが、initramfsのモジュールを提供するinitramfs-framework_1.0.bbでは次のようなパッケージも提供している。

SUMMARY:initramfs-module-overlayroot = "initramfs support for mounting a RW overlay on top of a RO root filesystem"
RDEPENDS:initramfs-module-overlayroot = "${PN}-base initramfs-module-rootfs"
FILES:initramfs-module-overlayroot = "/init.d/91-overlayroot"

ovarlayrootについて

initramfs-module-overlayrootではoverlayrootというスクリプトを提供している。

スクリプト冒頭のコメントを見ると、リードオンリーなルートファイルシステム/に書き込み可能なファイルシステムをオーバーレイするとのこと。

# Simple initramfs module intended to mount a read-write (RW)
# overlayfs on top of /, keeping the original root filesystem
# as read-only (RO), free from modifications by the user.

このスクリプトの中で/をリードオンリーでマウントし直すため、IMAGE_FEATUREへのread-onlyの設定は不要。

# NOTE: The read-only IMAGE_FEATURE is not required for this to work

このスクリプトはoverlay-etc.bbclassをベースにしているとのこと。overlay-etc.bbclassはkirkstoneから導入されたので、overlayrootもkirkstoneより前のバージョンには存在しないことが読み取れる。また、overlay-etc.bbclassをベースにしているため、不揮発な領域をオーバーレイすると問題が発生する/etcについても問題なくオーバーレイすることができる。

# This script is based on the overlay-etc.bbclass, which sets up
# an overlay on top of the /etc directory, but in this case allows
# accessing the original, unmodified rootfs at /rofs after boot.

不揮発な領域を/etcにオーバーレイすると発生する問題とは、具体的には下記のようなものである。

  1. OverlayFS上で/etcのファイルを書き換える。
  2. そのデータはupper_dirに保存される
  3. 次回システム起動時にinitプロセスが/etcのファイルを参照する
  4. /etc参照後にupper_dirをオーバーレイする
  5. 結果として、書き換え前の状態の/etcを参照することになる

overlay-etc.bbclassは/sbin/initをOverlayFSをマウントするスクリプトで置き換えることにより、initプロセスの起動よりも前にupper_dirをオーバーレイすることでこの問題を回避している。

initramfsは本来のルートファイルシステムをマウントする前に、RAM上に展開されinitramfs上のinitプログラムおよびモジュールを実行するため、initプロセスの起動よりも前に、任意の処理を実行することができる。 そのため、initramfsのモジュールとしてoverlayrootを実行することにより/etcのオーバーレイのタイミングの問題を回避しながら、書き込み可能な領域をオーバーレイすることができる。

overlayrootではオーバーレイする書き込み可能な領域をブートパラメータのrootrwで指定する。

# It relies on the initramfs-module-rootfs to mount the original
# root filesystem, and requires 'rootrw=<foo>' to be passed as a
# kernel parameter, specifying the device/partition intended to
# use as RW.

注意点としては、rootfsモジュールのあとに実行される必要がある。

# This module needs to be executed after the initramfs-module-rootfs
# since it relies on it to mount the filesystem at initramfs startup
# but before the finish module which normally switches root.
# After overlayroot is executed the usual boot flow continues from
# the real init process.

overlayrootの実装について

実装をきちんと見始めると実はこのスクリプトはモジュールとしていくつか問題がありそうなことがわかる。

  1. <module>_enable<module>_run関数を実装していない
  2. rootrwのためのfstypeとマウントパラメータに、rootfstypeとrootflagsを参照している

1つ目の方は、overlayrootの最後でchrootをexecし、initスクリプトを実行しているプロセスがルートファイルシステム上の/sbin/initに遷移するため、<module>_enable<module>_runはそもそも呼び出されないので問題ない。

2つ目の方は、rootfsモジュールでルートファイルシステムをマウントするためのマウントオプションとして参照するため、overlayrootのrootrwのために値を設定した時に問題が発生する可能性がある。 具体的には、rootrwをtmpfsに割り当てようとrootfstype=tmpfsとかすると、本来のルートファイルシステムのマウントに失敗するようになってしまう。 本来であればこれは変数を分けるべきだと思う。どちらもデフォルトの動作を期待してブートパラメータにrootfstyperootflagsを指定しなければ問題は発生しない。 overlayrootを使用する際はこの2つのブートパラメータは使用しないのが無難そうだ。

ラズベリーパイ4で試す

ベース環境の構築についてはYoctoProject ラズベリーパイ4でinitramfsを参照のこと。

書き込みパーティションの追加

wksの作成

既存のwksファイルをベースに新しくwksを作成し書き込み可能なパーティションを追加する。

$ bitbake-layers create-layer meta-work
$ bitbake-layers add-layer meta-work
$ mkdir meta-work/wic

meta-work/wic/sdimg-raspberrypi-overlay.wksを次の内容で作成する。

part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 20
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096 --size 2048
part --ondisk mmcblk0 --fstype=ext4 --label data --fixed-size 20

第3のパーティションにマウントポイントを指定せずにext4の領域を追加している。

local.confに下記を追加して、参照するwksファイルを差し替える。

WKS_FILE="sdimg-raspberrypi-overlay.wks"

local.confの修正

local.confに下記の内容が追加されるようにする。

INITRAMFS_IMAGE = "core-image-minimal-initramfs"
INITRAMFS_IMAGE_BUNDLE = "1"
BOOT_SPACE = "1073741"
INITRAMFS_MAXSIZE = "315400"
IMAGE_FSTYPES_pn-${INITRAMFS_IMAGE} = "${INITRAMFS_FSTYPES}"

IMAGE_BOOT_FILES = "${BOOTFILES_DIR_NAME}/* \
                 ${@make_dtb_boot_files(d)} \
               ${KERNEL_IMAGETYPE}-${INITRAMFS_LINK_NAME}.bin;${SDIMG_KERNELIMAGE} \
"

INITRAMFS_SCRIPTS = "initramfs-module-overlayroot"
CMDLINE_DEBUG = "debug rootrw=/dev/mmcblk0p3"

WKS_FILE="sdimg-raspberrypi-overlay.wks"

とりあえずCMDLINE_DEBUGにrootrw=/dev/mmcblk0p3を指定し、先程wksに追加したパーティションを書き込み領域に指定する。

動作確認

作成し直したイメージでラズベリーパイ4を起動、ログインして下記のコマンドの結果を確認。

# mount | grep 'overlay'
overlay on / type overlay (rw,relatime,lowerdir=/overlay/rofs,upperdir=/overlay/upper,workdir=/overlay/work)

/がoverlayされている。

ファイルの書き込みをして再起動後に参照できればOK。

まとめ

initramfsを使用してOverlayFSを使用する方法を試した。

メリットとしては、overlay-etc.bbclassと比較すると、マウントポイントの作成などoverlayrootモジュールの中でやってくれるので、 設定する項目が少ない。initramfsなので、処理の追加や変更が柔軟に行える。

デメリットとしては、initramfsをカーネルにバンドルする場合は、カーネルイメージが大きくなる。 バンドルしない場合は、管理対象が増える。initramfsをしない場合と比較して、起動時間が長くなることが多い。

overlayrootモジュールを使用する際の注意点としては、ブートパラメータにrootfstypeおよびrootflagsを使用しないほうが無難。

このモジュールはkirkstoneよりも前のバージョンには存在しない。ただ、同等の処理を実装するかそのものをバックポートすることで、 独自に仕組みを実装したり、overlay-etc.bbclassをバックポートしたりするよりも、簡単で比較的安全にOverlayFSを使用する環境を構築できると思う。

YoctoProject ラズベリーパイ4でinitramfs

はじめに

YoctoProjectでinitramfs入門でinitramfsの使用方法を調査した。今回は実機環境でinitramfsを使用してみる。

ターゲットはラズベリーパイ4。使用するブランチはkirkstone。

環境構築

作業環境

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone

pokyの取得

$ git clone -b kirkstone git://git.yoctoproject.org/poky.git

環境変数の設定

$ source poky/oe-init-build-env

meta-raspberrypiの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confを編集

meta-raspberrypiでinitramfsをバンドルしたカーネルを作成するための設定はmeta-raspberrypi/docs/extra-build-config.mdに下記のように記載されている。

## Image with Initramfs

To build an initramfs image:

* Set this 3 kernel variables (in kernel's do_configure:prepend in linux-raspberrypi.inc after the line kernel_configure_variable LOCALVERSION "\"\""
)
  - kernel_configure_variable BLK_DEV_INITRD y
  - kernel_configure_variable INITRAMFS_SOURCE ""
  - kernel_configure_variable RD_GZIP y

* Set the yocto variables (e.g. in local.conf)
  - `INITRAMFS_IMAGE = "<name for your initramfs image>"`
  - `INITRAMFS_IMAGE_BUNDLE = "1"`
  - `BOOT_SPACE = "1073741"`
  - `INITRAMFS_MAXSIZE = "315400"`
  - `IMAGE_FSTYPES_pn-${INITRAMFS_IMAGE} = "${INITRAMFS_FSTYPES}"`

これを参考にlocal.confに設定を追加する。kernel_configure_variableについては追加の作業は不要のようだ。

conf/local.confに下記の部分を追加

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# enable uart
ENABLE_UART = "1"

# systemd
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# Bundle initramfs
INITRAMFS_IMAGE = "core-image-minimal-initramfs"
INITRAMFS_IMAGE_BUNDLE = "1"
BOOT_SPACE = "1073741"
INITRAMFS_MAXSIZE = "315400"
IMAGE_FSTYPES_pn-${INITRAMFS_IMAGE} = "${INITRAMFS_FSTYPES}"

# fallbackto shell in the initramfs
INITRAMFS_SCRIPTS = "\
                      initramfs-framework-base \
                      initramfs-module-debug \
 "
CMDLINE_DEBUG = "debug shell"

INITRAMFS_IMAGEは前回と同様にcore-image-minimal-initramfsを使用する。 INITRAMFS_SCRIPTSでモジュールを設定。CMDLINE_DEBUGカーネルのブートパラメータを追加している。

使用するカーネルイメージの変更

例によってこのままでは、initramfsをバンドルしたカーネルイメージが作成されるようになるだけで、 wicによって作成されるイメージにはそれは適用されない。

meta-raspberrypiのカーネル

bitbakeで生成されるカーネルイメージは下記のようになっている。

$ pushd ./tmp/deploy/images/raspberrypi4-64/
$ ls -lha Image*
lrwxrwxrwx 2 mickey mickey  80  1月  6 02:08 Image -> Image-1-5.15.34+git0+e1b976ee4f_0086da6acd-r0-raspberrypi4-64-20230105170742.bin
-rw-r--r-- 2 mickey mickey 24M  1月  6 02:08 Image-1-5.15.34+git0+e1b976ee4f_0086da6acd-r0-raspberrypi4-64-20230105170742.bin
-rw-r--r-- 2 mickey mickey 36M  1月  6 02:08 Image-initramfs-1-5.15.34+git0+e1b976ee4f_0086da6acd-r0-raspberrypi4-64-20230105170742.bin
lrwxrwxrwx 2 mickey mickey  90  1月  6 02:08 Image-initramfs-raspberrypi4-64.bin -> Image-initramfs-1-5.15.34+git0+e1b976ee4f_0086da6acd-r0-raspberrypi4-64-20230105170742.bin
lrwxrwxrwx 2 mickey mickey  80  1月  6 02:08 Image-raspberrypi4-64.bin -> Image-1-5.15.34+git0+e1b976ee4f_0086da6acd-r0-raspberrypi4-64-20230105170742.bin

一方で、wicイメージのブートパーティションに書き込まれているカーネルは下記のようになっている。

$ ls -lha | grep kernel 
-rw-r--r--  1 mickey mickey  36M  4月  6  2011 kernel8.img

つまり、カーネルイメージはkernel8.imgという名前にリネームしてコピーされるということになる。

meta-raspberrypi/wic/sdimage-raspberrypi.wksを見るとブートパーティションbootimg-partitionのソースプラグインを使用していることがわかる。

part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 20

このプラグインIMAGE_BOOT_FILES変数によって、インストールするファイルを制御している。 meta-raspberrypiのこの変数の定義はmeta-raspberrypi/conf/machine/include/rpi-base.incで下記のようになっている。

IMAGE_BOOT_FILES ?= "${BOOTFILES_DIR_NAME}/* \
                 ${@make_dtb_boot_files(d)} \
                 ${@bb.utils.contains('RPI_USE_U_BOOT', '1', \
                    '${KERNEL_IMAGETYPE} u-boot.bin;${SDIMG_KERNELIMAGE} boot.scr', \
                    '${KERNEL_IMAGETYPE};${SDIMG_KERNELIMAGE}', d)} \
                 "

実際には下記のように展開される。

$ bitbake -e core-image-minimal | grep '^IMAGE_BOOT_FILES='
IMAGE_BOOT_FILES="bootfiles/*                  bcm2711-rpi-4-b.dtb bcm2711-rpi-400.dtb bcm2711-rpi-cm4.dtb overlay_map.dtb;overlays/overlay_map.dtb at86rf233.dtbo;overlays/at86rf233.dtbo disable-bt.dtbo;overlays/disable-bt.dtbo dwc2.dtbo;overlays/dwc2.dtbo gpio-ir.dtbo;overlays/gpio-ir.dtbo gpio-ir-tx.dtbo;overlays/gpio-ir-tx.dtbo gpio-key.dtbo;overlays/gpio-key.dtbo gpio-poweroff.dtbo;overlays/gpio-poweroff.dtbo gpio-shutdown.dtbo;overlays/gpio-shutdown.dtbo hifiberry-amp.dtbo;overlays/hifiberry-amp.dtbo hifiberry-dac.dtbo;overlays/hifiberry-dac.dtbo hifiberry-dacplus.dtbo;overlays/hifiberry-dacplus.dtbo hifiberry-digi.dtbo;overlays/hifiberry-digi.dtbo justboom-both.dtbo;overlays/justboom-both.dtbo justboom-dac.dtbo;overlays/justboom-dac.dtbo justboom-digi.dtbo;overlays/justboom-digi.dtbo i2c-gpio.dtbo;overlays/i2c-gpio.dtbo i2c-rtc.dtbo;overlays/i2c-rtc.dtbo imx219.dtbo;overlays/imx219.dtbo imx477.dtbo;overlays/imx477.dtbo iqaudio-dac.dtbo;overlays/iqaudio-dac.dtbo iqaudio-dacplus.dtbo;overlays/iqaudio-dacplus.dtbo mcp2515-can0.dtbo;overlays/mcp2515-can0.dtbo mcp2515-can1.dtbo;overlays/mcp2515-can1.dtbo mcp3008.dtbo;overlays/mcp3008.dtbo miniuart-bt.dtbo;overlays/miniuart-bt.dtbo pitft22.dtbo;overlays/pitft22.dtbo pitft28-capacitive.dtbo;overlays/pitft28-capacitive.dtbo pitft28-resistive.dtbo;overlays/pitft28-resistive.dtbo pitft35-resistive.dtbo;overlays/pitft35-resistive.dtbo pps-gpio.dtbo;overlays/pps-gpio.dtbo rpi-ft5406.dtbo;overlays/rpi-ft5406.dtbo rpi-poe.dtbo;overlays/rpi-poe.dtbo vc4-fkms-v3d.dtbo;overlays/vc4-fkms-v3d.dtbo vc4-fkms-v3d-pi4.dtbo;overlays/vc4-fkms-v3d-pi4.dtbo vc4-kms-v3d.dtbo;overlays/vc4-kms-v3d.dtbo vc4-kms-v3d-pi4.dtbo;overlays/vc4-kms-v3d-pi4.dtbo vc4-kms-dsi-7inch.dtbo;overlays/vc4-kms-dsi-7inch.dtbo w1-gpio.dtbo;overlays/w1-gpio.dtbo w1-gpio-pullup.dtbo;overlays/w1-gpio-pullup.dtbo wm8960-soundcard.dtbo;overlays/wm8960-soundcard.dtbo          Image;kernel8.img 

Image;kernel8.imgの行が、カーネルイメージのリネームの部分となることがわかる。つまり下記の部分を修正することで、wicイメージに適用されるカーネルイメージを変更することができる。

${@bb.utils.contains('RPI_USE_U_BOOT', '1', \
   '${KERNEL_IMAGETYPE} u-boot.bin;${SDIMG_KERNELIMAGE} boot.scr', \
   '${KERNEL_IMAGETYPE};${SDIMG_KERNELIMAGE}', d)} \

RPI_USE_U_BOOT変数によってカーネルをu-bootに差し替えるための分岐があるが、今回はu-bootを使用しないので、この部分は無視できる。 この変数はDEPLOY_DIR内のファイル名;wicイメージ内のファイル名という記法になっている。

ここでは、DEPLOY_DIRはbuild/tmp/deploy/images/raspberrypi4-64を指すので、この中にあるinitramfsバンドルのカーネルイメージを指すようにすれば良い。

これらを踏まえるとlocal.confIMAGE_BOOT_FILESを下記のように設定すれば良いことになる。

IMAGE_BOOT_FILES = "${BOOTFILES_DIR_NAME}/* \
                 ${@make_dtb_boot_files(d)} \
               ${KERNEL_IMAGETYPE}-${INITRAMFS_LINK_NAME}.bin;${SDIMG_KERNELIMAGE} \
                 "

${KERNEL_IMAGETYPE}-${INITRAMFS_LINK_NAME}.binを展開するとImage-initramfs-raspberrypi4-64.binになるので、 これがkernel8.imgとしてwicイメージに入るようになる。

動作確認

local.confまとめ

最終的にlocal.confのinitramfs関連の設定は下記のようになる。

INITRAMFS_IMAGE = "core-image-minimal-initramfs"
INITRAMFS_IMAGE_BUNDLE = "1"
BOOT_SPACE = "1073741"
INITRAMFS_MAXSIZE = "315400"
IMAGE_FSTYPES_pn-${INITRAMFS_IMAGE} = "${INITRAMFS_FSTYPES}"

INITRAMFS_SCRIPTS = "\
                      initramfs-framework-base \
                      initramfs-module-debug \
"
CMDLINE_DEBUG = "debug shell"

IMAGE_BOOT_FILES = "${BOOTFILES_DIR_NAME}/* \
                 ${@make_dtb_boot_files(d)} \
               ${KERNEL_IMAGETYPE}-${INITRAMFS_LINK_NAME}.bin;${SDIMG_KERNELIMAGE} \
"

ビルドおよび動作確認

下記でビルドする。

$ bitbake core-image-base

出来上がったwicイメージをSDカードに書き込む。

$ pushd ./tmp/deploy/images/raspberrypi4-64
$ sudo bmaptool copy ./core-image-base-raspberrypi4-64.wic.bz2 /dev/sdX

/dev/sdXは適宜読み替え。

下記のようにログイン前にshが実行されればinitramfsが動作している。

DEBUG: Loading module debug
DEBUG: Running debug_run
DEBUG: Calling module hook (post): debug_hook_handler
Starting shell after debug...
sh: can't access tty; job control turned off
/ #

まとめ

meta-raspberrypiを使用して、ラズベリーパイ4の実機でもinitramfsを使用できることを確認した。 カーネルイメージにinitramfsをバンドルしてしまえば、config.txtなどに追加の設定を行う必要もなく、 initramfsを使用できることがわかった。

YoctoProjectでinitramfs入門

はじめに

YoctoProjectで作成するLinuxイメージでもinitramfsを使用することができる。 これまであまりつかってこなかったので入門してみる。

使用するバージョンはkirkstone(4.0)。

環境構築

今回はqemuarm64のターゲットで実験する。

initramfsはカーネルイメージにバンドルして1つのファイルにする方法と、 別々のファイルにして、ブートローダに読み込ませる方法がある。

前者の方は、管理するファイルが少なくなり、u-bootなどのブートローダに対する設定項目も減るため、 特に理由がなければバンドルする方で問題ないと思われる。

poky

pokyを取得する。

$ ~/yocto/qemuarm64
$ git clone git://git.yoctoproject.org/poky.git -b kirkstone

環境変数の設定

$ source poky/oe-init-build-env

local.conf

Webでよく見かける使い方。local.confに下記を追加する。

MACHINE = "qemuarm64"

INITRAMFS_IMAGE = "core-image-minimal-initramfs"
INITRAMFS_IMAGE_BUNDLE = "1"
INITRAMFS_FSTYPES = "cpio.gz"

これで、initramfsがくっついたカーネルイメージが作成されるようになる。

動作確認

ビルド

これでビルドをしてみる。

$ bitbake core-image-minimal

確かにinitramfs付きのカーネルイメージがビルドされているように見える。

ls ./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin 
./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin

動作確認

普通に起動したように見える。

$ runqemu nographic
... (snip) ...
Poky (Yocto Project Reference Distro) 4.0.6 qemuarm64 ttyAMA0

qemuarm64 login:

initramfsを使用しているの確認したいが、痕跡がよくわからない。

先に結論を言うと、この状態ではinitramfsは使用されない。

使用するカーネルの変更

runqemuはcore-image-minimal-qemuarm64.qemuboot.confを参照して、QEMUを起動する。このファイルにはQEMUを起動する際に使用されるデフォルトのパラメータが記述されている。

デフォルトのカーネルイメージはImage--5.15.78+git0+f475b1a9de_a1d364fbe3-r0-qemuarm64-20230105065908.binとなっている。

$ cat ./tmp/deploy/images/qemuarm64/core-image-minimal-qemuarm64.qemuboot.conf  | grep qb_default_kernel
qb_default_kernel = Image--5.15.78+git0+f475b1a9de_a1d364fbe3-r0-qemuarm64-20230105065908.bin

これは、Imageに貼られたシンボリックリンクの実体で初期状態のカーネルイメージとなっている。 つまり、initramfsはバンドルされていない。

カーネルを指定してrunqemuを実行する。

$ runqemu nographic ./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin
... (snip) ...
Poky (Yocto Project Reference Distro) 4.0.6 qemuarm64 ttyAMA0

qemuarm64 login: 

起動した。initramfsを使用しているはずだが、痕跡を確認できない。

initramfsの構造

initramfsのディレクトリツリーを確認する。

$ pushd ./tmp/work/qemuarm64-poky-linux/core-image-minimal-initramfs/1.0-r0/rootfs
$ ls
bin  boot  dev  etc  home  init  init.d  lib  media  mnt  proc  run  sbin  sys  tmp  usr  var

initがinitramfsで最初に実行されるプログラムだと推測できる。

initramfsのinit

initの基本的な構造は下記のようになっている。

  1. 主要変数の定義
  2. /proc/cmdlineの解析
  3. modulesの実行

主要変数の定義

主要な変数の定義は下記のようになっている。注目すべきなのはMODULES_DIR=/init.dの部分。

# Variables shared amoung modules
ROOTFS_DIR="/rootfs" # where to do the switch root
MODULE_PRE_HOOKS=""  # functions to call before running each module
MODULE_POST_HOOKS="" # functions to call after running each module
MODULES_DIR=/init.d  # place to look for modules
EFI_DIR=/sys/firmware/efi  # place to store device firmware information

/proc/cmdlineの解析

カーネルに渡されたブートパラメータは/proc/cmdlineを読むことで参照できる。

# populate bootparam environment
for p in `cat /proc/cmdline`; do
    if [ -n "$quoted" ]; then
        value="$value $p"
        if [ "`echo $p | sed -e 's/\"$//'`" != "$p" ]; then
            eval "bootparam_${quoted}=${value}"
            unset quoted
        fi
        continue
    fi

    opt=`echo $p | cut -d'=' -f1`
    opt=`echo $opt | sed -e 'y/.-/__/'`
    if [ "`echo $p | cut -d'=' -f1`" = "$p" ]; then
        eval "bootparam_${opt}=true"
    else
        value="`echo $p | cut -d'=' -f2-`"
        if [ "`echo $value | sed -e 's/^\"//'`" != "$value" ]; then
            quoted=${opt}
            continue
        fi
        eval "bootparam_${opt}=\"${value}\""
    fi
done

このスクリプトではブートパラメータとして渡された内容は、bootparam_XXXという変数として参照できるようになっている。 細かいルールは下記のようにスクリプト冒頭のコメントで説明されている。

# Boot parameters are available on environment in the as:
#
# 'foo=value' as 'bootparam_foo=value'
# 'foo' as 'bootparam_foo=true'
# 'foo.bar[=value] as 'foo_bar=[value|true]'

これを踏まえて、プリント関連の関数の実装を見てみると、下記のようになっている。

# Prints information
msg() {
    echo "$@" >/dev/console
}

# Prints information if verbose bootparam is used
info() {
    [ -n "$bootparam_verbose" ] && echo "$@" >/dev/console
}

# Prints information if debug bootparam is used
debug() {
    [ -n "$bootparam_debug" ] && echo "DEBUG: $@" >/dev/console
}

ブートパラメータにverbosedebugを渡しておくと、initramfs内でのプリント出力が増えることが推測できる。

modulesの実行

modulesとはなにか?を一旦おいて、下記の部分を見る。

# Load and run modules
for m in $MODULES_DIR/*; do
    # Skip backup files
    if [ "`echo $m | sed -e 's/\~$//'`" != "$m" ]; then
        continue
    fi

    module=`basename $m | cut -d'-' -f 2`
    debug "Loading module $module"

... (snip) ...

    # process module
 . $m

    if ! eval "${module}_enabled"; then
        debug "Skipping module $module"
        continue
    fi

    debug "Running ${module}_run"
    eval "${module}_run"

... (snip) ...

done

MODULES_DIRにあるファイルを読み込んで何やら実行していることは読み取ることができる。 MODULES_DIRは/init.dが設定されているので、下記を確認する。

$ ls ./init.d
01-udev  80-setup-live  90-rootfs  99-finish  install-efi.sh  install.sh

何やらスクリプトが格納されている。 moduleについては、スクリプト冒頭のコメントの下記の部分で説明されている。

# Modules need to provide the following functions:
#
# <module>_enabled : check if the module ought to run (return 1 to skip)
# <module>_run     : do what is need

modulesはinitramfsの/init.dに格納されていて、<module>_enabled<module>_runの2つの関数が実装されているスクリプトということになる。 modulesは辞書順に読み込まれるため、現状のcore-image-minimal-initramfsでは01-udevから99-finishまで順に実行される。 ちなみに、99-finishの中でswitch_rootするため、それ以降のファイルは実行されない。

出力を増やして動作確認

$ popd
$ runqemu nographic ./tmp/deploy/images/qemuarm64/Image-initramfs-qemuarm64.bin bootparams="verbose debug"
... (snip) ...
DEBUG: Loading module udev
DEBUG: Running udev_run
Starting version 250.5+
[    4.720551] EXT4-fs (vda): recovery complete
[    4.721767] EXT4-fs (vda): mounted filesystem with ordered data mode. Opts: (null). Quota mode: disabled.
DEBUG: Loading module setup
DEBUG: Calling module hook (pre): udev_shutdown_hook_handler
DEBUG: Finished module hook (pre): udev_shutdown_hook_handler
DEBUG: Running setup_run
DEBUG: Loading module rootfs
DEBUG: Calling module hook (pre): udev_shutdown_hook_handler
DEBUG: Finished module hook (pre): udev_shutdown_hook_handler
DEBUG: Running rootfs_run
DEBUG: No e2fs compatible filesystem has been mounted, mounting /dev/vda...
DEBUG: Loading module finish
DEBUG: Calling module hook (pre): udev_shutdown_hook_handler
DEBUG: Finished module hook (pre): udev_shutdown_hook_handler
DEBUG: Running finish_run
... (snip) ...

initramfs中のmodulesが実行されていることが確認できた。

YoctoProjectのinitrmafsのmodulesについて

YoctoProjectのinitramfsはmodulesによって任意の処理を実行することができる。 これらのモジュールはどの様に選択、変更するのかを調査する。

core-image-minimal-initramfs.bbを参照する。

INITRAMFS_SCRIPTS ?= "\
                      initramfs-framework-base \
                      initramfs-module-setup-live \
                      initramfs-module-udev \
                      initramfs-module-install \
                      initramfs-module-install-efi \
                     "

PACKAGE_INSTALL = "${INITRAMFS_SCRIPTS} ${VIRTUAL-RUNTIME_base-utils} udev base-passwd ${ROOTFS_BOOTSTRAP_INSTALL}"

PACKAGE_INSTALLがinitramfsにパッケージを追加するための変数で、INITRAMFS_SCRIPTSを参照している。 INITRAMFS_SCRIPTSで指定されているパッケージは、initramfs-framework_1.0.bbで提供されている。

... (snip) ...

PACKAGES = "${PN}-base \
            initramfs-module-exec \
            initramfs-module-mdev \
            initramfs-module-udev \
            initramfs-module-e2fs \
            initramfs-module-nfsrootfs \
            initramfs-module-rootfs \
            initramfs-module-debug \
            initramfs-module-lvm \
            initramfs-module-overlayroot \
           "

... (snip) ...

SUMMARY:initramfs-module-exec = "initramfs support for easy execution of applications"
RDEPENDS:initramfs-module-exec = "${PN}-base"
FILES:initramfs-module-exec = "/init.d/89-exec"

SUMMARY:initramfs-module-mdev = "initramfs support for mdev"
RDEPENDS:initramfs-module-mdev = "${PN}-base busybox-mdev"
FILES:initramfs-module-mdev = "/init.d/01-mdev"

SUMMARY:initramfs-module-udev = "initramfs support for udev"
RDEPENDS:initramfs-module-udev = "${PN}-base udev"
FILES:initramfs-module-udev = "/init.d/01-udev"

SUMMARY:initramfs-module-e2fs = "initramfs support for ext4/ext3/ext2 filesystems"
RDEPENDS:initramfs-module-e2fs = "${PN}-base"
FILES:initramfs-module-e2fs = "/init.d/10-e2fs"

SUMMARY:initramfs-module-nfsrootfs = "initramfs support for locating and mounting the root partition via nfs"
RDEPENDS:initramfs-module-nfsrootfs = "${PN}-base"
FILES:initramfs-module-nfsrootfs = "/init.d/85-nfsrootfs"

SUMMARY:initramfs-module-rootfs = "initramfs support for locating and mounting the root partition"
RDEPENDS:initramfs-module-rootfs = "${PN}-base"
FILES:initramfs-module-rootfs = "/init.d/90-rootfs"

SUMMARY:initramfs-module-debug = "initramfs dynamic debug support"
RDEPENDS:initramfs-module-debug = "${PN}-base"
FILES:initramfs-module-debug = "/init.d/00-debug"

SUMMARY:initramfs-module-lvm = "initramfs lvm rootfs support"
RDEPENDS:initramfs-module-lvm = "${PN}-base"
FILES:initramfs-module-lvm = "/init.d/09-lvm"

SUMMARY:initramfs-module-overlayroot = "initramfs support for mounting a RW overlay on top of a RO root filesystem"
RDEPENDS:initramfs-module-overlayroot = "${PN}-base initramfs-module-rootfs"
FILES:initramfs-module-overlayroot = "/init.d/91-overlayroot"

modulesを変更してみる

INITRAMFS_SCRIPTSの内容を修正することで実行するmodulesを変更できそうなことがわかった。 この変数はcore-image-minimal-initramfsにbbappendを作成して変更できるが、手っ取り早く試すにはlocal.confでも上書きすることができる。

local.confに下記の内容を追加する。

INITRAMFS_SCRIPTS = "\
                      initramfs-framework-base \
                      initramfs-module-debug \
 "

QB_DEFAULT_KERNEL = "Image-initramfs-qemuarm64.bin"
QB_KERNEL_CMDLINE_APPEND:append = " debug shell"

QB_DEFAULT_KERNELで、runqemu実行時にinitramfs付きのカーネルを使用するように変更。 QB_KERNEL_CMDLINE_APPENDで、ブートパラメータを追加している。

initramfs-module-debugは文字通り、initramfsをデバッグするためのmodulesで、下記のようにフックを追加している。

debug_run() {
    add_module_pre_hook "debug_hook_handler"
    add_module_post_hook "debug_hook_handler"
}

フックは下記のような条件で実行される。

# Adds support to dynamic debugging of initramfs using bootparam in
# following format:
#   shell                 : starts a shell before and after each module
#   shell=before:<module> : starts a shell before <module> is loaded and run
#   shell=after:<module>  : starts a shell after <module> is loaded and run
#
#   shell-debug                 : run set -x as soon as possible
#   shell-debug=before:<module> : run set -x before <module> is loaded and run
#   shell-debug=after:<module>  : run set -x after <module> is loaded and run

今回はブートパラメータにshellを追加しているので、他のすべてのモジュールが読み込まれる前後にshにフォールバックされる。

動作確認

下記のようにしてイメージを作り直し、動作確認する。

$ bitbake core-image-minimal-initramfs -c cleansstate
$ bitbake core-image-minimal
$ runqemu nographic

これで、ログインの前にシェルが実行されれば成功。

DEBUG: Loading module debug
DEBUG: Running debug_run
DEBUG: Calling module hook (post): debug_hook_handler
Starting shell after debug...
sh: can't access tty; job control turned off
/ # ls
bin     dev     home    init.d  media   proc    run     sys     usr
boot    etc     init    lib     mnt     rootfs  sbin    tmp     var
/ # 

exitでシェルを抜けるときちんと次のモジュールの前と後ろで毎回シェルが実行される。

まとめ

YoctoProjectでinitramfsを使用する方法を調査した。 initramfsに関連するイメージレシピはいくつかあるが、core-image-minimal-initramfsを使用するので問題はない感じ。

その上で初期化処理を変更したりデバッグしたい場合はINITRAMFS_SCRIPTSを上書きしてmodulesを変更する。

initramfsを使用するためにはINITRAMFS_IMAGEでinitramfsを有効化するだけでは不十分で、カーネルにバンドルした場合は、 デフォルトのカーネルイメージではなくinitramfsがバンドルされたカーネルをイメージを使用する様に、ブートローダなどの 設定を変更しておく必要がある。

DebianUbuntuでinitramfs-toolsを使ってinitramfsをカスタマイズしたことがある人は、その作業手順や内容の違いにちょっと驚くかもしれない。

YoctoProject langdaleのRust事情

はじめに

以前にyoctoでもRustで、YoctoProjectで作成したLinuxにRustのプログラムを組み込む方法を紹介した。当時はmeta-rustを使用する必要があり、かつSDKでRustのプログラムをビルドすることもできなかった。

この記事が2019年で、あれから数年経ちhonister(3.4)でRustはpokyに組み込まれた。 langdale(4.1)の環境でRustはどうなったのか見てみる。

ちなみに筆者は2019年と変わらずRust入門前である。する予定はない。

環境構築

実機のほうが楽しいのでラズベリーパイ4向けの環境で試す。

作業環境

$ mkdir -p ~/yocto/rpi-langdale
$ cd ~/yocto/rpi-langdale

pokyの取得

$ git clone -b langdale git://git.yoctoproject.org/poky.git

環境変数の設定

$ source poky/oe-init-build-env

レイヤの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confを編集

conf/local.confに下記の部分を追加

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# systemd
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# enable uart
ENABLE_UART = "1"

# License flag for wireless firmware.
LICENSE_FLAGS_ACCEPTED = "synaptics-killswitch"

# SSH server
EXTRA_IMAGE_FEATURES += "ssh-server-openssh"

# Rust helloworld
IMAGE_INSTALL:append = " rust-hello-world"

ビルド

core-image-baseを作成する。

$ bitbake core-image-base

実行結果

イメージをマイクロSDに書き込み、ラズベリーパイ4でrust-hello-worldコマンドを実行してみる。

# rust-hello-world
Hello, world!

SDKの作成

SDKを作成してみる。

$ bitbake core-image-base -c populate_sdk

インストールして、rustcやcargoがあるか見てみる。

$ pushd ./tmp/deploy/sdk
$ ./poky-glibc-x86_64-core-image-base-cortexa72-raspberrypi4-64-toolchain-4.1.sh
$ sudo find /opt/poky/4.1 -name 'rustc'
$ sudo find /opt/poky/4.1 -name 'cargo'
$ popd

どちらも見当たらない。

SDK_TOOLCHAIN_LANGS

SDK_TOOLCHAIN_LANGSによると

Specifies programming languages to support in the SDK, as a space-separated list. Currently supported items are rust and go.

SDKでサポートされるプログラミング言語を設定する(ただしrustとgoに限る)。とある。この変数はlangdaleから使えるようになったらしい。

local.confに下記を追加してみる。

SDK_TOOLCHAIN_LANGS:append = " rust"

再度、作成&インストール、検索

$ bitbake core-image-base -c populate_sdk
$ pushd ./tmp/deploy/sdk
$ ./poky-glibc-x86_64-core-image-base-cortexa72-raspberrypi4-64-toolchain-4.1.sh
$ sudo find /opt/poky/4.1 -name 'rustc'
/opt/poky/4.1/sysroots/x86_64-pokysdk-linux/usr/bin/rustc
$ sudo find /opt/poky/4.1 -name 'cargo'
sudo find /opt/poky/4.1 -name 'cargo'
$ popd

こんどは見つかった。

Rustプログラムのビルド

The Rust Programming Language 日本語版Hello, Cargo!の手順でプログラムを作成してみる。

SDK環境変数の設定

SDKのツールやクロスコンパイルの環境を設定するために

bitbakeを実行したものとは別の端末を開いて、下記のコマンドを実行する。

$ . /opt/poky/4.1/environment-setup-cortexa72-poky-linux

こちらでも良い

$ source /opt/poky/4.1/environment-setup-cortexa72-poky-linux

これでrustcやcargoが使える

rustc

$ which rustc
/opt/poky/4.1/sysroots/x86_64-pokysdk-linux/usr/bin/rustc
$ rustc --version
rustc 1.63.0

cargo

$ which cargo
/opt/poky/4.1/sysroots/x86_64-pokysdk-linux/usr/bin/cargo
mickey@strawberry:~$ cargo --version
cargo 1.63.0

数年前試したときから進化していることがわかる。

プログラムの作成

手順に沿ってCargoプロジェクトを作成する。

$ cargo new hello_cargo
$ cd hello_cargo

ビルド。

$ cargo build

ビルドされた生成物。

$ tree ./target/
./target/
├── CACHEDIR.TAG
├── aarch64-poky-linux-gnu
│   ├── CACHEDIR.TAG
│   └── debug
│       ├── build
│       ├── deps
│       │   ├── hello_cargo-65bf9bff47909b38
│       │   └── hello_cargo-65bf9bff47909b38.d
│       ├── examples
│       ├── hello_cargo
│       ├── hello_cargo.d
│       └── incremental
│           └── hello_cargo-25efxll36h9y8
│               ├── s-ggddk5xlfe-1vy0jcl-l5dttugncxvm
│               │   ├── 1ic4wft24p16s9n1.o
│               │   ├── 1kifghi4qd2733gz.o
│               │   ├── 1wxvrj2sbzov2l74.o
│               │   ├── 49ixtbxqou4rnkyl.o
│               │   ├── 4pssy91efjuqygdf.o
│               │   ├── 53t2ya8toy7chqy6.o
│               │   ├── 5ake8hymu295aas8.o
│               │   ├── 5dwbnxw6sj6cm5mi.o
│               │   ├── dep-graph.bin
│               │   ├── query-cache.bin
│               │   └── work-products.bin
│               └── s-ggddk5xlfe-1vy0jcl.lock
└── debug
    ├── build
    ├── deps
    ├── examples
    └── incremental

13 directories, 18 files

ラズベリーパイ向けとなるARM aarch64のバイナリがビルドされている。

$ file ./target/aarch64-poky-linux-gnu/debug/hello_cargo
./target/aarch64-poky-linux-gnu/debug/hello_cargo: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=bf0c892f05640e6e0ee44745bd8edb6532df45be, for GNU/Linux 3.14.0, with debug_info, not stripped

動作確認

ラズベリーパイに送りつけてみる。

$ scp ./target/aarch64-poky-linux-gnu/debug/hello_cargo root@raspberrypi4-64.local:/home/root

実機で下記を実行する。

# cd
# ./hello_cargo
Hello, world!

まとめ

YoctoProjectでのRust事情を確認してみた。 honisterでmeta-rustが不要になり、langdaleではSDK_TOOLCHAIN_LANGSを設定することで、SDKでもRustが使用可能になった。

数年前と比較すると格段に進歩しているようだ。

kirkstone以前の環境でOverlayFSを簡単に使う方法を考える

はじめに

前回はkirkstoneでリードオンリーなファイルシステムを作成して、OverlayFSで書き込み可能な領域を追加する方法を紹介した。

kirkstoneではoverlayfs.bbclassoverlayfs-etc.bbclassを使用することができるが、 それらは以前のバージョンのYoctoProjectでは存在しないため使用できない。

OverlayFSで書き込み領域を追加する簡単な方法はないだろうか。すこし検討してみる。

環境構築

作業環境

$ mkdir -p ~/yocto/rpi-hardknott
$ cd ~/yocto/rpi-hardknott

pokyの取得

$ git clone -b hardknott git://git.yoctoproject.org/poky.git

環境変数の設定

$ source poky/oe-init-build-env

レイヤの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confを編集

conf/local.confに下記の部分を追加

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# systemd
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# enable uart
ENABLE_UART = "1"

# read only root filesystem.
EXTRA_IMAGE_FEATURES += "read-only-rootfs"

今回もsystemdの使用を前提とする。

書き込みパーティションの追加

wksの作成

既存のwksファイルをベースに新しくwksを作成し書き込み可能なパーティションを追加する。

$ bitbake-layers create-layer meta-work
$ bitbake-layers add-layer meta-work
$ mkdir meta-work/wic

meta-work/wic/sdimg-raspberrypi-overlay.wksを次の内容で作成する。

part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 20
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096 --size 2048
part /data --ondisk mmcblk0 --fstype=ext4 --label data --fixed-size 20

local.confに下記を追加

WKS_FILE="sdimg-raspberrypi-overlay.wks"

マウントポイントの作成

$ recipetool newappend meta-work core-image-base

作成されたmeta-work/recipes-core/images/core-image-base.bbappendに下記の内容を記述する。

create_data_dir() {
   mkdir -p ${IMAGE_ROOTFS}/data
}

ROOTFS_PREPROCESS_COMMAND += "create_data_dir;"

揮発しても良いデータ

/home/rootに対して、揮発しても良い領域を割り当てたい場合、local.confに下記のように記述する。

VOLATILE_BINDS_append = " \
    /var/volatile/root /home/root\n\
"

/home/rootは書き込み可能になるが、実際の書き込み先として/var/volatile/rootが割り当てられるため、 /home/rootに書き込んだデータはターゲットの電源がオフになると消える。

これは、下記のように/var/volatileがtmpfsの領域だから。

tmpfs on /var/volatile type tmpfs (rw,relatime)

以前にも記載したが、注意点として、local.confでVOLATILE_BINDSを書き換えただけではvolatile-bindsパッケージは再ビルドされないので、下記のように明示的にビルドし直す必要がある。

$ bitbake volatile-binds -c clean
$ bitbake core-image-base

volatile-bindsの実装

poky/meta/recipes-core/volatile-binds/volatile-binds.bbで、VOLATILE_BINDSは下記のように、定義されている。

VOLATILE_BINDS ?= "\
    /var/volatile/lib /var/lib\n\
    /var/volatile/cache /var/cache\n\
    /var/volatile/spool /var/spool\n\
    /var/volatile/srv /srv\n\
"
VOLATILE_BINDS[type] = "list"
VOLATILE_BINDS[separator] = "\n"

VOLATILE_BINDSに登録されたパスの分だけ、systemdのユニットファイルとして登録している。

def volatile_systemd_services(d):
    services = []
    for line in oe.data.typed_value("VOLATILE_BINDS", d):
        if not line:
            continue
        what, where = line.split(None, 1)
        services.append("%s.service" % what[1:].replace("/", "-"))
    return " ".join(services)

SYSTEMD_SERVICE_${PN} = "${@volatile_systemd_services(d)}"

実際にユニットファイルを作成するのは下記。

do_compile () {
    while read spec mountpoint; do
        if [ -z "$spec" ]; then
            continue
        fi

        servicefile="${spec#/}"
        servicefile="$(echo "$servicefile" | tr / -).service"
        sed -e "s#@what@#$spec#g; s#@where@#$mountpoint#g" \
            -e "s#@whatparent@#${spec%/*}#g; s#@whereparent@#${mountpoint%/*}#g" \
            volatile-binds.service.in >$servicefile
    done <<END
${@d.getVar('VOLATILE_BINDS').replace("\\n", "\n")}
END

作成されたユニットファイルは下記のようになっている。

[Unit]
Description=Bind mount volatile /home/root
DefaultDependencies=false
Before=local-fs.target
RequiresMountsFor=/var/volatile /home
ConditionPathIsReadWrite=/var/volatile
ConditionPathExists=/home/root
ConditionPathIsReadWrite=!/home/root

[Service]
Type=oneshot
RemainAfterExit=Yes
TimeoutSec=0
ExecStart=/sbin/mount-copybind /var/volatile/root /home/root
ExecStop=/bin/umount /home/root

[Install]
WantedBy=local-fs.target

ターゲット上でに実行されるのは下記の行

ExecStart=/sbin/mount-copybind /var/volatile/root /home/root

mount-copybindはシェルスクリプトで、下記のように一度OverlayFSでマウントを試みて、 失敗した場合はmount -o bindでマウントする様になっている。この時、マウントポイント内の既存データをbind先にコピーすることによって、擬似的にオーバーレイの様にしている。

... (snip) ...

    # Try to mount using overlay, which is must faster than copying files.
    # If that fails, fall back to slower copy.
    if ! mount -t overlay overlay -olowerdir="$mountpoint",upperdir="$spec",workdir="$overlay_workdir" "$mountpoint" > /dev/null 2>&1; then

        if [ "$specdir_existed" != "yes" ]; then
            cp -aPR "$mountpoint"/. "$spec/"
        fi

        mount -o "bind$options" "$spec" "$mountpoint"
    fi
elif [ -f "$mountpoint" ]; then
    if [ ! -f "$spec" ]; then
        cp -aP "$mountpoint" "$spec"
    fi

    mount -o "bind$options" "$spec" "$mountpoint"
fi

mount-copybind自体はパラメータとして渡されたディレクトリがvolatileかどうかは判断していない。

揮発してほしくないデータ

/home/rootに対して揮発しない領域を割り当てる場合にはlocal.confのVOLATILE_BINDSを下記のようにする。

VOLATILE_BINDS_append = " \
    /data/home/root /home/root\n\
"

/dataはwksで追加した、書き込み可能なパーティションのマウントポイントとなる。 volatile-bindsの本体となるmount-copybindが、割当先がvolatileかどうかを判断しないため、 VOLATILE_BINDSのリストに揮発しない領域を記述することで、揮発しない書き込み可能領域を追加することができる。

注意点

OverlayFSが使用できる場合は、揮発する場合でもしない場合でもVOLATILE_BINDは期待通りに動作するが、 OverlayFSが使用できない場合は問題となる。

mount-copybindは、OverlayFSが使用できない場合は下記のような動作を行う。

例えば/home/root/data/home/rootにbindするケースで、予め/home/rootにデータが存在する場合。例えば/home/root/helloが存在する。

  1. /home/root内の全データを/data/home/rootにコピー
  2. /data/home/rootを/home/rootにバインド

ユーザーはバインド後の/home/rootにデータを書き込む。この時/home/root/helloの内容も書き換える。 そしてターゲットを再起動する。

mount-copybindによってコピーとバインドが行われる。 この時のコピーで、再起動前に書き換えた/data/home/root/hello/home/root/helloで上書きされる。

つまり変更内容が失われてしまうのだ。

これは、名前の通りVOLATILE_BINDSが本来は揮発することを前提とした設計になっているためである。

まとめ

kirkstone以前のバージョンのYoctoProjectで、OverlayFSにより書き込み可能領域を追加するには、 VOLATILE_BINDSを言わば悪用することで実現できる。

VOLATILE_BINDSの実処理であるmount-copybindは処理対象がvolatileであるかどうかは判断しないが、 揮発することを前提して設計されている。OverlayFSが使えないケースでは割当先を不揮発な領域にに指定した場合は問題がある。

今回の目的はOverlayFSの使用が前提となっているため、基本的に問題はないのだが、 OverlayFSが意図せず使用できない場合、わかりにくい不具合となる可能性がある。

このことから、kirkstone以降ではVOLATILE_BINDSの悪用は避け、 overlayfs.bbclassoverlayfs-etc.bbclassを使用するべき。

YoctoProject kirkstoneでリードオンリーのファイルシステム

はじめに

2021-03-25 Yocto リードオンリーなルートファイルシステムを構築するでリードオンリーなシステムの構築方法をまとめたが、情報が古くなってきたのでまとめ直す。

ターゲットボードはラズベリーパイ4、使用するYoctoProjectのバージョンはkirkstone(4.0)とする。

環境構築

作業環境

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone

pokyの取得

$ git clone -b kirkstone git://git.yoctoproject.org/poky.git

環境変数の設定

$ source poky/oe-init-build-env

レイヤの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confを編集

conf/local.confに下記の部分を追加

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# systemd
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# enable uart
ENABLE_UART = "1"

overlayfs.bbclassとoverlayfs-etc.bbclassはinitがsystemdでないと使用できない。

リードオンリーなルートFS

local.confなどのconfファイルで指定する場合は下記のようにする。

EXTRA_IMAGE_FEATURES += "read-only-rootfs"

core-image-minimal.bbappendなどを作成してレシピから指定する場合は下記のようにする。

IMAGE_FEATURES += "read-only-rootfs"

書き込み可能ディレクトリの追加

kirkstoneからoverlayfs.bbclassoverlayfs-etc.bbclassが追加されており、簡単に書き込み可能ディレクトリを定義できるようになっている。

下記の2つで使用するクラスが異なる。

  • /etc以外の任意の場所を書き込み可能にしたい場合
  • /etcを書き込み可能にしたい場合

書き込みパーティションの追加

wksの作成

既存のwksファイルをベースに新しくwksを作成し書き込み可能なパーティションを追加する。

$ bitbake-layers create-layer meta-work
$ bitbake-layers add-layer meta-work
$ mkdir meta-work/wic

meta-work/wic/sdimg-raspberrypi-overlay.wksを次の内容で作成する。

part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 20
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096 --size 2048
part /data --ondisk mmcblk0 --fstype=ext4 --label data --fixed-size 20

local.confに下記を追加

WKS_FILE="sdimg-raspberrypi-overlay.wks"

マウントポイントの作成

$ recipetool newappend meta-work core-image-base

作成されたmeta-work/recipes-core/images/core-image-base.bbappendに下記の内容を記述する。

create_data_dir() {
   mkdir -p ${IMAGE_ROOTFS}/data
}

ROOTFS_PREPROCESS_COMMAND += "create_data_dir;"

/etc以外の任意の場所を書き込み可能にしたい場合

overlayfs.bbclassを使用する。

overlayfs使用のためのレシピ

レシピ格納用のディレクトリを作成

$ mkdir meta-work/recipes-example/overlay

meta-work/recipes-example/overlay/overlay-dirs.bbを下記の内容で作成する。

inherit features_check overlayfs

REQUIRED_DISTRO_FEATURES = "systemd overlayfs"

OVERLAYFS_WRITABLE_PATHS[data] += "/home/root"
OVERLAYFS_WRITABLE_PATHS[data] += "/usr/share/my-application"

do_install() {
    install -d ${D}/home/root
    install -d ${D}/usr/share/my-application
}

FILES:${PN} += "/home"
FILES:${PN} += "/usr"

1つのマウントポイントに対して複数のディレクトリ指定できる。

local.confの設定

local.conf に下記の内容を追記する。

DISTRO_FEATURES:append = " overlayfs"

# This has to be done in your machine configuration:
OVERLAYFS_MOUNT_POINT[data] = "/data"
OVERLAYFS_QA_SKIP[data] = "mount-configured"

IMAGE_INSTALL:append = "overlay-dirs"

OVERLAYFS_MOUNT_POINTは本来、MACHINE定義のconfファイルで定義するべきらしい。

マウントポイントのQAチェック(mountユニットとfstabを確認している)はdo_rootfsで実行されるが、 今回ので手順では/dataがマウントポイントであることがfstabに反映されるタイミングがdo_image_wicタスク実行後なので、 OVERLAYFS_QA_SKIPが必要となる。

overlay-dirsパッケージをイメージに追加し、overlayするディレクトリをルートFSに反映する。

データが揮発してもOKな場合

confに下記のように追記して、揮発する領域を指すこともできる。

OVERLAYFS_MOUNT_POINT[var-volatile] = "/var/volatile"

この場合、meta-work/recipes-example/overlay/overlay-dirs.bbを 下記のようにすると保存したデータが電源をオフする毎に消えるようになる。

inherit features_check overlayfs

REQUIRED_DISTRO_FEATURES = "systemd overlayfs"

OVERLAYFS_WRITABLE_PATHS[var-volatile] += "/home/root"
OVERLAYFS_WRITABLE_PATHS[var-volatile] += "/usr/share/my-application"

do_install() {
    install -d ${D}/home/root
    install -d ${D}/usr/share/my-application
}

FILES:${PN} += "/home"
FILES:${PN} += "/usr"

このレシピを変更した場合は下記のように一度cleansstateしないとルートFSに反映されないことがある。。

$ bitbake overlay-dirs -c cleansstate
$ bitbake core-image-base -c cleansstate
$ bitbake core-image-base

/etcを書き込み可能にしたい場合

overlayfs-etc.bbclassを使用する。

overlayfs-etcの使い方

overlayfs-etc.bbclassは直接inheritしてはダメで、イメージレシピの中でIMAGE_FEATURESにoverlayfs-etcを追加する。

具体的には、core-image-minimal.bbappendなどを作成して下記のようにする。

IMAGE_FEATURES += "overlayfs-etc"

local.confなどでEXTRA_IMAGE_FEATURESに追加することもできない。

local.confの設定

これらは本来、MACHINE定義のconfファイルで定義するべきらしい。

OVERLAYFS_ETC_MOUNT_POINT = "/data"
OVERLAYFS_ETC_DEVICE = "/dev/mmcblk0p3"
OVERLAYFS_ETC_FSTYPE = "ext4"

OVERLAYFS_ETC_DEVICEOVERLAYFS_ETC_MOUNT_POINTとしてマウントされる読み書き可能なパーティションを設定する。 今回の手順では、sdimg-raspberrypi-overlay.wksで定義した第3パーティションの情報を設定すればOKとなる。

ちなみに、OVERLAYFS_ETC_MOUNT_POINTとOVERLAYFS_MOUNT_POINTは同じ場所を指しても問題ない。今回はどちらも/dataを指している。

こちらはoverlayfs.bbclassと異なりtmpfsなどの揮発する領域を指すことはできないようだ。

まとめ

kirkstoneからはoverlayfs.bbclass、overlayfs-etc.bbclassが導入された。 どちらもsystemdに依存するため、sysvinitでは使用できない。

使い方については、マニュアルも言葉足らずだし、実例の紹介などが少ないため若干解りづらい気がした。 しかし一度、理解できると柔軟にいろいろなケースで書き込み領域をオーバーレイすることができるため、便利。

組み込み機器では求められがちなリードオンリーなルートFSとは切っても切れないオーバーレイについては、 かなり利便性は向上したのではないだろうか。