組み込みシステム構築の定石

本章では、組み込みシステムを構築する際の定石を紹介します。

8.1. 起動時にコマンドを自動実行する

システム起動時にコマンドを自動的に実行する方法には、以下の二種類があります。

  1. 初期化スクリプトとして登録する。
  2. inittabに登録する。

本章では、これらの方法について説明します。

8.1.1. 初期化スクリプトで自動実行する(一時的な方法)

起動時に特定のコマンドを自動実行したい場合、コマンドをスクリプトに記述し、それを初期化スクリプトとして登録[55]します。

初期化スクリプトは、以下の順番で実行されます。

  1. /etc/init.d/rcファイル。
  2. /etc/rc.d/ディレクトリにあるファイル。
  3. /etc/config/rc.localファイル。

通常、/etc/init.d/rcファイルは固定で、/etc/rc.dディレクトリ内のファイルや/etc/config/rc.localをシステムに合わせて変更します。

Armadilloでは、ルートファイルシステムにRAMディスクを置いているため、/etc/rc.dディレクトリのファイルをArmadillo上で変更しても、次回起動時にはその変更は失われます。/etc/rc.dディレクトリの内容を変更したい場合には、毎回ルートファイルシステムイメージを作成しなおさなければならないため、開発の初期段階などファイルの変更が頻繁にある場合、効率が良くありません。

一方で、/etc/configディレクトリの内容は、flatfsdコマンドでフラッシュメモリのコンフィグ領域に保存することができ、起動時に復元されます[56]。そのため、/etc/config/rc.localを使うと、ルートファイルシステムイメージを更新せずに自動実行するコマンドを変更することができます。

例として、Armadillo-440の標準イメージの/etc/config/rc.localは以下のようになっています。

#!/bin/sh

. /etc/init.d/functions

PATH=/bin:/sbin:/usr/bin:/usr/sbin

echo -n "Starting functester: "
export TZ=JST-9
DISPLAY=:0 functester > /dev/null 2>&1 &
check_status

図8.1 Armadillo-440の標準イメージの/etc/config/rc.local


図8.1「Armadillo-440の標準イメージの/etc/config/rc.local」では、functesterコマンド[57]をバックグラウンドプロセスとして実行しています。

/etc/config/rc.localがない場合は、ファイルを新規作成し、実行権限を付けることで起動時に自動で実行されます。

/etc/config/rc.localを新規作成したり、編集した場合、その内容をフラッシュメモリのコンフィグ領域に保存しなければなりません。コンフィグ領域に保存するには、flatfsdコマンドを使用します。

/etc/config/rc.localファイルを扱うコマンドは、以下のようになります。

[armadillo ~]$ vi /etc/config/rc.local 1
[armadillo ~]$ chmod +x /etc/config/rc.local  2
[armadillo ~]$ flatfsd -s  3

図8.2 /etc/config/rc.localファイルを変更しフラッシュメモリに保存する


1

rc.localファイルを新規作成または編集します。

2

実行権限を付けます。

3

フラッシュメモリに保存します。

8.1.2. 初期化スクリプトで自動実行する(恒久的な方法)

/etc/config/rc.localへの変更は一時的なものです。開発の最終段階でルートファイルシステムのイメージを固める時には、自動実行するスクリプトは、/etc/rc.dディレクトリに登録します。

/etc/rc.dディレクトリのファイルはSで始まり、文字コード順でソートした場合に先にくるものから順番に実行されます。通常、実行される順番が分かりやすいように、Sの後には二桁の数値文字が続きます。

大抵の場合、/etc/rc.dディレクトリのファイルは、/etc/init.dディレクトリのファイルへのシンボリックリンクになっています。そのため、スクリプト本体のファイル名は固定で、シンボリックリンクの名前を変更するだけで、スクリプトの実行順番を変えることができます。

初期化スクリプトの実行順番には、注意が必要です。例えば、ネットワークの設定を行う前にWebサーバーを起動してはいけません。

また、バックグラウンドプロセスやデーモンとして起動したプロセスの実行順番には、より注意が必要です。バックグラウンドプロセスやデーモンを実行するコマンドは、プロセスを起動するとすぐに戻ってきます。そのため、後続のコマンドを実行する際に、それらのプロセスが行う処理が完了しているとは限りません。

Armadillo-420やArmadillo-440の標準イメージでは、/etc/rc.dディレクトリ内の初期化スクリプトは、以下の順番で実行されます。

  1. ファイルやディレクトリの準備

    1. S03udevd:udevdの起動とデバイスノードの作成。
    2. S04flatfsd:コンフィグ領域に保存した内容を/etc/configディレクトリに復元。
    3. S05checkroot:重要なファイルやディレクトリの権限の設定。
    4. S06checkftp:FTPサーバーが使用する/home/ftpディレクトリの設定。
  2. ログデーモンの起動

    1. S10syslogd:syslogデーモンの起動。
    2. S20klogd:カーネルログデーモンの起動。
  3. ネットワークの設定

    1. S30firewall:ファイヤーウォール(iptables)の設定。
    2. S30hostname:ホストネームの設定。
    3. S40networking:ネットワークインターフェースの有効化。
  4. 各種ネットワークサーバの起動

    1. S60inetd:inetd(スーパーサーバー)の起動。
    2. S70at-cgi:at-cgi[58]で必要なファイルの設定。
    3. S70lighttpd:lighttpd(Webサーバー)の起動。
    4. S71avahi:avahi-daemon(Zeroconfサーバー)の起動。
  5. その他

    1. S99misc:/home/ftp/pubディレクトリをramfsでマウント。
  6. rc.localの実行

    1. S99rc.local:/etc/config/rc.localファイルが存在し、実行可能なファイルであれば実行。

