2023年06月19日
【第9回】メモリマップ
静的解析/コンパイラ技術
OSの無い環境でC言語プログラムを動かすときは、マイコンのメモリを意識する必要があります。
今回は、メモリとCソースプログラムの関係についての話です。
メモリマップ
マイコンの内蔵メモリには、ROMとRAMがあります。
たとえば、32ビットのアドレス空間がある場合、メモリは0番地から0xFFFF_FFFF番地まであるわけですが、この間にROMの部分とRAMの部分があります。4GBのメモリ空間全体を自由に使えるわけではなく、I/Oを制御するビット情報など、自由に使えないエリアもあります。ROMとRAMはひとつずつではなく、複数箇所に配置されていることもあります。また、アドレスの途中にメモリ未実装のエリアを含む場合もあります。
プログラムは、これらのメモリ状況(メモリマップ)に合わせて配置しなければなりません。メモリマップはマイコンのハードウェアマニュアル等に記載されています。たとえば、こんな感じの図です。
ROMとRAM
RAMエリアは読み書きが可能ですが、ROMエリアには書けません。
変数はRAMに配置しなければなりません。一般的には、コード(関数)と定数(文字列など)はROMに配置します。constという修飾子がありますが、const変数は書き込みできないので、ROMに配置するでしょう。
C言語プログラムのメモリ配置
C言語ソースプログラムの中には、変数の宣言と関数があります。変数には、const修飾付きの変数があります。文字列も使います。ひとつのソースプログラムに書かれたこれらをROMとRAMに分けて配置しなければなりません。
Cソースプログラムに書かれた関数や変数・定数をROMとRAMに配置するために、Cコンパイラは複数のセクションというかたまりの中に、関数、const変数、初期化変数、非初期化変数を集め、アセンブリソースを作り出します。上図のRAM・ROMの箱をセクションに見立てて頂くと良いのですが、実際にはもっと細かいセクション分割を行っています。
そして、分割されたセクションをメモリマップに合わせて、どこのアドレスに配置するかを決めるのがリンク処理です。一般的にはリンカに与えるパラメタを使って、どのアドレスにどのセクションを配置するという細かい指定を行います。この影響もあって、Cソースプログラムに書いた変数の宣言順は、そのままの並びでメモリに配置されません。
変数の初期化
C言語の変数宣言には初期値を指定することができますが、初期化式の無い外部変数や静的変数の初期値はゼロになります。
変数はRAMに配置しますが、CPU起動時のRAMの値は不定です。C言語変数の初期化を有効にするためには、初期値をROMに配置し、C言語プログラムが動く前に初期値をRAM上の変数にコピーしなくてはなりません。さらに、初期化していない変数はゼロにしておかなければなりません。
C言語のmain関数が動作する前に、このような初期化処理を行うことにより、C言語変数の初期値を設定することができます。
main関数が動作する前に行う処理をStartUpなどと呼びます。変数の初期化はStartUp内で行い、C言語プログラムが動作する時には変数の初期化は終わっています。StartUpもC言語の関数と同じようにROMに配置します。
OS上で動くアプリ
OSの無い、組み込み向けのCコンパイラやリンカは、ここまでお話したように色々と面倒です。では、LinuxやWindowsで動作するアプリを作る時はどうでしょう。
以前、GAIOが作っていたCコンパイラもUnixやWindowsで動作するアプリでしたが、意識してリンカを使ったことはありません。たとえば、コンパイラにGCCを使うとして、4つのCソースファイルからバイナリファイルをつくるのは、こんなコマンドを叩くだけです。
これだけで、コンパイルもリンクも終わります。ROMもRAMも意識する必要はありません。
変数の初期化も、アプリをどこのアドレスで動かすかも、OSが管理してくれます。逆に、アプリにどこのアドレスで動きたいなどと要求されては、OSは困るのです。
このGCCでは、プログラムをどこでも動かせるようにするために、コンパイラやリンカは多少の工夫をし、組み込み用のバイナリファイルには無いリロケーション情報等を作りますが、ツールを使うユーザに何か特別な指示してもらう必要はありません。
筆者紹介
浅野 昌尚(あさの まさなお)
ガイオ・テクノロジー株式会社
開発1部 QTXグループ
1980年代から30年以上にわたり汎用構造のCコンパイラ開発に従事し、その間に8ビットマイコンからRISC・VLIW・画像処理プロセッサまで、さまざまなCPU向けのクロスCコンパイラを開発。