前日17日はautoracexのオーガナイザー @torifukukaiou の 「kochi.ex #6 Livebook で Nerves ハンズオン!」 イベントレポートでした!
おはこんばんにちは、OkazaKirin.beam の pojiro です。
今日は Nerves の組込み Linux としての側面の紹介にトライしてみます!
組込み Linux って?
炊飯器、給湯器、車載、体温計や血圧計などなどいたるところに組込まれているコンピュータがあります。これらは各分野の要求(低消費電力や低価格など)を満たすためにリソースが限られている『マイコン』が用いられていると思います。
一方で、組込み 『Linux』 はそれらよりもリソースを用いて処理をするような分野で使われています。
どんなところで使われているのか?
私がパッと思いつくのは
- サイネージや電光掲示板1
- DSP
- ルーター
- 自動販売機
です。その他だと以前、くら寿司のお皿の画像認識の一部にラズパイ4が使われていることが話題になりました。これも組込み Linux だと想像されます。
くら寿司のCoral Edge TPU事例が出ました!!全国数百店舗で大規模に運用、お皿の画像認識部分でEdge TPUを使っています。僕も微力ながら記事化に協力させていただきました〜https://t.co/BITHt8tYkc pic.twitter.com/dlY94tpOx6
— Hayato Y (@hayatoy82) October 29, 2020
これらは、画像描画・認識、ネットワーク処理、数値演算などを1つのコンピュータで同時に担うため、マイコンよりもリソースが多いボード上に組込み Linux を構築して用いられていると考えられます。
Nervesについて
Nerves は Buildroot を用いて構成される組込み Linux です。その組込み Linux 上で Erlang VM を動作させています。本記事では Nerves の組込み Linux としての側面を紹介してみます。対象にするターゲットマシン rpi4 です。
Nerves に内包される Linux コマンド
まずはどんな Linux コマンドを持っているのか見てみます。まず which
はあるかな?
iex(1)> cmd "which" # cmd は IEx シェルから Linux コマンドを直で叩ける Nerves 独自の関数です
/bin/sh: which: not found
127
残念。which
はありません。なので代わりに type
を使います。
iex(3)> cmd "type"
0
iex(4)> cmd "type sh"
sh is /bin/sh
0
Nerves 上で使用したい Linux コマンドの有無を調べるには type
が使えます。find
もあります。
iex(5)> cmd "type find"
find is /usr/bin/find
0
しかしながら、こんなふうに調べていたら埒が開かないので、/bin
, /sbin
, /usr/bin
をまとめて調べてしまいましょう。
iex(12)> Enum.map(["/bin", "/sbin", "/usr/bin"], fn path -> cmd("ls -la #{path}/*") end)
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/umount -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/sleep -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/sh -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/rmdir -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/rm -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/pwd -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/ps -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/mv -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/mount -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/mknod -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/mkdir -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/ls -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/kill -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/grep -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/dmesg -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/df -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/dd -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/date -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/cp -> busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/cat -> busybox
-rwsr-xr-x 1 root root 280448 Nov 6 21:43 /bin/busybox
lrwxrwxrwx 1 root root 7 Nov 6 21:43 /bin/ash -> busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/uevent -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/udhcpc -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/sysctl -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/rmmod -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/reboot -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/poweroff -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/modprobe -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/modinfo -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/lsmod -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/iptunnel -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/iprule -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/iproute -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/ipneigh -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/iplink -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/ipaddr -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/ip -> ../bin/busybox
-rwxr-xr-x 1 root root 48088 Nov 6 21:43 /sbin/init
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/ifconfig -> ../bin/busybox
lrwxrwxrwx 1 root root 14 Nov 6 21:43 /sbin/halt -> ../bin/busybox
-rwxr-xr-x 1 root root 10016 Nov 6 21:43 /usr/bin/vcmailbox
-rwxr-xr-x 1 root root 46960 Nov 6 21:43 /usr/bin/vchiq_test
-rwxr-xr-x 1 root root 10016 Nov 6 21:43 /usr/bin/vcgencmd
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/unzip -> ../../bin/busybox
-rwxr-xr-x 1 root root 31320 Nov 6 21:43 /usr/bin/tvservice
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/tail -> ../../bin/busybox
-rwxr-xr-x 1 root root 10016 Nov 6 21:43 /usr/bin/slencheck
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/sha256sum -> ../../bin/busybox
-rwxr-xr-x 1 root root 14808 Nov 6 21:43 /usr/bin/rngtest
-rwxr-xr-x 1 root root 5920 Nov 6 21:43 /usr/bin/randstat
-rwxr-xr-x 1 root root 44184 Nov 6 21:43 /usr/bin/pigs
-rwxr-xr-x 1 root root 14144 Nov 6 21:43 /usr/bin/pigpiod
-rwxr-xr-x 1 root root 10016 Nov 6 21:43 /usr/bin/pig2vcd
-rwxr-xr-x 1 root root 22312 Nov 6 21:43 /usr/bin/odbcinst
-rwxr-xr-x 1 root root 10016 Nov 6 21:43 /usr/bin/odbc_config
-rwxr-xr-x 1 root root 19040 Nov 6 21:43 /usr/bin/nbtty
-rwxr-xr-x 1 root root 5363 Nov 6 21:43 /usr/bin/ldd
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/killall -> ../../bin/busybox
-rwxr-xr-x 1 root root 26408 Nov 6 21:43 /usr/bin/iusql
-rwxr-xr-x 1 root root 30512 Nov 6 21:43 /usr/bin/isql
-rwxr-xr-x 1 root root 859 Nov 6 21:43 /usr/bin/img2fwup
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/id -> ../../bin/busybox
-rwxr-xr-x 1 root root 288824 Nov 6 21:43 /usr/bin/fwup
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/free -> ../../bin/busybox
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/find -> ../../bin/busybox
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/env -> ../../bin/busybox
lrwxrwxrwx 1 root root 9 Nov 6 21:43 /usr/bin/dtparam -> dtoverlay
-rwxr-xr-x 1 root root 280 Nov 6 21:43 /usr/bin/dtoverlay-pre
-rwxr-xr-x 1 root root 282 Nov 6 21:43 /usr/bin/dtoverlay-post
-rwxr-xr-x 1 root root 30584 Nov 6 21:43 /usr/bin/dtoverlay
-rwxr-xr-x 1 root root 10016 Nov 6 21:43 /usr/bin/dtmerge
-rwxr-xr-x 1 root root 38784 Nov 6 21:43 /usr/bin/dltest
-rwxr-xr-x 1 root root 31088 Nov 6 21:43 /usr/bin/boardid
-rwxr-xr-x 1 root root 30656 Nov 6 21:43 /usr/bin/aserver
-rwxr-xr-x 1 root root 75680 Nov 6 21:43 /usr/bin/arecord
-rwxr-xr-x 1 root root 75680 Nov 6 21:43 /usr/bin/aplay
-rwxr-xr-x 1 root root 55216 Nov 6 21:43 /usr/bin/amixer
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/[[ -> ../../bin/busybox
lrwxrwxrwx 1 root root 17 Nov 6 21:43 /usr/bin/[ -> ../../bin/busybox
リストにすると多いように見えますがそこまで多くないですね。あと、busybox
へのsymlink になっているコマンドが結構あります。
Nerves は Buildroot を使って構成する組込み Linux と述べましたが、ファームウェアイメージが大きくならないよう用意されているコマンドが限られています。また、そのために busybox
も使われています。busybox
は各コマンドのバイナリサイズの総和が大きくならないよう各コマンド機能を1つに固めたバイナリです(各コマンドもオプションがフルにサポートされていません2)。そのドキュメントには
The Swiss Army Knife of Embedded Linux
と書かれており組込み Linux 向けに作られていることが分かります。かっこいいですね!
FAQ
使いたいコマンドがないのだけど、どうしたらいい?
大丈夫です。Nerves は目的に合わせカスタマイズができます。 see. Customizing Your Own Nerves System
Nerves のファイルシステム
次は mount
コマンドでファイルシステムを確認してみます。
iex(19)> cmd "mount"
/dev/root on / type squashfs (ro,relatime,errors=continue) # ルートファイルシステム
devtmpfs on /dev type devtmpfs (rw,nosuid,noexec,relatime,size=1024k,nr_inodes=430743,mode=755)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,relatime,size=377524k)
tmpfs on /run type tmpfs (rw,nosuid,nodev,noexec,relatime,size=188764k,mode=755)
/dev/mmcblk0p1 on /boot type vfat (ro,nosuid,nodev,noexec,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)
pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,relatime,size=1024k,mode=755)
cpu on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)
/dev/mmcblk0p3 on /root type f2fs (rw,lazytime,relatime,background_gc=on,discard,no_heap,user_xattr,inline_xattr,acl,inline_data,inline_dentry,flush_merge,extent_cache,mode=adaptive,active_logs=6,alloc_mode=reuse,checkpoint_merge,fsync_mode=posix,discard_unit=block)
Nerves のルートファイルシステムは SquashFS
です。
SquashFSは Read Only(ro マウントされる)であり、これが組込み機器に要求される電断対策になっています。
読み書き可能なファイルシステムですと、ファイル書き込み中に電源を断つとファイル破損が起きる可能性があります(Linux サーバが停電により起動できなくなる場合もファイル破損が起きています、電気的にサーバマシンが壊れている可能性もありますが、、)。
電断対策はいくつか選択肢(電断を検知しUPS持続中に正常終了させるなど)がありますが、Nerves はそもそもファイル書き込みをしないことによりファイル破損を起こさない選択肢をとっています。これにより Nerves は電源をぶちぶち切ることができる Linux になっています。
そうはいっても、永続化させたいデータはあるのでは?
はい、そのため /root
を rpi4 では f2fs で rw マウントしています。
このパーティションは読み書きができますので、ユーザが残したいデータを保存することができますし、 iex シェルのヒストリやvintage_net の設定等が保存されます。(「rpi4では」と書いたのは、nerves_systemu_***によって異なるからです。そうはいっても f2fs
か ext4
のいずれかとは思います。)
※ /root
書き込み中に電断が起きればファイル破損の可能性はあります。なので /root
には常にがしがし書き込むことはせず、ユーザ操作に呼応した書き込み頻度に保つことが必要かなと思います。(SD は書き込み上限回数もありますし)
Nerves のシェル
Nerves の内包する Linux コマンドの節で type
を使いました。type はシェルの built-in コマンドです。でも Nerves が使っているシェル何でしょうか?これは /etc/shells
ファイルを確認することで分かります。
iex(27)> cmd "cat /etc/shells"
/bin/ash
0
ash
です。bash
を前提に built-in コマンドを呼ぶと動作しないので、この点は注意が必要です。
Nerves の /sbin/init
Linux(カーネル) はルートファイルシステムをマウントすると
プロセス ID を 1 として init と呼ばれるプログラムを起動します。この時点がユーザー空間の開始です。
refs. Linuxシステムの仕組み p.125
Nerves はこの init プログラムを独自の erlinit
に置き換えています。
erlinit の README.md の冒頭には以下の記載があります。
This is a replacement for /sbin/init that launches an Erlang/OTP release.
Nerves は Linux のユーザー空間での処理の殆どを Erlang VM 上の Elixir の世界で実行させるコンセプトがあるため、このようにしていると想像されます。ps
コマンドを実行し /sbin/init
に紐づくプロセスを抽出すると以下になります。(捨象したプロセスはカーネル管理下にあるもの
iex(1)> cmd "ps -wlT"
S UID PID PPID VSZ RSS TTY STIME TIME CMD
S 0 1 0 2172 252 ttyS0 05:00 00:00:00 /sbin/init
S 0 72 1 2008 216 ttyS0 05:00 00:00:00 /usr/bin/nbtty /srv/erlang/erts-13.1.2/bin/erlexec -config /srv/erlang/release
S 0 80 1 5476 332 0:0 05:00 00:00:00 /usr/sbin/rngd
S 0 81 72 2008 80 0:0 05:00 00:00:00 /usr/bin/nbtty /srv/erlang/erts-13.1.2/bin/erlexec -config /srv/erlang/release
S 0 82 81 2497m 116m pts0 05:00 00:00:00 /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbwt none -sbwtdc
S 0 83 81 2497m 116m pts0 05:00 00:00:00 {sys_sig_dispatc} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 84 81 2497m 116m pts0 05:00 00:00:00 {sys_msg_dispatc} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 85 81 2497m 116m pts0 05:00 00:00:00 {async_1} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbwt no
S 0 87 81 2497m 116m pts0 05:00 00:00:00 {1_scheduler} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbw
S 0 88 81 2497m 116m pts0 05:00 00:00:00 {2_scheduler} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbw
R 0 89 81 2497m 116m pts0 05:00 00:00:01 {3_scheduler} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbw
S 0 90 81 2497m 116m pts0 05:00 00:00:00 {4_scheduler} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbw
S 0 91 81 2497m 116m pts0 05:00 00:00:00 {1_dirty_cpu_sch} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 92 81 2497m 116m pts0 05:00 00:00:00 {2_dirty_cpu_sch} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 93 81 2497m 116m pts0 05:00 00:00:00 {3_dirty_cpu_sch} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 94 81 2497m 116m pts0 05:00 00:00:00 {4_dirty_cpu_sch} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 95 81 2497m 116m pts0 05:00 00:00:00 {1_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 96 81 2497m 116m pts0 05:00 00:00:00 {2_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 97 81 2497m 116m pts0 05:00 00:00:00 {3_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 98 81 2497m 116m pts0 05:00 00:00:00 {4_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 99 81 2497m 116m pts0 05:00 00:00:00 {5_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 100 81 2497m 116m pts0 05:00 00:00:00 {6_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 101 81 2497m 116m pts0 05:00 00:00:00 {7_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 102 81 2497m 116m pts0 05:00 00:00:00 {8_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 103 81 2497m 116m pts0 05:00 00:00:00 {9_dirty_io_sche} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 104 81 2497m 116m pts0 05:00 00:00:00 {10_dirty_io_sch} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp
S 0 105 81 2497m 116m pts0 05:00 00:00:00 {1_aux} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbwt none
S 0 106 81 2497m 116m pts0 05:00 00:00:00 {0_poller} /srv/erlang/erts-13.1.2/bin/beam.smp -Bc -C multi_time_warp -sbwt n
S 0 86 82 2144 1168 0:0 05:00 00:00:00 erl_child_setup 1024
S 0 112 86 2008 224 0:0 05:00 00:00:00 heart -pid 82 -ht 30
S 0 115 86 2176 1024 0:0 05:00 00:00:00 /srv/erlang/lib/nerves_uevent-0.1.0/priv/uevent modprobe
S 0 122 86 2000 212 0:0 05:00 00:00:00 /srv/erlang/lib/nerves_logging-0.2.0/priv/kmsg_tailer
S 0 125 86 2192 256 0:0 05:00 00:00:00 /srv/erlang/lib/vintage_net-0.12.2/priv/if_monitor
S 0 139 86 2008 216 0:0 05:00 00:00:00 /srv/erlang/lib/muontrap-1.1.0/priv/muontrap -- /usr/sbin/ntpd -n -S /srv/erla
S 0 140 139 2396 280 0:0 05:00 00:00:00 /usr/sbin/ntpd -n -S /srv/erlang/lib/nerves_time-0.4.5/priv/ntpd_script -p 0.p
S 0 270 86 2008 204 0:0 05:00 00:00:00 /srv/erlang/lib/muontrap-1.1.0/priv/muontrap -- /usr/sbin/wpa_supplicant -i wl
S 0 271 270 8432 6580 0:0 05:00 00:00:00 /usr/sbin/wpa_supplicant -i wlan0 -Dnl80211,wext -c /tmp/vintage_net/wpa_suppl
S 0 274 86 2008 224 0:0 05:00 00:00:00 /srv/erlang/lib/muontrap-1.1.0/priv/muontrap -- /sbin/udhcpc -f -i eth0 -x hos
S 0 275 274 2396 276 0:0 05:00 00:00:00 /sbin/udhcpc -f -i eth0 -x hostname:nerves-051f -s /srv/erlang/lib/beam_notify
R 0 297 86 2396 280 0:0 05:01 00:00:00 ps -wlT
眺めてみると、
- カーネルは
/sbin/init
(erlinit
) を PID 1 で実行 -
erlinit
は/usr/bin/nbtty
(PID 72),/usr/sbin/rngd
(PID 80) を実行 -
nbtty
は/srv/erlang/erts-13.1.2/bin/erlexec
(PID 81)を実行 -
erl_child_setup
(PID 86)はheart
,ntpd
,wpa_supplicant
,udhcpc
を実行
のようなことが分かります。
- heart は nerves_heartで、Erlang VM 起動時に VM 自身の死活監視をするプロセスとして実行されます。
-
ntpd
はnerves_timeにより muontrap でくるまれて呼び出され時刻合わせをします。 -
wpa_suppulicant
,udhcpc
は vintage_net 関連により muontrap にくるまれて呼び出されています。それぞれ WiFiとDHCPクライアント をつかさどっています。
プロセスについて今の私の知識で分かることはここまでです。
分かっていないこととして beam.smp
を引数付きで実行しているプロセス達です。この調査は今後の私の課題です🦾
2022/12/22 追記: erlinit が起動する beam.smp プロセスについて を書きました。
まとめ?
発散感がありますが、組込み Linux としての Nerves の側面を紹介してみました。
Nerves は組込み Linux 開発のゲームチェンジャーだと私は思っています。これからも Nerves をやっていきましょう!
この記事は以前調べてまとめた「Nerves tips, /bin/sh & nerves_heart」も参考にしています。興味があればそっちも見ていただければ思います〜。
明日は @myasu の担当日です!お楽しみに〜!