新しく初期化スクリプトを追加する場合、どの順番でおこなうべきか、良く検討してください。

Atmark Distで作成するルートファイルシステムに初期化スクリプトを追加するには、以下のような手順でおこないます。

  1. プロダクトディレクトリ以下のetc/init.dディレクトリに初期化スクリプトを追加する。
  2. rc.dディレクトリのシンボリックリンクを作成するようプロダクトディレクトリ以下のMakefileを修正する。

Atmark Distのプロダクトディレクトリ以下のetc/init.dディレクトリにファイルを追加すると、Armadilloのルートファイルシステムの/etc/init.dディレクトリにファイルが追加されます。

また、Atmark DistのプロダクトディレクトリのMakefileに、Armadilloのルートファイルシステムの/etc/init.dディレクトリのファイルへのシンボリックリンクを/etc/rc.dディレクトリに作成するよう記述しておくと、make romfsを実行時に、シンボリックリンクが作成されます。

以下にMakefileの変更箇所を示します。romfsターゲットで実行するコマンドに、$(ROMFSINST)コマンド[59]を追加します。

romfs:
        @rm -f etc/DISTNAME
(中略)⏎
        $(ROMFSINST) -s /etc/init.d/misc /etc/rc.d/S99misc
        [ "$(CONFIG_USER_FLATFSD_FLATFSD)" != "y" ] || \
        $(ROMFSINST) -s /etc/init.d/rc.local /etc/rc.d/S99rc.local
        この行にシンボリックリンクを作成するコマンドを追加する

図8.3 /etc/rc.dディレクトリにシンボリックリンクを追加するための変更箇所


Atmark Distへの修正をArmadilloに反映するには、通常通り、makeコマンドを実行してユーザーランドのルートファイルシステムイメージ(romfs.img.gz)を作成しなおし、Armadilloのフラッシュメモリのユーザーランド領域に書き込んだ後、再起動してください。

8.1.3. inittabで自動実行する

起動時に自動実行するコマンドは、inittabに記述することもできます。

第1部「Armadilloが動作する仕組み」の「ユーザーランドの初期化処理」にも記述したように、inittabのactionに指定できる値のうち、起動時にコマンドを実行するアクションは、sysinitとrespawnです。

sysinitアクションのコマンドには、通常、初期化スクリプトの/etc/init.d/rcを実行するよう記述します。

respawnアクションに記述されたコマンドは、sysinitアクションに記述されたコマンドが完了したあと、すなわち、初期化スクリプトがすべて完了した後に実行されます。また、そのコマンドによって起動されたプロセスが終了すると、そのコマンドを再度実行します。

Atmark Distで作成するルートファイルシステムの/etc/inittabを変更するには、Atmark Distのプロダクトディレクトリ以下のetc/inittabファイルを変更します。

[ティップ]respawnアクションの実行間隔

Atmark Distに含まれるbusyboxのinitでは、inittabのrespawnアクションで指定されたコマンドが起動したプロセスが、起動してから5秒以内に終了した場合、約5秒後にコマンドを再度実行するようになっています。

また、その状況が5回続くと、次のコマンドの実行はプロセスが終了してから約5分後に設定されます。

ですので、すぐに終了するようなプログラムはinittabに登録するには向いていません。次章で紹介するcronの利用を検討してください。

[ティップ]一時的なinittab

Atmark Distに登録されているbusyboxのinitは/etc/inittabのほかに/etc/config/inittabも読み込みます。

そのため、開発初期段階で一時的に使用する初期化スクリプトとして/etc/config/rc.localを使用したように、一時的に使用するinittabとして/etc/config/inittabを使用することができます。

なお、/etc/config/inittabには、respawnアクションのみ指定することができます。その他のアクションを指定しても実行されません。

8.2. 定期的にコマンドを実行する

前章では、起動時にコマンドを実行する方法を紹介しました。本章では、指定した時刻に定期的にコマンドを実行する方法として、cronを使う方法を紹介します。

8.2.1. cronで定期実行する

cronは、指定した時刻に指定したコマンドを実行するための仕組みです。crontabと呼ばれる設定ファイルに記述された内容に従って、crondがコマンドを実行します。

コマンドを実行する時刻は分単位で指定可能で、毎分実行するという設定もできるので、間隔が比較的長い周期的な処理を行うこともできます。

crondの設定ファイルであるcrontabファイルは、crontabコマンドを使用して作成することができます。

crontabファイルには1行に付き、1つの設定を記述します。1つの設定は、スペースまたはタブによって区切られた、複数のフィールドを持ちます。各行のフォーマットは以下のとおりです。

分  時  月内日  月  曜日  コマンド

図8.4 crontabファイルのフォーマット


crondは、毎分crontabファイルの内容を調べます。分、時、月が現在のシステム時刻と一致し、かつ、月内日または曜日のいずれかが現在のシステム時刻と一致すれば、コマンドが実行されます。

それぞれのフィールドには以下の内容を指定できます。

表8.1 crontabファイルのフィールド

フィールド 指定可能な値

0-59

0-23

月内日

1-31

1-12(もしくは名前[a])

曜日

0-7(0と7は日曜日、もしくは名前[b])

[a] 「月」フィールドには、月の名前の最初の3文字を使用することができます。(例.Jan)

