この記事の前提
- C-5〜C-7で見た「カーネルがrootfsをマウントし、PID 1が起動する」流れの続きです
- systemdを採用した構成(Raspberry Pi OS等)を例にします。BusyBox initや独自initの製品もあります
- コマンド・設定例は説明用です。パスやユニット名はディストリビューションとOS世代で異なります
はじめに
第2部の最終回です。ここまでで電源ONからPID 1までの流れ(C-5、C-6)と、ハード構成の伝え方(C-7)を見てきました。今回はユーザーランド側――rootfsの中身と、自作アプリを「電源を入れれば勝手に動く」製品らしい姿にする方法です。
ネットの記事では今もrc.localへ書く方法を見かけますが、本記事ではsystemdのサービス化を本筋とし、rc.localは補足に留めます。
rootfsには何が入っているのか
rootfs(ルートファイルシステム)は、カーネルが/としてマウントするファイルシステムです。その後、/procや/sysなどの仮想ファイルシステム、別パーティション、tmpfsなどが重ねてマウントされ、ユーザーからは「/以下の一つの木」に見えます。組み込みで意識したいのは、実体がどこにあり、どこへ書き込みが発生するかです。
| ディレクトリ | 中身 | 書き込みの傾向 |
|---|---|---|
| /bin、/usr | コマンド、ライブラリ、アプリ | 基本読むだけ。/binが/usr/binへのリンクになっている構成も多い |
| /etc | 設定ファイル | 設定変更時のみ |
| /var | ログ、スプール、DB | 書き込みが発生しやすい |
| /home | ユーザーデータ | 用途次第 |
| /tmp | 一時ファイル | 頻繁。消えてよければtmpfs化しやすい |
| /proc、/sys | カーネル情報を見せる仮想ファイルシステム | ストレージ上の通常ファイルではない |
これは典型的な傾向であり、絶対的な分類ではありません。たとえばパッケージ更新中は/usrにも書きますし、アプリの作りによっては/homeへログを出していることもあります。実機ではfindmntでマウント構成を、journalctlやlsofなどで実際の書き込み先を確認します。
同じ「Linuxのrootfs」でも、Debian系フルセット(Raspberry Pi OS等)から、BusyBoxで小さく絞った最小構成(Yocto等で作る。→C-10)まで振れ幅があります。製品づくりとは、この中から要るものだけを選び、書き込み場所を管理することだと言えます。
自作アプリをsystemdサービスにする
例として、センサー値を定期記録する常駐アプリtemploggerを自動起動にしてみます。
1. ユニットファイルを書く
# /etc/systemd/system/templogger.service
[Unit]
Description=Temperature logger
After=network.target
[Service]
Type=simple
ExecStart=/opt/templogger/templogger.py
Restart=on-failure
RestartSec=5
User=applog
[Install]
WantedBy=multi-user.target
2. 有効化して動かす
sudo systemctl daemon-reload
sudo systemd-analyze verify /etc/systemd/system/templogger.service
sudo systemctl enable --now templogger # 自動起動ON+即時起動
systemctl status templogger # 状態確認
journalctl -u templogger -f # このサービスのログを追尾
これだけで、次のものが手に入ります。
-
電源ONで自動起動(
enable) -
異常終了時の自動再起動(
Restart=)。常駐アプリでは有力な選択肢です -
起動順序の制御(
After=) -
ログの一元管理(journald)。標準出力への出力がそのまま
journalctlで読めます -
専用ユーザーでの実行(
User=)。事故の被害範囲を絞れます
この例では、事前にapplogユーザーを作り、実行ファイルとデータ保存先へ必要最小限の権限を与えておく必要があります。Pythonスクリプトを直接指定する場合は、先頭のshebangと実行権限も必要です。ユニットの文法はsystemd-analyze verifyで確認できますが、アプリの権限や実行時の通信までは検証してくれません。
各ディレクティブの正確な仕様はsystemd.serviceのマニュアルで確認できます。
「ネットワークが使えてから起動」の注意
After=network.targetは起動順序を決めるだけで、IPアドレスが付いたことや、接続先へ到達できることまでは保証しません。network-online.targetをAfter=とWants=の両方に指定すれば、ネットワーク管理ソフトが定義する「online」まで待てますが、その判定内容は環境依存です。現場でのリンク断やDHCPの遅延は起動後にも起きるため、通信するアプリ自身がタイムアウトと再接続を持つ方が堅くなります。
rc.localではダメなのか(補足)
rc.localに書けば起動時に走りますが、自動再起動、順序制御、ログ管理のない「言いっ放し」です。試作の一時しのぎには使えても、製品の常駐アプリは管理機能ごとsystemdへ任せる方が確実です。そもそも近年のディストリビューションではrc.localが標準無効の場合もあります。
「いきなり電源断」に耐える:読み取り専用化の入口
C-1で挙げた組み込みの宿命「電源はいきなり切られることがある」に対する定石のひとつが、rootfsの読み取り専用(RO)運用です。書き込み中の電源断がファイルシステム破損の主要因なので、そもそも書かなければ壊れにくい、という考え方です。
もちろんRO化だけで、ストレージ故障や書き込み用領域のデータ不整合まで防げるわけではありません。守る範囲を小さくし、復旧方法を単純にするための設計です。
本格的な設計(overlayfsの構成、A/B面更新など)は本シリーズの範囲を超えるため、考え方と入口だけ紹介します。
-
方針:/はROでマウントし、書き込みが必要な場所だけ分離する
- /tmpやログなど、消えてよいもの → tmpfs(RAM上)
- 設定・データ → 別パーティションに分けて書き込みを局所化
-
Raspberry Piの入口:
raspi-configのPerformance Options→Overlay File Systemに、rootfsをRO化してRAM上のoverlayと組み合わせる機能があります。まず試すならここからです(メニュー構成はOS世代で変わることがあります) - アプリ側の作法:書き込み先を設定で切り替えられるようにしておく。ログをjournaldへ集約すると分離しやすくなりますが、ジャーナルをRAMだけに置くか永続化するかは製品要件に合わせて決めます
「どこに書き込みが発生するか」を冒頭の図で押さえておくと、この設計がすっと入ってきます。
ハマりどころ
-
enableし忘れ:startで動作確認して満足し、再起動したら動かない――定番です。systemctl is-enabled temploggerで確認する癖を -
ExecStartは絶対パスで書く:シェルのPATHは期待できません。スクリプトならshebangと実行権限も確認します -
環境変数が「ログイン時と違う」:サービスは
.bashrcを読みません。必要な環境変数はEnvironment=かEnvironmentFile=で明示します -
SDカードへの書き込み過多:ログを無制限に書くとカード寿命を削ります。journaldは
/etc/systemd/journald.confのSystemMaxUse=等で使用量に上限を設定できます(journald.confのマニュアル) -
再起動ループに気づかない:
Restart=は便利な半面、即死→再起動を繰り返すことがあります。systemctl statusとjournalctl -uで確認し、必要ならStartLimitIntervalSec=とStartLimitBurst=も設計します
現場コラム:「デモは動く、納品先で動かない」
自動起動まわりは「自分のデスクでは完璧、現地で動かない」が起きやすい領域です。原因はだいたい環境差――ネットワークが上がる順序、NTP同期前の時刻、挿さっていると思っていたUSB機器、ログイン環境前提の環境変数。
対策として有効なのが 「電源断リハーサル」(所謂、電源ぶち切り対策) です。復旧用イメージとデータのバックアップを用意した評価機で、アプリの書き込み中や起動途中など条件を変えて電源断→再投入を試します。rootfs破損、起動順序の競合、再起動ループといった問題を、出荷前にあぶり出すための試験です。
journalctl --list-bootsを使えば起動単位でログを比較できます。ただし、ジャーナルが揮発設定なら前回起動のログは残りません。電源断試験では、保存先の耐久性と「障害直前のログをどこまで残したいか」をセットで決めておきます。
既存サービスを教材にする
手元のLinuxボードで、いま動いているサービスと自動起動の設定を眺めてみてください。
systemctl list-units --type=service --state=running
systemctl list-unit-files --type=service --state=enabled
「誰が・何を・どの順で起動しているか」が見えると、自作アプリをどこへぶら下げるべきかも見えてきます。
まとめと次回(第2部完)
- rootfsは「/以下の全部」。製品視点ではどこに書き込みが発生するかの把握が肝
- 自作アプリはsystemdサービス化:自動起動・自動再起動・順序制御・ログ一元化が数行で手に入る
- ネットワーク待ちは
network-online.target+Wants=か、アプリ側リトライで - 電源断対策の方向性はRO化+書き込み場所の分離。Raspberry PiならOverlay File Systemが入口
これで第2部「起動の中身」は完結です。電源ONからアプリ常駐まで、一通り自分の手で追えるようになりました。
次回からの第3部は、必要な読者が選んで読む深掘り編です。CPU搭載FPGA SoC(Zynq-7000、Cyclone V SoC、Arria 10 SoC等)とYoctoの世界へ進みます。まずC-9で「プロセッサ側とロジック側の境界」からです。
