では、実際に組み込みLinuxの開発手順を見ていきましょう。
最初に行わなければならないのは、ターゲット上でLinux自体を動作させるためのブートローダの開発です。組み込み分野におけるブートローダは、PC/AT互換機のBIOSに相当する部分です(編注)。
ブートローダの役割は、機器の電源投入に伴い、カーネルイメージを任意の場所から取り出してターゲットのメモリへ展開し、実行することです。この「任意の場所」について取り決めはありませんが、多くの機器の場合はフラッシュROMなどのボード実装メモリを使用します。従って、メンテナンスなどのために、本来の機能以外にROMへのプログラム書き込み機能などが組み込まれることもあります。
ブートローダの実際の開発には、イン・サーキット・エミュレータ(ICE)やJTAGエミュレータといった組み込みソフトウェア開発のための専用機器を併用した方式が主体となります。こうした方式で行う理由は、単にターゲットにはまだソフトウェア実装されていないため、ほかに手法がないからです。
ただし、ブートローダを無の状態から開発することはほとんどありません。オープンソースのブートローダが存在するので、それらを参考に開発するのが近道です。また、市販されているシングルボードコンピュータの多くはブートローダ機能が実装されているので、ブートローダの開発が必要となるのはターゲットボード自体を自作した場合のみといっても過言ではありません。
ブートローダの次は、Linuxのカーネル実装となります。実装とはいっても、通常のカーネルをそのまま動かすことはまず不可能で、この作業もかなり細かいステップが必要となります。
1.ターゲットに近いカーネルの選択
kernel.org(http://www.kernel.org/)などから、ターゲットに素性が近いカーネルを選択します。特に、アーキテクチャ(CPUなど)が異なるカーネルを使用するのは不可能に近いので、必ず適合するアーキテクチャのカーネルを選ぶ必要があります。対象周辺機器が同じであればなおさら好都合です。
2.組み込むドライバを最小限にする
カーネルコンフィグレーション(make menuconfigなど)を用いて、静的リンクするモジュール(ドライバ)を最小限にします。こうすることで、動作時の問題調査の範囲を狭めることができます。また、ドライバはモジュール形式で後付け可能なので、カーネルが動き出してから開発しても間に合います。この段階では、取りあえず後回しとするのが賢明です。
一般的に、組み込み機器で最低限必要とされるのは以下のデバイスです。
3.ドライバのターゲット対応
静的に組み込むドライバなどのソースコードをターゲットに対応させます。この際に注意しなければならないのは、メモリ管理と呼ばれているパーツの修正です。ターゲットの実装メモリサイズや各種デバイスの割り付け(注)を決定する個所なので、ここを間違えるとデバイスドライバの動作などに支障が生じるので注意が必要です。
それ以外のCPUではレジスタがメモリに直接展開されるので、特定のアドレスを読み書きすることでデバイス制御を実現することになります。このメモリへの割り付けは機械的(ターゲットごとに)決まっている場合やメモリ管理機構でソフト的に決定される場合など、さまざまなケースがあります。
4.RAMディスクイメージの作成
ご存じのとおり、Linuxはその動作のためにディスクが必要です。しかし、ハードディスクなどを動作させるにはドライバが必要となり、ドライバが動作するためにはカーネルが動作している必要があります。しかし、現時点ではまだカーネルを動作させる前段階であるという矛盾した状態です。従って、取りあえずハードウェア依存性が低いRAMディスクを当面は使用するのが最良の選択となります。そこで、まずRAMディスクイメージを作成する必要があります。
この場合の注意点は、RAMディスクはメモリを使ったディスクなので使用量には制限があるということです。つまり、むやみやたらにファイルやコマンドを入れるのはご法度です。BusyBox(http://www.busybox.net/)などを活用して、極力サイズを小さくしターゲットの搭載メモリを超えないようにしなければなりません。また、コマンドなどの実行ファイルは、ターゲットと同じCPUで動くものをそろえる必要があることも忘れないでください。
ここまで来て初めて、クロスコンパイラを用いてカーネルのビルドを行い、ターゲット用カーネルイメージの作成に入ることになります。また、この際にRAMディスクを組み込んでおくことをお勧めします。ビルドしたカーネルイメージを前述したブートローダを使ってターゲット上で動作させるわけですが、RAMディスクはその際の転送を簡単にするのに役立ちます。
Linuxコンソールを組み込んでいるので、カーネルが動作を開始すれば通常はコンソール上に何らかのメッセージが表示されるはずです。ただし即座に動作することはまれで、大抵は動かしても何のメッセージも出力されない状態に陥ります。
このような状態を避けるため、printk()を用いてカーネルの要所要所にデバッグメッセージを入れ、プログラムの通過点の確認や変数表示を盛り込んでおくことが必要です。また、ICEやJTAGエミュレータといった専用開発機器を使って状態確認を行うのも手でしょう。
printf()はその名のとおり、標準出力に対して文字列を出力する関数で、OSのサポート機能の一環です。printk()はprintf()とまったく異なり、その関数の中身が直接ハードウェアを操作して文字列を出力する構造になっています。printk()はいかなる状態であっても文字列を表示させ、それを問題解決の糸口とするために使われるからです。つまり、printk()がOS(Linux)を必要とするものであれば、すなわちカーネル自体が動作を開始していなければ使用できないということになりますが、そうなるとカーネルの動作準備中のメッセージを表示するすべがなくなってしまうということになります。従って、printk()は必要に応じて直接ハードウェアを制御して文字列を表示させているのです。
カーネルの実装が完了したら、当面は必要なドライバの作成を行うことになります。この段階での基本的な流れとしては、「ひたすらモジュールを作成しては動かしてみる」という地道な作業の繰り返しとなります。
もし、ターゲットにリムーバブル系のストレージデバイスがあるのであれば、そのドライバ作成を優先することをお勧めします。その理由は、カーネル実装の話を思い出していただければ分かると思います。最初はRAMディスクのみの実装としたので、このままだとモジュール1つの実行だけで、
モジュール作成(ファイル作成)
↓
RAMディスク内容変更
↓
カーネル再ビルド(RAMディスクイメージ同梱)
↓
ターゲット再起動(カーネル再転送/実行)
↓
モジュール動作
といった、非常に長い手順を踏むことになってしまいます。リムーバブルストレージが使えるようになれば、モジュールのみの入れ替えを実現できることになります。
カーネルとモジュールの作成が完了したら、アプリケーションの開発です。これについてもさまざまな手順があります。基本はモジュール開発と同じく、
作成
↓
ターゲットに転送
↓
実行
ということになりますが、その方法は以下の2種類に大別されています。
1.疑似開発
組み込みLinuxを用いた開発の大半は、この方法ではないでしょうか。
手順としては、LinuxをインストールしたPC(ホストコンピュータ)を用意し、ホストコンピュータ上のLinuxで動作するプログラムを最初に開発します。次に、クロスコンパイラを用いてそのプログラムをホストコンピュータでターゲット用にコンパイルし、ターゲットに転送して動作させます。
この方法の場合、最後のターゲット動作まではターゲット(実機)を使用する必要がないため、ターゲットの数が開発者に対して不足している場合などに有効です。また、最終的にターゲット動作をする場合、ほとんどの問題はホストコンピュータ上で解決しているので、ターゲットで動作させる段階ではあまり問題が出ないという利点があります。
逆に、この方法は開発できるソフトウェアが制限されるという問題があります。例えば、ターゲットにしか存在しないデバイスを制御するプログラムを開発するのは困難です。同様に、Linuxにはさまざまなグラフィックライブラリ(X11など)がありますが、組み込み機器で同じグラフィックライブラリを使用することはまれで、なおかつ画面サイズも異なるなど、単純にホストコンピュータ上ですべての開発ができることはありません。
また、プログラムを固定時間で動作させなければならない場合、ホストコンピュータでは正しい時間配分で動作しても、ターゲットではプロセッサの能力差によって時間配分が異なってしまうことも考えられます。従って、最終的には“実機開発”が必要になります。
2.実機開発
実機開発の手順は、まさにクロス開発そのものです。つまり、
ホストコンピュータでプログラムの作成
↓
コンパイル
↓
実行ファイルをターゲットで実行
という手順で開発を行います。
なお、実行ファイルをターゲットに転送することを、“ダウンロード”と呼びます。ダウンロードにはさまざまな方法が存在します。FTPなどによるファイル転送、NFSやSambaなどのネットワークファイルシステムを用いたファイル共有も使われます。特に、NFSやSambaなどを利用した方法は、ダウンロードという処理を意識せずにターゲットでのファイル実行が可能となるので、比較的多くの組み込みLinux開発で使用されています。
具体的には、ホストコンピュータの一部のディレクトリをNFSなどで共有公開します。プログラムの作成はそのディレクトリ下で行い、ターゲットは公開されたディレクトリをNFSマウントして直接利用します。組み込みLinuxであってもコンソールの概念はあるので、ターゲットでもLinuxコマンドが使用できます。もちろん、逆にターゲット側の任意のディレクトリをNFSで公開し、ホストコンピュータ側でNFSマウントを実施することも可能です。ただし、多くのターゲットはディスク領域にあまり余裕がないため、ホストコンピュータのディレクトリを共有する方が一般的です。
これ以外の方法としては、古典的な方法ですが、FDやUSBストレージといったリムーバブルメディアを用いて、実行ファイルをその都度やりとりする方法も考えられます。しかし、その手間を考えると、非現実的といわざるを得ないでしょう。
以上の開発作業が終了しても、単に取りあえず動くものができたというだけで、完成というには少し気が早い状態です。この時点ではあくまで試作機環境で動作しただけで、製品とすべき状態で動いたわけではないからです。
では、どのような状態であれば完成したといえるのでしょうか? 製品によって「完成」の定義は千差万別ですが、要はファイルサイズが規定範囲内か、動くべきプログラムが自動起動するか、などが挙げられます。
ファイルサイズは、それを収めるハードウェアの大きさなどに影響するため、かなり重要です。カーネルや各モジュール、アプリケーションなどのファイルサイズ、ディスクの空き容量がどれくらいあるのかを確認します。これらが規定範囲内に収まっていない場合は、プログラムのコンパイルオプションなどを変更して、サイズを調整するなどの作業を行うことになります。
今回は、組み込みLinux開発の全体像を説明しました。次回からは、アットマークテクノのArmadillo-9(http://armadillo.atmark-techno.com/armadillo-9/)を前提に、実際にターゲットを用いた開発を行っていきます。最初の目標として、Armadillo-9でWebサーバを稼働させてみましょう。(次回に続く)
Copyright © ITmedia, Inc. All Rights Reserved.