[b] 「曜日」フィールドには、曜日の名前の最初の3文字を使用することができます。(例.Sun)


各フィールドには、アスタリスク(*)も指定できます。アスタリスクを指定した場合、そのフィールドが取りうるすべての値を指定したことになります。また、上記の指定可能な値以外にも高度な設定が可能です。詳細はman 1 crontabman 5 crontabを参照してください。

Armadillo-400シリーズの標準イメージでは、crondはユーザーランドのルートファイルシステムには含まれていますが、自動起動するようになっていません。そのため、crontabファイルも含まれていません。Atmark Distで作成するルートファイルシステムにcrontabファイルを追加し、crondを自動起動するには、以下のような手順でおこないます。

  1. プロダクトディレクトリにcrontabファイルを追加する。
  2. crontabファイルをルートファイルシステムに含めるよう、プロダクトディレクトリ以下のMakefileを修正する。
  3. crondを起動する初期化スクリプトをプロダクトディレクトリに追加する。
  4. crondを起動する初期化スクリプトを自動実行するよう、プロダクトディレクトリ以下のMakefileを修正する。

まず、Atmark Distのプロダクトディレクトリに、crontabを追加します。

Armadilloの標準イメージに含まれるcrondは、BusyBoxのものです。BusyBoxのcrondは、/var/spool/cron/crontabs/[ユーザー 名]crontabとして読み込みます。

このファイルは、Armadillo-420とArmadillo-440の標準イメージにはありませんので、新しく追加する必要があります。rootユーザー用のcrontabファイルは、プロダクトディレクトリ以下のvar/spool/cron/crontabs/rootというファイル名で追加します。

crontabファイルをルートファイルシステムイメージに追加するには、プロダクトディレクトリ以下のMakefileを修正する必要があります。具体的な変更方法については「ファイルの追加、変更」を参照してください。

crondを起動する初期化スクリプトは、以下のようになります。この起動スクリプトをプロダクトディレクトリ以下のetc/init.d/crondというファイル名で追加します。

#!/bin/sh

. /etc/init.d/functions

PATH=/bin:/sbin:/usr/bin:/usr/sbin

echo -n "Starting crond: "
crond
check_status

図8.5 crondを起動する初期化スクリプト


図8.5「crondを起動する初期化スクリプト」が起動時に実行されるようにMakefileを修正する方法は、「初期化スクリプトで自動実行する(恒久的な方法)」を参照してください。

8.3. 不具合発生時の自動再起動

一度起動したシステムがいつまでも元気に動き続けるという保証はありません。システムが正常に動かなくなる原因には、ソフトウェアのバグやハードウェアの故障などが考えられます。

システムは停止することがある、ということを前提として、停止した場合の対処についても、システム設計時に考慮しておかなければなりません。ここでは、問題が発生した場合、自動的に再起動する方法について説明します。

8.3.1. initによるプロセスの自動再起動

「inittabで自動実行する」で紹介したように、inittabのrespawnアクションによって起動されたプロセスがなんらかの理由により意図せず終了してしまった場合、initが自動的にそのプロセスを再起動します。

プロセスで起動時や一定時間毎にログを出力しておけば、いつ頃異常終了したかを特定できるので、障害解析時に重要な情報となります。

8.3.2. ウォッチドッグタイマーによるシステムの再起動

アプリケーションプログラムのプロセスが異常終了したのであれば、initによってそのことを検出し、再起動することができます。しかし、カーネルがハングアップ(停止)してしまった場合、ソフトウェア的にそのことを検出する手段はありません。

システム全体がハングアップしてしまった場合、そのことを検出するための仕組みとして、ウォッチドッグタイマーがあります。

ウォッチドッグタイマーは、有効にされると内部のタイマーのカウントを開始します。システム側は、正常に動作している間はウォッチドッグタイマーに対して、定期的に信号を送ります[60]。ウォッチドッグタイマーは、信号を受け取るとタイマーのカウントを最初からやり直します。もし、システム側に問題が発生し、信号を送ることができなくなったら、ウォッチドッグタイマーがタイムアウトし、システムのリセットをおこないます。

Armadillo-400シリーズでは、標準でi.MX25が持つハードウェアウォッチドッグタイマーが有効になっています。

Hermit-Atブートローダーによって、ウォッチドッグタイマーのタイムアウト時間が10秒に設定されてから、Linuxカーネルが起動されます。カーネルは、自動でウォッチドッグタイマーをキックします。

もし、何らかの要因でカーネルがハングアップしてウォッチドッグタイマーをキックできなくなりタイムアウトが発生すると、システムが再起動します。

Linuxカーネルに手を入れるようなことがなければ、通常、カーネルのハングアップに気を配る必要はありませんが、このような仕組みがArmadillo-400シリーズには組み込まれていることを覚えておいてください。

8.4. ログ管理

Linuxシステムでは、ログの管理はsyslogでおこなうことが一般的です。

各アプリケーションプログラムは、loggerコマンドやC言語のsyslog関数で、ログ記録用プログラムであるsyslogデーモンにメッセージを送ります。syslogデーモンは、送られてきたメッセージをファイルに記録したり、別サーバーへの転送、ログファイルのローテーションなど、ログの管理を一括して行います。

syslogデーモンには、オリジナルのsyslogdの他に、いくつかのバリエーションがあります。

Atmark Distで作成したユーザーランドの場合、syslogデーモンとしてBusyBoxのsyslogdを使用します。

