リンカスクリプトの役割と設計時の注意点
リンカスクリプトとは設計者が意図どおりのメモリ領域に、プログラムを配置するためのスクリプトです。Webシステムやアプリ開発の時には、気にせずにコードを組んでいることも多いですが、マイコンなど低レイヤの開発になると重要性が増してきます。使用可能な物理メモリが限られる分、アドレスを指定してメモリを効率的に利用することが求められたり、メモリの種類によって、アクセス速度が変わったりするからです。
この記事ではマイコン設計時の注意点とともに、発生頻度の高いエラー原因に関してご紹介します。
リンカスクリプトの役割
リンカスクリプトは組み込みプログラムにおいて、プログラムを配置する時に必要なスクリプトです。基本の構造として、ENTRYシンボル、MEMORYセクション、SECTIONSセクションに分かれます。
ENTRYシンボルはプログラムを開始する関数を指定します。MEMORYセクションはROMとRAMのメモリエリアを定義するなどシステムのメモリ領域を定義します。SECTIONSセクションは最終的なバイナリの状態でセクションがメモリのどこに配置されるかを指定します。
各セクションの役割
コンパイルしたプログラムがメモリ上に展開されるとき、その用途に応じた6つの領域に展開されます。各領域をセクションと呼び、リンカスクリプトで配置先を明示的に指定します。
各セクションの役割を記載します。
読み取り専用のROMに配置されるセクションはtext領域とrodata領域の2つです。
- text領域
実行されるコードを格納します - rodata領域
値を再代入できないconst変数やリテラル文字列を格納します。
書き込みが可能なRAMに配置されるセクションはdata領域、bss領域、ヒープ領域、スタック領域の4つです。
- data領域
初期値のあるグローバル変数を格納します。ROMから初期値をコピーするため、ROM側にも領域を持ちます。 - bss領域
初期値のないグローバル変数を格納します。 - ヒープ領域
プログラム内で動的に確保されるメモリ領域です。 - スタック領域
関数呼び出しやローカル変数で使用される領域です。
このほかに、複数種類のメモリを持っている組み込み機器の場合は、それぞれのメモリにどのような実行コードを配置するかを設定します。たとえば、アクセス速度の速いメモリには、リアルタイム性を求めるコードを置くなどといった工夫をすることでシステムのパフォーマンスを引き出すことができます。
メモリ設計の時に注意したい点
組み込みシステムは容量が限られるため、スタック領域とヒープ領域の設計が特に重要になります。設計時、注意したいポイントはそれぞれ以下の通りです。
スタック領域
スタック領域は関数呼び出し時に使用される領域で、戻りアドレス・関数の引数・ローカル変数が格納されます。スタック領域は、リンカスクリプトであらかじめ上限サイズが定義されます。関数呼び出しのネストが深くなったり、再帰を必要以上に繰り返したりすると、スタック領域が不足してオーバーフローを引き起こします。
ヒープ領域
プログラムがmallocなどで動的に確保するメモリ領域です。必要なときに必要な分を使う形で動的にメモリを確保しますが、実行中に使用量が変動するため制御の難しい領域でもあります。使用量の予測が難しかったり、明示的に解放しないと確保され続けてしまったりと、利用には工夫が求められる領域でもあります。
配置ミスに伴うエラー
リンカスクリプトの記述が誤っていても、コンパイルは通ってしまいます。実行時にはじめてエラーが検出されたり、そもそも起動しない、といったことがあるため、よくあるエラーは頭に入れておき設計段階から考慮するようにしましょう。起こしやすいエラーをいくつかご紹介します。
スタックオーバーフロー
実行されるプログラムに対してスタックの見積もりが不足している場合に発生します。組み込み環境ではスタックのサイズ自体が非常に小さいため、開発時にパソコンで問題なく動作していたコードもマイコンで動かすとスタックオーバーフローを起こす可能性があります。そもそものメモリ使用量の見積もりが少なすぎるのももちろんですが、再帰や深いネストなどスタックを消費しやすい処理を避けて設計することでリスクを減らせます。
ヒープとスタックの衝突
スタック領域は高アドレスから低アドレス方向へ向かって伸長し、ヒープ領域は低アドレスから高アドレス方向へ向かって動的にメモリ領域を確保します。スタックの確保サイズ、ヒープとスタックの間にあまり余裕がないと発生する確率が高くなります。ヒープ領域が予想以上に拡大して、スタックとぶつかる場合もあるためヒープとスタックの使用量をそれぞれ余裕のある数字で見積もることが不可欠です。
初期値データの不整合
データセクションのROM上の配置とRAM上の配置が正しく対応していないときに起きます。初期値はROMに保存されているため、プログラム起動時にROMからRAMへコピーされますが、アドレスが誤っていると初期値が正しくコピーされず、エラーを起こします。反対にRAMに格納して起動時にすべて初期化されるはずの変数が、ROMにあることで初期化されないこともあり、配置を間違えると意図しない動作を引き起こす原因になります。


