コードサイズを教えて!
お客様に組込みミドルウエア製品を検討していただいている時に、よく聞かれるのが「コードサイズを教えてください」という質問なんだ。「コードサイズ」といわれたら何を答えればよいのだろうか。サイズといっても、ソースコードのファイルサイズを合計してもだめだよ(笑)
ここでいうコードサイズは、実際にそのミドルウエア製品をハードウエアに組込んだときに「プログラムを格納するために必要なメモリの容量」を指しているんだ。「必要なROM/RAMサイズを教えてください」と聞かれることもあるよ。
コードサイズが大きくなると、それだけハードウエアに容量の大きなROM/RAMを載せておく必要があるから、ハードウエアのコストが上がってしまう。つまり、想定しているハードウエア仕様に適合するか?というとても重要な検討アイテムといえるんだ。
Windowsマシンなどのように既に動作している機器の上で動作するアプリケーションを作るのではなく、「機器そのもの」から作り上げる必要がある組込み開発ならではの質問といえるよね。このコードサイズ、どうやって計算すればいいんだろう?そのためにはまず、ソフトウエアプログラムが、実際のハードウエアにどのように格納されるのかを知っておく必要があるんだ。
組込み機器のプログラムの保存場所
ソフトウエアプログラムは、いったいどこに保存されているのだろうか?PCの場合は、ハードディスクに保存されているよね。最初だけCD-ROMを入れてインストールするけど、次回からはCD-ROMなしでプログラムが起動するようになるよね。
これはインストール作業によって、ハードディスクにプログラムが展開・コピーされているからなんだ。ハードディスクに空きがあれば、他のプログラムを追加インストールすることもできるよね。
だけど、組込み機器では、PCと違ってハードディスクなどのローカルな大容量ストレージを持たないものがほとんどなんだ。また、基本的にプログラムの内容は製品出荷時に決まっていて、機能の追加・変更等はできないんだ。
最近では「ファームウエア・アップデート」といって、あとから組込み機器のプログラム本体を更新することができることもあるし、携帯電話ではiアプリやゲームなどのアプリケーションを後からダウンロードして追加することができるようになっているよね。ただ、それもほんの一分野であり、やはり組込み機器は基本的に「機能の追加/削除はできない」ものがほとんどなんだ。
このような組込み機器では、全てのプログラムは「メモリ」の中に保存されているんだ。
ひとくちにメモリと言っても、大きく分けてROM(Read-Only Memory)とRAM(Random Access Memory)の2種類ある。これは聞いたことがあるんじゃないかな。
それじゃ、ROM、 RAMそれぞれについておさらいしてみよう。CD-ROMやDVD-RAMなどの外部メディアもROM/RAMとついているけど、組込み機器のプログラムの格納という目的では使われないから、ここでは除外するね。
ROMの種類
ROMは、読み出しアクセス専用のメモリだ。データを保持するための電源が不要だから、電源OFFしてもデータが消えないんだ。これを不揮発性といって、電源を切ると全てのデータが消えてしまうRAMに対して大きな特徴になっているんだけど、アクセス速度はRAMに比べると遅いんだ。
ROMは大きく分けて、内容を書き換えられないマスクROMと書き換え可能なプログラマブルROM(PROM)の2種類に分類されている。PROMは、さらにその消去方法や書き込み方法によって、UV-EPROM, EEPROM, FlashROMの種類があるよ。
マスクROM | UV-EPROM | EEPROM | FLashROM | |
---|---|---|---|---|
データ消去 | 不可 | 可能(紫外線) | 可能(プログラム) | 可能(プログラム) |
データ書き込み | 1回だけ可能 | 可能(ROMライタ) | 可能(プログラム) | 可能(プログラム) |
消去単位 | - | 全体 | 1バイト単位 | ブロック単位 |
容量 | 小 | 小 | 小 | 大 |
マスクROMは、ROMを製造する時に内容を決めてしまって、その後は書き換えができない。開発が完了したソフトウエアを書き込んで、最終製品に搭載されるんだ。出荷したら二度と修正できないわけだから、ソフトを払いだす開発現場は、プレッシャーだね(笑)
UV-EPROMは、紫外線を当てることでデータを消去でき、ROMライタという専用の装置を使って内容を書き換えることができる。評価ボード用のROMとしてよく使われているよ。だけど、データ消去にはかなり時間がかかるし、一部だけを消去することができないんだ。また、ROMライタによる書き込みも時間がかかるし、面倒なんだよ。
EEPROMは、基板に実装したままの状態で、電気的に内容を書き換えることができる。シリアル通信を使ってプログラムを流し込むタイプのものもあるんだ。PCのマザーボードに載っているBIOSと呼ばれる「毎回電源ON時に起動するプログラム」によく使われているよ。最近は、FlashROMに置き換わりつつあるね。
FlashROMは、EEPROMの一種で最近の主流。データの消去/書き換えをプログラムから行うことができるんだ。消去はブロック単位でしかできないけど、構造的に大容量化が容易なのが特徴だね。ただし、ブロックの書き換え回数に上限があることに注意が必要だよ。
FlashROMは、いろいろなところで活用されているよ。前述した製品出荷後にプログラムを更新する「ファームウエアアップデート」という機能も、このFlashROMが普及したおかげで実現できるようになったんだ。データ書き換え速度もかなり高速になってきていて、携帯電話の端末設定や電話帳のデータなど「電源OFFしても消えては困るデータ」をリアルタイムに保存/更新できるのもFlashROMならではといえるんだ。
今ではなくてはならないアイテムであるUSBメモリ、SDカード/Memory Stickなどの各種メモリカードもFlashROMで構成されているんだよ。組込みの世界でも、今後、ますますFlashROMに関わることが増えていくだろうね。
RAM(Random Access Memory)
FlashはROMといいつつも書き込みの使い勝手がどんどん良くなっている。でも読み書きの速度(特に書き込み)など、任意の場所を自由に読み書きできるという点では、RAMにはかなわないんだ。RAMは、大きく分けてSRAM(Static RAM)とDRAM(Dynamic RAM)に分類されるよ。
SRAM | DRAM | |
---|---|---|
データ保持処理 | 不要 | 必要(周期リフレッシュ) |
アクセス速度 | 高速 | 低速 |
価格 | 高価 | 安価 |
一般的にメモリやRAMといえば、DRAMのことを指すことが多いんだ。よく半導体の相場の指標としても使われるよね。PC用のSDRAMやDDR-SDRAMなどもDRAMの一種だよ。SRAMは高価だけど、非常に高速なためCPUのキャッシュメモリとして利用されているよ。
ROMとRAMの役割
組込み機器が動作するためには、少なくとも1個のROMと1個のRAMが必要になる。この2つのメモリがないと、いかなるシステムも動くことができないんだ。それぞれの役割を簡単に説明してみよう。
ROMは、「システム動作中に不変なデータ」を格納する場所だ。プログラムが書き変わることはない組込み機器の場合は、プログラムコード部分をまるごとROMに格納することができるよね。また、文字列など動作中に書き変わらない定数データも置くことができるよ。
任天堂ファミリーコンピュータのカセットは、マスクROMなんだよ。
RAMは、「システムが動作中に任意に書き換える可能性があるデータ」を格納する場所だ。プログラムが使用する変数データだけではなく、タスクのスタックメモリなどもRAM上の領域として割り当てられるよ。残りの未使用領域は、一般にOSによって「ヒープメモリ」として管理されるんだ。
ヒープメモリは、システムが動作中に動的に確保することができるメモリ領域で、OSやアプリケーションが必要に応じて利用することができるようになっているよ。ちなみに、メモリ以外(ハードディスク等)にプログラムを格納しているシステムだと、ROMに「ブートストラップローダ」(正確にはその一部)というものが書かれているんだ。
前述したPCのマザーボードについているBIOSもその1つだよ。これは、電源ONで起動する時に最初に実行される特別なプログラムで、最終的にWindowsやLinuxなどのOSカーネルプログラムをハードディスクからRAMにロードして起動させるという役割を担っているんだ。「ブートストラップ(bootstrap)」はブーツの靴紐を意味する単語だけど、「自分の靴紐を引っ張って自分を持ち上げる」というイメージに由来しているんだって。それでは、もっと具体的に実際のソースコードで見てみよう。
この変数はどこに置かれる?(1)
以下のソースコードを見てね。
これをLinuxでコンパイルして実行すると、以下のような結果になるんだ。例だから、プログラムの処理内容には特に意味はないよ。あしからず。
このプログラムで注目してほしいのは「変数」だ。var1~var5まで5個の変数が使われているけど、これらはそれぞれ異なる性質を持っているんだ。この性質が「その変数がどこに格納されるか」ということに関係するんだね。
では、これらの「変数」をひとつずつ見ていくことにしよう。ROMとRAMのどちらにあるべきか、わかるかな?
もう一度プログラムを見ながら、ひとつひとつ確認していこう。
この変数はどこに置かれる?(2)
それぞれの変数の特徴と格納先のメモリを表にまとめてみたよ。
変数名 | 分類 | 初期値 | 書き換え | 格納メモリ |
---|---|---|---|---|
var1 | グローバル変数(初期値なし) | なし | 可 | RAM |
var2 | グローバル変数(初期値あり) | あり | 可 | ROM and RAM |
var3 | グローバルconst変数 | あり | 不可 | ROM or RAM |
var4 | ローカルstatic変数 | あり/なし | 可 | RAM |
var5 | ローカルsuto変数 | なし | 可 | RAM |
変数var2は、初期値をROMにおいておかないと消えてしまうんだ。だけど、書き換えも可能だから、実際の領域はRAM上にとらないといけないんだ。変数var3は、書き換え不可だから、ずっとROMにおいてあっても大丈夫だよ。高価なRAMを節約することができるね。変数var5の格納メモリは別の言い方をするとなんだろう・・・もう習ったよね?そう!Auto変数はスタックメモリ上に確保されるんだったね。スタックメモリとして使われる領域はRAMに確保されているんだ。
セクションとは
これらの変数データやプログラムのコード本体は格納されるべきエリアがきめられている。これを「セクション」と呼ぶんだ。セクションは普通、以下のように分類されている。これから先は、簡単にP、C、D、Bと略した書き方をするよ。
Program(P)セクション
プログラムコードが格納されるセクション。Textセクションとも呼ばれるよ。
Const(C)セクション
定数データが格納されるセクション。(→ var3)
Data(D)セクション
初期値ありのグローバル変数が格納されるセクション。(→ var2, var4)
Bss(B)セクション
初期値なしのグローバル変数が格納されるセクション。(→ var1)
タスクのスタックメモリはこのセクション以外のエリアから確保されるので、var5はこの中には含まれないんだ。これ以外にも、OSやコンパイラによって、OS予約セクションとか、スタックメモリ用セクション、ヒープメモリ用セクションなどが定義される。また、コンパイラによっては、#pragmaを使ってアプリケーションプログラムが独自にセクションを宣言することもできるんだ。
セクションをどこに割り当てるか
これらのセクションを実際のハードウエアのROM/RAMのどこに割り付けるかは、リンカの設定によって決まるよ。これは個々のシステムに依存して決められるものだけど、一般的には右の表のように配置するんだ。
最近は、外付けのROM/RAMだけじゃなく、ROM/RAMを内蔵したCPUもあるから、このセクションはこっちのRAMに置いて・・・といった細かい設定もできるね。
さて、今回のテーマは「コードサイズを聞かれたら」だったよね。ぴんぽ~ん!この各セクションのサイズを合計したものが「コードサイズ」なんだ!
この公式を使うと、プログラムをハードウエアに組み込んだときに、これだけのROM/RAMメモリを占有するよ、というサイズが計算できるというわけなんだ。それじゃあ、実際にコードサイズを計算してみよう!
Dセクションはコピーが必要!
ところで、DセクションがROMサイズとRAMサイズの両方で出てきてるよね。なぜだろう?
Dセクションは、初期値ありグローバル変数なんだ。初期値はROMで覚えておく必要があるけど、変数は書き換え可能だからRAMに配置する必要があるね。これは具体的にいうと「Dセクションは、システム起動時にROMからRAMにコピーしなければならない」ということなんだ。
開発環境によっては自動的にコピーするコードを生成してくれる場合もあるけど、たいていはシステム起動プログラムを書くプログラマが意識してDセクションのコピー処理を書かなければいけないから、注意しよう。
PセクションをRAMにコピーする
また、システムによっては、プログラムコードをRAMにコピーして実行させたい場合もあるよね。これはROM上で実行するよりも、RAM上で実行するほうがアクセス速度が高速になって、システム全体の処理パフォーマンスが向上するからなんだ。
この場合も、Pセクションをコピーして、CPUの実行アドレスをRAM上のコードにジャンプさせる処理を書く必要があるよ。ただし、Pセクションはサイズが大きくなるから、RAM容量にそれだけの余裕がなければダメなんだ。これもコストとのトレードオフといえるね。
Bセクションのクリアはシステム依存
Bセクションは「未初期化変数」のためのエリアだから、一般的には0が書かれているよ。だけど、起動時にBセクションを0でクリアするかどうかはシステムに依存するんだ。例えば、起動時間をとにかく短縮したい場合などは0クリアの処理時間もばかにならない。このため、Bセクションの0クリアを行わない場合もあるんだ。
また、Bセクションだけじゃなく、ヒープメモリやスタックメモリとして使われるRAMエリアも0クリアされる保証はないよ。ここで「変数が0で初期化されている」という前提のプログラムがあったらどうなるだろうか。もちろん、正しく動かないよね。これは、あるシステムで実績のあるプログラムを他のシステムに移植した場合などに起こる問題といえるんだ。
最近はコンパイラやソースコード解析ソフトで「変数を初期化せずに使おうとしている」という警告を出してくれるものもあるよ。変数には「明示的に初期値を代入してから使用する」ように心がけよう。
セクションのサイズを知るには
さて、作成したプログラムのセクションのサイズはどうやって知るんだろうか。これは開発環境が提供する「セクション解析ツール」を使うんだ。Linuxでは、sizeコマンドとobjdumpコマンドが使えるよ。先ほどのソースコード例(codesize.c)をコンパイルしたオブジェクトファイルに対して、これらのコマンドを実行した結果を見てみよう。
※補足1
codesieze.o で bss=0 となるのは?
今回生成したオブジェクトファイル(codesize.o, a.out)は、Linux上で動作するためのフォーマット形式「elf32-i386」となっている。どうもこの形式では .o ファイルの時点では bss セクションが割り当てられないようだ。
※補足2
a.out で bss=8 となるのは?
ソースコード上では var1 しかないので、bss セクションは4バイトとなってほしいところだが、8バイトになっている。試しに、初期値なしグローバル変数を全く持たないソースをコンパイルしてみると bss=4 となった。最低4バイトは必ず確保されるようだ。
とりあえず、このように「セクション解析ツール」の表示結果から各セクションのサイズがわかるということはわかってもらえたよね。
メモリサイズを見積もる
今回説明したコードサイズは、プログラムやデータを格納するために必要なメモリサイズ、つまり静的なメモリサイズだったんだ。だけど、実際にプログラムが動作するためには、さらに動的に確保されるRAMメモリが必要になるんだ。生成するタスクに割り当てるスタックメモリやmalloc()などでヒープから確保するメモリなどだね。つまり、システムが必要とするメモリサイズを検討する場合には、
- 静的なメモリサイズ(コード・データ)
- 動的なメモリサイズ(スタック・ヒープ等)
の両方を考慮する必要があるということになるから、覚えておこう!
組込み機器に搭載されるROM/RAMメモリの容量は、非常に限られている。このため、組込み向けのソフトウエアは、できるかぎり省メモリ設計とすることが求められているよ。また、動的に確保するメモリに関しては、メモリの解放を忘れる「メモリリーク」というバグにも注意する必要がある。このバグが残っていると、システムが長時間動作しているうちに次第に動けなくなるというトラブルが発生するからだ。
組込み向けソフトウエアの開発者は、自分が作成したソフトウエアがどれくらいのメモリを消費しているのか?ということを常に意識しておくことが大変重要なことなんだ。
これにて、「コードサイズを聞かれたら」は終わるけど、みんなわかったよね!う~ん、次は何を話そうかな・・・
リクエストや感想があれば、お問い合わせから送ってね。
-
- 第1回 組込みシステムのこれから
- 第2回 IoTの成功はセキュリティ次第
- 第3回 組込みでもGPUやFPGAと早めに親しんでおこう
- 第4回 電子産業の紅白歌合戦、CEATECで垣間見えた未来
- 第5回 小口開発案件の集合市場、IoTの歩き方(上)
- 第6回 小口開発案件の集合市場、IoTの歩き方(下)
- 第7回 徹底予習:AI時代の組込みシステム開発のお仕事
- 第8回 いまどきのセンサー(上):ありのままの状態を知る
- 第9回 いまどきのセンサー(下):データを賢く取捨選択する
- 第10回 組込みブロックチェーンの衝撃(上)
- 第11回 組込みブロックチェーンの衝撃(下)
- 第12回 エネルギーハーベスティングの使い所、使い方
- 第13回 「人を育てる」から「道具を育てる」へ、農業から学ぶAI有効活用法
- 第14回 CPS時代に組込みシステム開発に求められることとは
- 第15回 次世代車のE/Eアーキテクチャに見る組込みの進む道
- 第16回 RISC-Vが拓く専用プロセッサの時代
- 第17回 振動計測の大進化で、熟練エンジニアのスキルを広く身近に
-
- 零の巻:組込みというお仕事
- 壱の巻:2進数と16進数を覚えよう!
- 弐の巻:割り込みとポーリング
- 参の巻:printf()が使えない?
- 四の巻:これにもIntelが入ってるの?
- 五の巻:Endianってなに?
- 六の巻:マルチタスクとは
- 七の巻:スタックってなあに?(1)
- 七の巻:スタックってなあに?(2)
- 八の巻:メモリを壊してみましょう
- 九の巻:コードが消える?~最適化の罠~
- 拾の巻:例外が発生しました
- 拾壱の巻:コードサイズを聞かれたら
- 拾弐の巻:キャッシュは諸刃の剣
- 拾参の巻:デバイスにアクセスするには
- 拾四の巻:セキュリティってなに?(1)
- 拾四の巻:セキュリティってなに?(2)
- 拾四の巻:セキュリティってなに?(3)
- 拾五の巻 :DMA対応と言われたら(1)
- 拾五の巻 :DMA対応と言われたら(2)
- 拾六の巻:ヒープとスタック
- 拾七の巻:フラグメンテーション
- 拾八の巻:CPU起動とブートローダ
- 拾九の巻:kmとKByteの「kとK」
- ビリーへの質問:DMAとキャッシュの関係
- ビリーへの質問:スタックオーバーフローについて
- ビリーへの質問:CPUレジスタについて