BusyBoxのsyslogdは機能が基本的なものに限られているため、設定ファイルを持たず、設定はすべてコマンドラインオプションで指定します。

Armadillo-400シリーズの標準設定では、初期化スクリプトの/etc/rc.d/S10syslogd(/etc/init.d/syslogdへのシンボリックリンク)でsyslogdを起動しています。ログは、/var/log/messagesへ書き込まれます。

Debian GNU/Linux 5.0(コードネーム "lenny")では、rsyslogdが標準です。rsyslogdの設定方法については、「man rsyslogd」や「man rsyslog.conf」を参照してください。

8.4.1. ログファイルのローテーション

ログファイルにログを書き込み続けると、ファイルサイズがどんどん大きくなり、いずれストレージの限界に達してしまいます。このような事態を避けるため、通常、ログファイルのローテーションをおこないます。ログファイルのローテーションは、一定の期間(1日や1週間など)や、一定のサイズごとに行います。

BusyBoxのsyslogdでは、サイズ毎のローテーションをサポートしています。

ログのローテートに関連するオプションは、-s SIZE-b NUMです。SIZE [KB]になる前にログファイルをローテートします。-sオプションを指定しない場合のSIZEのデフォルト値は200[KB]です。また、ローテートされたファイルをNUM個分保持します。-bオプションを指定しない場合のデフォルト値は、1です。

Armadillo-400シリーズの標準設定では、-s-bオプションを指定していないので、200[KB]になる前にローテートが行われ、過去のログファイルを1個だけ保持します。最新のログは/var/log/messagesに書き込まれ、一つ過去のログファイルは/var/log/messages.0となります。

8.4.2. ログをリモートサーバーに送る

BusyBoxのsyslogdは、別のサーバーで動作しているsyslogデーモンへメッセージを転送できます。

メッセージの転送も、コマンドラインオプションで指定します。メッセージの転送を指定するオプションは、-R HOST[:PORT]です。HOSTには、転送先サーバーのIPアドレスかホスト名を指定します。PORTには、転送先サーバーのポート番号と転送に使用するプロトコルを指定します。デフォルトの値は、514/UDPで、UDPプロトコルで514番ポートに転送します。プロトコルには、TCPも指定できます。UDPは到達保証のないプロトコルですので、TCPを使うのが良いでしょう。

具体的な設定方法を、ArmadilloからATDE3にログを転送する場合を例として説明します。ATDE3のIPアドレスは192.168.0.1となっているとします。メッセージの待ち受けには、TCPの514番ポートを使用します。

まず、ATDE3側の設定をおこないます。/etc/rsyslog.confのUDPとTCPの待ち受けに関する設定を有効にします。/etc/rsyslog.confの編集は、sudoコマンドを使い、特権ユーザー権限で行う必要がありますので、注意してください。

# provides UDP syslog reception
#$ModLoad imudp
#$UDPServerRun 514

# provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514

を以下のように、修正します。

# provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514

# provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514

設定を反映させるために、rsyslogdを再起動します。Debian GNU/Linuxでは、サーバーの起動、停止、再起動などは、/etc/init.d/ディレクトリ以下のスクリプトでおこないます。

[ATDE ~]$ sudo /etc/init.d/rsyslog restart

次に、Armadilloのsyslogdを、-Rオプション付きで起動します。既にsyslogdが起動しているので、一度終了させてから再起動します。

[armadillo ~]$ ps | grep syslog
  334 root        484 S   syslogd -L
[armadillo ~]$ kill 334
[armadillo ~]$ syslogd -L -R 192.168.0.1:514/TCP

以上の設定を行うと、ArmadilloでloggerコマンドまたはC言語のsyslog関数で送ったメッセージが、ATDE3のrsyslodによって記録されます。

8.5. 外部ストレージのデータを守る

Armadilloでは、USBメモリやSD/microSDカードなどの外部ストレージを使用することができます。これらのストレージデバイスは、大容量のデータを保存するために大変便利ですが、扱い方に注意が必要です。

本章では、外部ストレージのデータを安全に扱う方法を紹介します。

8.5.1. データがストレージに書き込まれたことを保証する

ストレージデバイスに対するデータの読み書き速度は、CPUの動作速度やメモリの読み書きの速度と比べると、非常に遅いです。そのため、Linuxシステムでは、色々な場所にバッファを設けてストレージとのデータのやりとりを効率化しています。プログラムでファイルに対して書き込みを行った後、そのデータがストレージデバイスに書き込まれたことを保証するためには、すべてのバッファされているデータをフラッシュ(書き出し)しなければなりません。

ストリームに対するフラッシュは、fflushライブラリ関数でおこないます。しかし、fflushライブラリ関数は、ユーザー空間でバッファされているデータをフラッシュするだけで、カーネル空間でバッファされているデータはフラッシュされませんので、これでは不十分です。

syncシステムコールはカーネル空間のバッファをフラッシュします。syncシステムコールは、デバイスへの書き込みが終了するまで返ってきません。そのため、このシステムコールを使用すると、データの書き込みを保証できるように思われます。しかし、最近のストレージデバイスは、デバイス側で大きなキャッシュを持っているため、syncシステムコールから返ってきても、データは実際にはストレージデバイスに書き込まれていないかもしれません。syncコマンドでも同様です。

データがストレージに書き込まれたことを保証する最も確実な方法は、umountシステムコールかumountコマンドで、デバイスをアンマウントすることです。アンマウントが完了した時点で、すべてのデータがストレージデバイスに書き込まれていることが保証されます。

