SRAM LOWERを使いたい(GNU Linker scriptの編集方法)

自分がよく使うNXPのマイコンはものによっては、SRAM領域が下のようにupperとlowerの2領域に分割されている場合があります。

例えば、MKE18F512VLL16というマイコンは、データシートによれば、SRAM Lower = 32KB, SRAM Upper = 32KBで計64KBのSRAMがあると書かれています。

メモリたりてるけど、ビルドできない

IDE(MCUXpresso)をデフォルト設定のまま、ビルドするとメモリはSRAM_Upperが優先してつかわれます。変数を増やし 32KB以上になると、下のようなエラーが発生します。

SRAM_UPPERが165%となっており、SRAM_LOWERを使えば、容量的に問題ないはずですが、SRAM_LOWERの使用率は0%のままです。容量オーバした分を勝手にLOWERに定義してくれればいいんですが、そうはなりません。くく。

実際にSRAM_LOWERを活用するにはGNU-Linker scriptを変更してやる必要があります。なので、今回の記事の目標は、

  1. Linker Script の概要を理解する
  2. Linker Scriptを編集して、NXPマイコンで分割されてるSRAMをフルで使えるようにする

です。

そもそもGNU Linker Script(ld)ってなに?

GNU Linker scriptとはGNU toolchain用のlinker scriptです。linker scriptは、コンパイル後に生成されるオブジェクトファイルたちをマージして、メモリ上にコードや変数を指定したアドレスに割り当てるためのスクリプトです。

実際のファイルは、MCUXpressoでは、ビルド後にDebugフォルダ内につくられます。拡張子はldです。

中身はどんなの?ためしにみてみる

スクリプトは、MEMORYとSECTIONの主に二つに分けられています。まずはMEMORY。

MEMORY 
{
/* Define each memory region */
  PROGRAM_FLASH (rx) : ORIGIN = 0x0, LENGTH = 0x80000
  SRAM_UPPER (rwx) : ORIGIN = 0x20000000, LENGTH = 0x8000
  SRAM_LOWER (rwx) : ORIGIN = 0x1fff8000, LENGTH = 0x8000
  FLEX_RAM (rwx) : ORIGIN = 0x14000000, LENGTH = 0x1000   
}

MEMORYには実際の物理的なメモリ領域が定義されます。PROGRAM_FLASHだとか、SRAM_UPPERだとか、各メモリ領域に任意の名前が付けらていて、ORIGINはその領域の先頭アドレス、LENGTHがそのサイズです。

例) SRAM Lower 0x8000 = 32768, 32768B=32768 /1024 KB = 32KB

後で説明するSECTIONで、オブジェクトファイルのMEMORYで定義された領域にオブジェクトファイルがひもづけられてきます。

オブジェクトファイルの構成

SECTION内のスクリプトを確認する前にリンカーの機能をざっくりと確認します。オブジェクトファイルは下のように.text .data .bssという3つのセクションに分けられています。

.text はプログラム、 .dataは初期値をもつ変数、.bssは初期値をもたない変数のあつまりになります。リンカーはコンパイルするとプログラムの数だけ作成されるオブジェクトファイルからプログラムはプログラム同士、変数は変数同士とかでまとめ、指定したアドレスに割り当てます。

Linker Scriptはそのためのコマンドがつまったスクリプトです。

.mapファイルとLinker Scriptの関係

似たようなファイルにmapファイルというものがありますが、これはLinker ScriptによってLinkerが出力したファイルになります。Linker scriptが指示書で、mapファイルが完成図書みたいなものです。Linker Scriptが狙い通り動いたかどうかは、mapファイルで確認できます。

SECTIONの書き方

実際のスクリプトは複雑なので、簡単なものだけとりあえず抜き出しました。

メインコード上で、配列を定義してみます。

unsigned char bufbuf[500];

初期値がないので、bssです。上のスクリプトだとすべてのbssはSRAM_UPPERに保存されるので、生成されたマップファイルを確認すると、

mapファイルの中身

たしかにSRAM Upperの領域(0x2000_0000 ~ )に配列が定義されてました。メモリがオーバフローしてもLowerにいかない理由は、bssを一括でSRAM_UPPERにいているためかもしれません。

変数をセクションで分けて、選択的に変数をLowerとUpperに分けていきます。

__attribute__ ((section (".bss_bufbuf"))) unsigned char bufbuf[500];
unsigned char bufbuf2[500];

こ うすると、.bss_bufbufという新たなセクションに配列bufbufが格納されます。コード側でこうやって変数のセクションを明示しておくと、linker script側で、そのセクションだけメモリの割り当て方を変えられます。比較のため、bufbuf2を従来どおりの宣言で定義しときます。

.bss:
{
  *(.bss)
}>SRAM_UPPER
 .bss_RAM2:
{
 *(.bss_bufbuf)
}>SRAM_LOWER

対応したlinker scriptが上です。bss_bufbufに対してのみSRAM_LOWERで定義するようにしました。再度、ビルドを実行して、マップファイルを確認しました。

bufbufはSRAM LOWERにうつった
bufbuf2はUPPERで定義された