8.5.2. 不意な電源断への対応

組み込みシステムでは、予期せぬタイミングで電源が遮断される状況への対応は必須とも言えるでしょう。特に、ストレージにデータを書き込みしている最中に電源断が発生すると、どうしてもデータの不整合が発生して、ファイルシステムが破壊されてしまいます。

このような状況への対処として、ジャーナリングファイルシステムを用いる方法と、ファイルシステムをリードオンリーでマウントする方法があります。

ジャーナリングファイルシステムとは、定期的にファイルシステムの状態(ジャーナル)を保存しておくことで、クラッシュが発生した場合に、ジャーナルを元に状態を復元できるファイルシステムです。ジャーナリングファイルシステムを用いると、フラッシュされていないデータがある状況で不意な電源断が発生した場合でも、少し前の正常な状態にファイルシステムを復元することができます。

Linuxシステムで標準的なジャーナリングファイルシステムは、ext3ファイルシステムです。

ext3ファイルシステムで、デバイスをフォーマットするには、mke2fsコマンドに-jオプションを付ける(Atmark Distベースのユーザーランドの場合)か、mkfs.ext3コマンド(ATDE3の場合)を使用します。

ストレージへの書き込みが必要ない場合、デバイスをリードオンリー(読み込みのみ)でマウントするのが最も確実で安全な方法です。mountシステムコールやmountコマンドには、リマウント(再マウント)オプションがあります。書き込みが必要ない場合は、リードオンリーでマウントしておき、書き込みが必要になった時だけ、読み書き可能でリマウントすることで、ファイルシステムが破壊される危険性を少なくすることができます。(もちろん、読み込みの必要すらないときは、アンマウントしておくのが一番安全です。)

ストレージに保存するデータの内容をよく吟味して、リードオンリーで済むデータがあるのならば、デバイスをリードオンリーのパーティションと書き込み可能なパーティションに分割するという方法もあります。

パーティションを分割するには、fdiskコマンドを使用します。

8.6. データ溢れを防ぐ

組み込みシステムで制限の厳しいリソースの一つが、ストレージ容量でしょう。

Armadilloでは標準のルートファイルシステムとしてRAMディスクを使用しているので、ログを出力しているだけでもルートファイルシステムの容量を使い切ってしまう、という状況に陥りがちです。

本章では、ストレージの使用状況を調べ、使用できるストレージの容量を増やす方法について説明します。

8.6.1. ストレージのサイズと使用量を調べる

システムで使用しているストレージのサイズと、その使用量を調べるにはdfコマンドを使用します。

Armadillo-440の標準イメージでdfコマンドを実行すると、以下のように表示されます。-hオプションにより、人間に読みやすい形式にしています。

[armadillo ~]# df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/ram0                26.4M     23.9M      1.2M  95% /

図8.6 Armadillo-440のストレージ使用量


この表示から、/dev/ram0(ルートファイルシステムに使用しているRAMディスク)のサイズは26.9MByteで、23.9Mbyte使用されており、残り1.2MByteなので、使用率は95%であることが読み取れます。

どのファイルやディレクトリが容量を使用しているのか調べるには、duコマンドを使用します。

[armadillo ~]# du -h -d 1 /
1.3M    /lost+found
956.0k  /sbin
215.0k  /etc
0       /sys
3.6M    /usr
2.8M    /home
28.0k   /var
1.0k    /mnt
4.0k    /dev
2.0k    /tmp
1.8M    /bin
13.2M   /lib
1.0k    /root
0       /proc
23.9M

図8.7 Armadillo-440の各ディレクトリのサイズ使用量


ルートディレクトリ直下のディレクトリの使用量を、人間に読みやすい形式で表示しています。全部で23.9Mbyte使用しているうち、半分以上の13.2MByteは/libディレクトリが使用していることが分かります。

8.6.2. ルートファイルシステムの空き容量を増やす

Atmark Distでルートファイルシステムを作成すると、ルートファイルシステムのサイズは、標準では必要最小限になるよう自動で調整されます。概ね、空き容量は10%以下となります。

ルートファイルシステムのサイズを手動で設定することで、空き容量を増やすことができます。

Atmark Distのmenuconfigの「Userland Configuration → Vendor specific→ generate file-system option」をAutoからManualにすることで、ルートファイルシステムのサイズを手動で設定できるようになります。

「Size of the image in blocks」で、ユーザーランドのサイズをブロック単位で指定します。1ブロックは1024Byteです。

「Maximum number of inodes」で、ファイルシステムに保存できるファイルやディレクトリの数を指定します。

自動でサイズ設定されたユーザーランドがどのようなパラメータを持っているかは、make時のログから確認することができます。

一度ビルド済みのatmark-distディレクトリで、make imageを実行することで、現在の設定を確認できます。以下は、Armadillo-440の標準イメージをビルドしたときの設定です。

[ATDE ~]$ make image | grep genext2fs
/usr/bin/genext2fs --size-in-blocks 27241 --number-of-inodes 1280 --squash-uids --root /home/atmark/atmark-dist/romfs --devtable ext2_devtable.txt /home/atmark/atmark-dist/images/romfs.img

図8.8 ルートファイルシステムのブロック数と inode 数の確認


--size-in-blocks」で指定されているオプションがブロック単位でのサイズで、「--number-of-inodes」で指定されているオプションがinodeの数となります。この例では、それぞれ27241と1280となっています。手動でサイズを指定する場合は、これらの値を参考にしながら、設定してください。

例として、ルートファイルシステムのサイズを約32MByte、inode数2048に設定するには、以下のようにコンフィギュレーションをおこないます。

ルートファイルシステムサイズの手動設定

図8.9 ルートファイルシステムサイズの手動設定


コンフィギュレーションを変更したあと、makeまたはmake imageを実行すると、設定したサイズでイメージが生成されます。あとは、通常と同様にromfs.img.gzをArmadilloのユーザーランド領域に書き込んで再起動すると、ルートファイルシステムのサイズが約32MByteになります。

[armadillo ~]# df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/ram0                31.7M     24.1M      6.0M  80% /

図8.10 手動で設定したルートファイルシステムのストレージ使用量


[ティップ]ルートファイルシステムの最大サイズ

ルートファイルシステムはRAMディスクのサイズより大きくすることはできません。

RAMディスクのサイズは、カーネルの設定で決まります。Armadilloの標準設定では、32MByteとなっています。

RAMディスクのサイズを増やすには、カーネルコンフィギュレーションの「Linux Kernel Configuration → Device Drivers → Block devices →Default RAM disk size (kbytes)」を変更してください。

8.6.3. 一時RAMファイルシステムを使用する

ログローテーションをおこないログファイルを保存するなど、使用するファイルサイズがあらかじめ計算できる場合は、ルートファイルシステムのサイズを変更して空き容量を増やすことで対応できます。しかし、ArmadilloがFTPサーバーになっていて最大サイズの分からないファイルを受信する場合など、使用するサイズが計算できない場合は、いくらルートファイルシステムのサイズを大きくしておいても、容量不足の懸念が残ります。

そのような場合は、ルートファイルシステム以外に、別のファイルシステムを用意することで対応します。最大サイズが計算できないファイルをルートファイルシステムとは別のファイルシステムに保存することで、そのファイルシステムが容量不足になったとしても、システム全体が動作不能になる事態は避けることができます。

通常のPCやサーバーなどのLinuxシステムでは、HDDのパーテーションを分けることで、ファイルシステムを分割します。Armadillo-400シリーズでは、microSDが使えますので、それを使用するのも良いでしょう。

microSDが使えない場合は、一時RAMファイルシステム(tmpfs)を使うことができます。tmpfsは、RAMディスクと同様に、RAMの一部をストレージとして使用する機能です。

最大1MByteのRAMをtmpfsに割り当て、/mntディレクトリにマウントするには、以下のようにします。

[armadillo ~]# mount -t tmpfs -o size=1m tmpfs /mnt/

図8.11 tmpfs をマウントする


tmpfsでマウントした/mntディレクトリには、ルートファイルシステムにあるファイルとは別に、合計1MByteまでのファイルやディレクトリを置くことができます。

mountコマンドをオプションを指定せずに実行すると、現在マウントされているすべてのディレクトリを確認することができます。

[armadillo ~]# mount
/dev/ram0 on / type ext2 (rw)
proc on /proc type proc (rw)
usbfs on /proc/bus/usb type usbfs (rw)
sysfs on /sys type sysfs (rw)
ramfs on /home/ftp/pub type ramfs (rw)
tmpfs on /mnt type tmpfs (size=1m)

図8.12 マウントされているディレクトリの確認


tmpfsをマウントする際のオプションについては、man 8 mountを参照してください。

[注記]ramfs

tmpfsとほぼ同じ機能を提供するものとして、ramfsもあります。Armadillo-400シリーズの標準設定では、/home/ftp/pubディレクトリがramfsでマウントされています。

ramfsは最大サイズの指定ができないため、ファイルを追加していくと、いずれRAMを使い切ってしまいます。実運用するシステムでは、ramfsではなく最大サイズを指定したtmpfsを使用する方が良いでしょう。

8.7. イメージの自動アップデート

開発段階で、Armadilloのイメージをアップデートする方法には以下のものがあります。

  1. Hermit-At ダウンローダーを使用して、シリアル経由で書き換える。
  2. Hermit-At ブートローダーの tftpdl 機能を使用して、ネットワーク経由で書き換える。
  3. netflash コマンドを使用して、ネットワーク経由で書き換える。
  4. at-cgi のファームウェアアップデート機能を使用して、ネットワーク経由で書き換える。

これらの方法は、いずれも人手での操作を伴います。製品出荷後でもイメージアップデートできるような機能をつける場合、可能な限り簡単な手順でアップデートできることが望ましいところです。

本章では、なるべく人手を介さない自動アップデート機能の実現方法を紹介します。

8.7.1. USBメモリを使用してのアップデート

udevdを使用して、USBメモリを接続するだけでイメージのアップデートを自動で行う方法を紹介します。

例として、次のような動作を考えます。

  1. USBポートにUSBメモリが接続されたら、以下の処理を行う。

    1. USBメモリのマウント。
    2. USBメモリにカーネルまたはユーザーランドのイメージファイルがあれば、フラッシュメモリをアップデート。
  2. USBメモリが抜かれ、フラッシュメモリのアップデートが正常に終了していたら、以下の処理を行う。

    1. 変更を反映するためにリブート。

USBメモリが接続されたことを検知して、特定の動作を行わせるにはudevdの機能を使用します。

udevdは、/etc/udev/rules.d/ディレクトリに、どのようなデバイスが接続された時にどのような動作を行うか、というルールを記述したファイルを置いておくと、そのルールに従って処理をおこないます。udevルールについての詳細は、man 7 udevを参照してください。