宣言どおり、bufbuf2だけSRAM_LOWERに定義されたことが分かりました。

ちなみに冒頭の*(.isr_vector)も下のように割り込みベクターテーブルというポインタの配列をisr_vectorというSection内で定義されたものでした。

つまり、コード側はSectionを定義し、メモリの割り当ては、Section毎にLinker scriptで決めるといった関係になっています。

Location counter

SECTIONS内ではlocation counterとよばれるドット( . )が使われます。下がそのサンプルです。_etextと_sdataとかに( . )が代入されています。( . )は、各セクション内のアドレスを示していて、たとえば、途中に *(.data)とかがはいるとそのデータサイズ分だけ自動でオフセットしてくれます。

各シンボルの具体的なアドレス値を右側に示しています。text, data, bssを挟むとアドレス値が繰り上がっていくのがわかります。

( . ) はセクション内での参照アドレスなので注意です。なので、最初に、0で初期化しても上の結果とかわりません。ちょっと混乱しますが、_sbss_lowとかのシンボルは、最終的に( . ) + MEMORYで定義した領域の先頭アドレスがたされて出てきます。

location counterを使うと、こんな感じで、各データやプログラムの境界のアドレスを簡単に出力できるようになります。

memory アライメント

SRAM_LOWERのbssセクションですが、bss_bubufのサイズが0x1F8になっています。データサイズは500バイトなので、0x1F4で4バイト増えてます。このときのmapファイルが下のとおりです。

ちょうど赤枠の部分です。データ自体は0x1FFF_91F4までですが、fillというところで4バイトオフセットしています。これはメモリのアライメント調整のためです。ここでは仕様上8バイト境界として、データの先頭アドレスは8の倍数おきにそろえる必要があります。

アライメント?何それという方は、以前関連記事を書いてますので、参考にしてください。

メモリアライメント関連記事

Linker Scriptでアライメントの調整は下のようにします。

. = ALIGN(8)は、 bss_bubufが1バイトの場合は、7バイトオフセット。5バイトの場合は、3バイトオフセットみたいにアライメントを動的に調整してくれます。

Cコード側での使い方

main プログラムの手前で動くStart upでは、.dataのセクション(初期値あり変数)をmainプログラム中に変数として取り扱えるようにSRAMに値をコピーする必要があったりとかして、その時にLinker Scriptで定義したアドレス値が必要になります。

Cコード側でLinker Scriptのシンボルを使うには、下のようにexternで宣言すればよいです。

extern unsigned int _etext; 

extern unsigned int _sdata;
extern unsigned int _edata;

extern unsigned int _sbss;
extern unsigned int _ebss;

extern unsigned int _sbss_low;
extern unsigned int _ebss_low;

実際のアドレスは&をつけて、&_extextで取得する必要があります。

etextAddr = &_etext;  //&をつけてね

Manage Linker Script

ここから、MCUXpressoの話です。MCUXpressoはLinker Scriptをユーザが直接編集しなくてよいようにスクリプトの自動生成機能をもっています。バックグランドで、FreeMakerというソフトを動かしてるそうです。

これまで、手修正前提で話をすすめていましたが、MCUXpressoでビルドするとこの自動生成機能が修正したldファイルを上書きしてしますので、注意が必要です。

Manage Linker Script オフ

機能を使いたくない場合は、プロジェクト→右クリック→Propertiesで、下の画面のチェックボックスのチェックを外します。

Defaultでは、この機能はONになっているので、とりあえずそのままで、ldファイルを生成しておき、手修正が必要なタイミングで、OFFにするのが一般的な使い方だと思います。

Manag Linker ScriptでSectionを定義

せっかくなので、この機能をつかって、上でやったbss_bufbufセクションを設定したいと思います。

さっきの画面で、Extra linker script input sectionのプラスアイコンをクリックします。すると、下にdummyのsectionが追加されます。

Section名を*(.bss_bufbuf)、RegionをSRAM_LOWER、Section Typeを.bssに設定して、Applyボタンをクリックします。

ビルドすると、ldファイルにbss_bufbufセクションが追加されます。

以上で、設定は完了です。

MCUXpresso上でメモリコンテンツを確認

MCUXpressoには、Image Infoというメモリコンテンツを確認できる便利なツールがあるので、結果の確認をそれで行います。画面下のImage infoタブをクリック。

デバッグ実行後に右上のアイコンをクリックして、テーブルを更新します。すると、メモリのリージョン毎に設定されたセクションの確認ができます。

bss_bufbufというセクションはリストにありませんが、かわりにbss_RAM2というセクション内にbufbufという配列が書かれていることが分かります。(エイリアスでRAM2にしているため)

Heap, Stack領域を定義

Manage Linker Scriptでは、Sectionの定義の他にもHeap、Stuckの領域も設定できます。ここでは、Heapサイズを変更します。Defaultでは、SRAM_UPPERに2KBを確保するようになっていますが、ここでは0KBにして、Heap領域をなくします。

プログラム実行時のHeapをimage infoで確認した結果が下です。

Heapが0KBになりました。

以上になります。説明不足な箇所もありますが、それはまたの機会に調べて、まとめていきたいです。Linker scriptばんざい。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です