上下いずれかのUSBポートにマスストレージクラスのUSBメモリが接続されたら、または、USBメモリが抜かれたら、/etc/config/usb_image_update.shという名前のシェルスクリプトを実行するルールは、以下のようになります。

$KERNELには、USBメモリが接続された時に/devディレクトリに作成される、「sd*[0-9]*」にマッチしたデバイスファイル名が入ります。通常、sda1になるでしょう。

KERNEL=="sd*[0-9]*","BUS=="usb",ACTION=="add",RUN+="/etc/config/usb_image_update.sh start $KERNEL"
KERNEL=="sd*[0-9]*","BUS=="usb",ACTION=="remove",RUN+="/etc/config/usb_image_update.sh stop"

図8.13 USBメモリが接続された時にスクリプトを実行するudevルール(z99_usb_image_update.rules)


上記の内容を、/etc/udev/rules.d/z99_usb_image_update.rulesという名前で保存します。

設定内容をすぐに反映するには、udevdを一度終了して再起動します。

[armadillo ~]# killall udevd && udevd --daemon

図8.14 udevdの再起動をおこなうコマンド


udevdから実行されるusb_image_update.shは、次のようになります。

USBメモリが接続されたときは、第一引数にstartという文字列が指定されるので、start_action関数を実行します。/dev/$KERNELという名前のデバイスファイルを/mnt/auto_image_updateディレクトリにマウントし、ファイル名がlinux*.bin.gzまたはromfs.*.bin.gzにマッチするイメージファイルでフラッシュメモリをアップデートします。処理中は、red LEDを点滅させます。

USBメモリが抜かれた時には、第一引数にstopという文字列が指定されるので、stop_action関数を実行します。フラッシュメモリが正常に更新されていれば、rebootコマンドを実行し、再起動します。

#!/bin/sh

LOCKDIR='/var/lock/auto_image_update'
SUCCESSDIR='/var/lock/auto_image_update_success'
MOUNTDIR='/mnt/auto_image_update'
KERNEL_IMG_PTN='linux*.bin.gz'
USERLAND_IMG_PTN='romfs*.img.gz'

ACTION=$1
DEVICE=$2

log()
{
    logger -p user.$1 -t "$0[$$]" -- "$2"
}

die()
{
    log err "$1"
    if [ "$(mount | grep /mnt/auto_image_update | grep -v grep)" ]; then
        umount $MOUNTDIR
    fi
    if [ -e $MOUNTDIR ]; then
        rmdir $MOUNTDIR
    fi
    ledctrl red blink_off
    rmdir $LOCKDIR
    exit 1
}

start_action()
{
    mkdir -p $LOCKDIR || die "making LOCKDIR($LOCKDIR) is failed"

    if [ -e $SUCCESSDIR ]; then
        die "SUCCESSDIR($SUCCESSDIR) is already exist"
    fi

    ledctrl red blink_on 100

    if [ ! -e $MOUNTDIR ]; then
        mkdir $MOUNTDIR
    fi

    MOUNT_SUCCESS=no
    for fs in vfat auto; do
        mount -t $fs -o ro /dev/$DEVICE $MOUNTDIR > /dev/null 2>&1
        if [ $? = 0 ]; then
            MOUNT_SUCCESS=yes
            break;
        fi
    done
    if [ "$MOUNT_SUCCESS" != "yes" ]; then
        die "failed to mount /dev/$DEVICE to $MOUNTDIR"
    fi

    KERNEL_IMG_FILE=$(ls $MOUNTDIR/$KERNEL_IMG_PTN 2>/dev/null | tail -1)
    USERLAND_IMG_FILE=$(ls $MOUNTDIR/$USERLAND_IMG_PTN 2>/dev/null | tail -1)

    if [ -z "$KERNEL_IMG_FILE" ] && [ -z "$USERLAND_IMG_FILE" ]; then
        die "no image file found"
    fi

    if [ "$KERNEL_IMG_FILE" ]; then
        log info "update kernel image: $KERNEL_IMG_FILE"
        netflash -knubsr /dev/flash/kernel $KERNEL_IMG_FILE > /dev/null 2>&1
        if [ $? != 0 ]; then
            die "update kernel image failed"
        fi
    fi

    if [ "$USERLAND_IMG_FILE" ]; then
        log info "update userland image: $USERLAND_IMG_FILE"
        netflash -knubsr /dev/flash/userland $USERLAND_IMG_FILE > /dev/null 2>&1
        if [ $? != 0 ]; then
            die "update userland image failed"
        fi
    fi

    umount $MOUNTDIR || die "umount $MOUNTDIR failed"
    rmdir $MOUNTDIR || die "rmdir $MOUNTDIR failed"

    mkdir $SUCCESSDIR || die "mkdir $SUCCESSDIR"

    ledctrl red blink_off

    rmdir $LOCKDIR || die "rmdir $LOCKDIR"

    exit 0
}

stop_action()
{
    if [ -e $LOCKDIR ]; then
        log err "LOCKDIR($LOCKDIR) is exist"
        exit 1
    fi

    if [ ! -e $SUCCESSDIR ]; then
        log err "SUCCESSDIR($SUCCESSDIR) is not exist"
        exit 1
    fi

    rmdir $SUCCESSDIR

    log info "reboot"
    reboot

    exit 0
}

log info "$ACTION"
case $ACTION in
    start)
        start_action
        ;;
    stop)
        stop_action
        ;;
    *)
    log err "unknown action"
        ;;
esac

図8.15 USBメモリ内のイメージファイルでフラッシュメモリをアップデートするスクリプト(usb_image_update.sh)


[警告]注意: セキュリティホール

USBメモリを使用してのアップデート機能は、第三者がUSBポートに触れることができる環境下では、その第三者によってイメージを書き換えられる危険性が伴います。

本方法を適用できる状況であるか否かについては、十分ご検討ください。

8.7.2. Webサーバーを使用してのアップデート

Webサーバにイメージファイルを置くと、自動でイメージをアップデートする方法を紹介します。

例として、次のような動作を考えます。

  1. 毎日定時に特定のシェルスクリプトを実行する。
  2. シェルスクリプトでは、以下の処理をおこなう。

    1. Webサーバーにアクセスし、更新すべきイメージファイルがあるか確認する。
    2. ファイルがある場合取得し、フラッシュメモリをアップデート。
    3. 正常に書き込みが完了したら、変更を反映するためにリブート。

毎日定時にスクリプトを実行する処理は、cronに任せることにします。cronの設定は、「cronで定期実行する」を参照してください。

毎日4:00に処理を実行する場合のcrontabの設定は以下のようになるでしょう。イメージのアップデートを行うスクリプトは、/etc/config/web_image_update.shという名前だとします。

* 4 * * * /etc/config/web_image_update.sh

図8.16 毎日4:00に処理を実行するcrontab


cronから実行されるweb_image_update.shは、次のようになります。wgetコマンドでイメージファイルを取得し、netflashコマンドでフラッシュメモリを書き換えます。SERVER_IP_ADDRESS(WebサーバーのIPアドレス)、PROTCOL(httpまたはhttps)、USER_NAME(認証を行う場合のユーザー名)、PASSWORD(認証を行う場合のパスワード)、KERNEL_IMG_PATH(カーネルイメージのパス)、USERLAND_IMG_PATH(ユーザーランドいめーのパス)の各変数は、環境に合わせて書き換えてください。

wgetコマンドを使用することで、httpプロトコルだけでなく、httpsプロトコルも使用することができます。また、USER_NAMEとPASSWORD変数を指定することで、basic認証またはdigest認証を利用できます。

#!/bin/sh

SERVER_IP_ADDRESS=172.16.2.101
PROTCOL='http'
USER_NAME=''
PASSWORD=''
KERNEL_IMG_PATH='linux.bin.gz'
USERLAND_IMG_PATH='romfs.img.gz'

KERNEL_IMG_URL=${PROTCOL}://${SERVER_IP_ADDRESS}/${KERNEL_IMG_PATH}
USERLAND_IMG_URL=${PROTCOL}://${SERVER_IP_ADDRESS}/${USERLAND_IMG_PATH}
TMPFS_SIZE='32m'
LOCKDIR='/var/lock/auto_image_update'
TMPDIR=''
FILE_IS_UPDATED=false

log()
{
    logger -p user.$1 -t "$0[$$]" -- "$2"
}

die()
{
    log err "$1"

    if [ "$TMPDIR" ]; then
        cd
        umount $TMPDIR
        rm -rf $TMPDIR
    fi

    ledctrl red blink_off
    rmdir $LOCKDIR

    exit 1
}

update_image()
{
    region=$1
    img_url=$2

    wget_ops=""
    if [ "$USER_NAME" ]; then
        wget_ops="--http-user=$USER_NAME"
        if [ "$PASSWORD" ]; then
            wget_ops="$wget_ops --http-password=$PASSWORD"
        fi
    fi

    wget $wget_ops $img_url > /dev/null 2>&1
    if [ $? = 0 ]; then
        log info "update $region image: $(basename $img_url)"
        netflash -knubsr /dev/flash/$region $(basename $img_url) > /dev/null 2>&1
        if [ $? = 0 ]; then
            FILE_IS_UPDATED=true
        else
            die "update $region image failed"
        fi
        rm -f $(basename $img_url)
    fi
}

mkdir -p $LOCKDIR || die "making LOCKDIR($LOCKDIR) is failed"

ledctrl red blink_on 100

TMPDIR=$(mktemp -d /tmp/web_image_update.XXXXXXXX)
if [ -z "$TMPDIR" ]; then
    die "mktemp failed"
fi

mount -t tmpfs -o size=$TMPFS_SIZE tmpfs $TMPDIR
cd $TMPDIR

if [ "$KERNEL_IMG_PATH" ]; then
    update_image kernel $KERNEL_IMG_URL
fi
if [ "$USERLAND_IMG_PATH" ]; then
    update_image userland $USERLAND_IMG_URL
fi

cd
umount $TMPDIR
rm -rf $TMPDIR


ledctrl red blink_off

rmdir $LOCKDIR

if [ "$FILE_IS_UPDATED" = true ]; then
    log info "reboot"
    reboot
fi

図8.17 Webサーバーにあるイメージファイルでフラッシュメモリをアップデートするスクリプト(web_image_update.sh)




[55] 登録といっても、所定のディレクトリに所定の名前でファイルを置くだけです。

[56] この処理は/etc/rc.d/S04flatfsdで行っています。

[57] 液晶画面に表示され、タッチパネルによる操作でArmadillo-440の動作確認を行うことができる、ファンクションテストアプリケーションプログラム。

[58] WebブラウザでArmadilloの設定管理を行うためのCGI。

[59] 実体はatmark-dist/tools/romfs-inst.sh

[60] この操作を、ウォッチドッグタイマーをキックする、撫でる、起こす、などと表現します。