オブジェクトの配置

Windowsプログラミングに慣れてしまうと、プログラムやデータといったオブジェクトがどこに配置されるのかについて感心が薄れます。UnixやWindowsではそういった問題はプログラマから見えなくなっており、OSが適切に処置してくれます。これはひとえにメモリーの大きさから来るものであり、大容量のメモリーがあるからこそOSは動的にアプリケーションにメモリーを割り付けることができます。また、汎用OSという性質上動的にメモリーを割り付けなければ次々変化するアプリケーションに対応できないという事情もあるでしょう。

一方で、組み込みソフトウェアにおいてはメモリーの管理は重大事です。多くの場合OSがないため、どこにあるメモリーを何に使うかはプログラマが制御しなければなりません。OSがある場合でもデータとプログラムの振り分けはプログラマの責任であることがあります。

ADSP-2191の場合、命令ワードとデータ・ワードの大きさが違うことが、問題に輪をかけます。間違った位置にプログラムを置けばそれは決して動作しません。そこでオブジェクトの配置が非常に重要になります。これを司るのがリンカーです。

語長

リンカーがオブジェクトを配置する際にキーになるのが語長です。ADSP-2191の命令の語長は24ビットであり、データの語長は16ビットです。一方、ADSP-2191の内蔵アドレス空間には24ビット長のSRAMと16ビット長のSRAMが配置されています。したがって、間違えて16ビットSRAM上に命令を配置するようなことがあればそのプログラムは正しく走りません。他方でポストモディファイDM転送およびポストモディファイPM転送命令を見てわかるように、データは16ビット・メモリ、24ビット・メモリのどちらにおいても正しく読み書きすることができます。

このように、ADSP-2191はその特殊な語長構成から汎用CPUよりもオブジェクト配置に注意が必要です。

セクション

VisualDSP++のアセンブラはプログラムの中の任意の単位をセクションとしてまとめあげる機能を持っています。セクションの宣言は以下のような構文です

<セクション宣言> ::= .section/<type> <section名>;
<type>           ::= PM, DM, CODE, DATA

セクション宣言を行うとそれ以降の命令やデータはすべてそのセクションに含まれることになります。これは新たなセクションを宣言するまで続きます。セクションは開始のみ宣言し、終わりを宣言する必要はありません。また、すべてのオブジェクトは何らかのセクションに含まれていなければなりません。複数のソースファイルの中で同じ名前のセクションを宣言してもかまいません。これら同じ名前のセクションはリンク時に一箇所にまとめられます。

以下に例を示します。この例では最初の行でdata1という名前のセクションを宣言しています。2行目で宣言さた変数alphaはこのdata1セクションに含まれます。また、sub:以下のプログラムはprogramセクションに含まれます。

.section/dm data1;
.var alpha;

.section/pm program;
sub:
    nop;
    ...

セクション宣言で重要なのは"/"に続く<type>です。この部分はオブジェクトを24ビットに配置するか16ビットに配置するかを指定する部分です。ここには4種類の型を指定できますが、PMとCODE、DMとDATAは同じものですので、実際には2種類となります。すでにお分かりだと思いますが、PMあるいはCODEと記述されたセクションは24ビット・メモリーに配置されなければならず、DMあるいはDATAと記述されたセクションは16ビット・メモリーに配置しなければならないと宣言したことになります。

ADSP-2191はデータを24ビットSRAMに置いた場合と16ビットSRAMに置いた場合で何ら変わりなく動作することができます。したがって、DMあるいはDATAと宣言したセクションは「16ビットあるいは24ビット」メモリーに配置されるべきです。しかしながら、VisualDSP++3.0ではこれは許されず16ビット・メモリーにのみ配置が許されます。

セクションには任意の名前を付けることができます。しかし、一般には以下の3種類のセクションを使います。この3つはVisualDSP++3.0のライブラリやスタートアップ・ランタイム、標準のLDFが使用していますのでこれにのっかるほうが手間が省けて便利です。

.section/code program;    // プログラム
.section/data data1;      // 大域データ
.sectoin/data data2;      // PMバスからアクセスする大域データ

LDF

前の節にLDFという言葉が出てきました。これはLink Description Fileの略で、リンカーにどのような仕事をするのか詳細を説明するファイルです。

セクション宣言を行うことで大まかにプログラムやデータといったオブジェクトをどんなメモリーに配置するかを指定できます。しかしながら、何番地に配置するかといったことは指定できません。オブジェクトの中には割り込みベクトルのように特定の番地に配置しなければならないものがあります。こういったオブジェクトは特別なセクション名を持っており、リンカーはそれらを正しいアドレスに配置しなければなりません。しかしリンカー自身は何が正しいかを知りません。

リンカーに正しい配置を説明するのがLDFの仕事です。このファイルには物理メモリーの宣言と、セクションを物理メモリに配置するためのマッピング宣言を記述します。リンカーはLDFを読み込んで、それに従ってオブジェクトを配置します。

LDFの基本構造は単純でそれほど恐れるようなものはありません。しかし作るとなれば作業量は大きなものになります。リンクするオブジェクトはプログラマが作った物以外にもランタイムなどがありますし、ものによっては特殊な配置が必要です。

標準LDF

LDFを作るにはうんざりするほどたくさんのライブラリやセクションのことを理解しなければなりません。しかもその多くは文書化されていません(-_-)。そこで、普通はVisualDSP++についてくる標準のLDFを流用します。標準LDFを使う方法は簡単です。プロジェクトの中でLDFを明示的に指定しなければ、VisualDSP++は自動的に適切なLDFを選び出して使用します。

標準のLDFではCおよびC++言語用のセクションは次のように配置されています。これらのうちIVxxxは割り込みベクトルです。heap/stackは実際にはセクション名としては存在しませんがヒープスタック用に確保されている領域です。また、programセクションはEZ-KIT Liteライセンス下では8KWに制限されます。ライセンスによる切り替えはプリプロセッサ命令を使った条件ビルドによります。

セクション名 SRAM (bit) 領域(KW)
IVxxx 24 約0.5
program 24 約31.5
data1 16 16.0
data2 16 11.75
ctor 16 0.25
heap 16 2.0
stack 16 2.0

EZ-KIT Liteを使って遊ぶ分には標準のLDFで十分間に合うはずです。もし、標準LDFでは都合が悪い場合には、標準LDFをコピーして改造するとよいでしょう。標準LDFのパスはVisualDSP\219x\LDF\ADSP-2191.LDFです。このファイルを自分のプロジェクト・ディレクトリにコピーして、"MEMORY"キーワードで始まる物理メモリの割り付けをいじれば大抵の場合は間に合うでしょう。

VisualDSP++3.0からExpert Linkerが提供されました。VisualDSP++のプロジェクト・ウインドウでLDFをダブルクリックするとExpert Linkerが立ち上がります。この機能を使うと設定を視覚的に把握できるのでこちらのほうが好きな人もいるでしょう。しかし、VisualDSP++3.0の時点ではExpert Linkerは条件ビルドを理解しません。そこで、Exper Linkerを使う前に手作業でLDFから条件ビルドを取り除いて置いてください。

おまけ:標準LDFを読み解く

本来、私はLDFの全貌を知る必要などないと考えています。必要なときにちょいちょいと標準LDFをいじればいいのであって、しかもEZ-KIT Liteユーザーであるのならいじる必要すらないだろうと思っています。が、そうはいっても知りたいという人もいるでしょうし、仕事で使う場合にはLDFをかなりいじらないといけないかもしれません。そこで、以下に標準LDFの簡単な読み下しを行います。

標準LDFの種類

VisualDSP++3.0には標準LDFが4種類ついてきます。

ADSP-2191_CPP.LDF
C++言語専用のLDFです。C++言語に必要なスタートアップ・ランタイムやライブラリをリンクします。
ADSP-2191_C.LDF
C言語専用のLDFです。C++とは異なるスタートアップ・ランタイムおよびライブラリを使います。
ADSP-2191_ASM.LDF
アセンブリ言語専用のLDFです。構造は前二者に非常に似ています。また、C言語のライブラリもリンクされます。しかしスタートアップ・ランタイムと割り込みベクトル・テーブルは自分で用意しなければなりません。
ADSP-2191.LDF
C++/C言語両対応のLDFです。切り分けはリンク時にプリプロセッサ・マクロで行います。プロジェクトでLDFを指定しないときに使用されるのはこれです。割り込みを使用しないならばアセンブリ言語で組んだプログラムもこのLDFを使うことができます。

アセンブリ言語でプログラムを組むときにはADSP-2191_ASM.LDFを使います。このとき、スタートアップ・ルーチンやベクトル・テーブルは自分で用意しなければなりません。これらの配置規則はLDFに従わなければなりませんが、それは以下で説明します。

では、早速LDFを読んでみましょう。まな板に乗せるのはADSP-2191.LDF 1.1.4.1です。

アーキテクチャー定義など

最初に現れるのはアーキテクチャー宣言とサーチ・ディレクトリの定義です。アーキテクチャー宣言は使用するプロセッサに関する宣言で、ここではADSP-2191が宣言されています。

サーチ・ディレクトリはライブラリを探す場所を指定します。ディレクトリの中に$ADI_DSPという文字列がありますが、これはインストール・ディレクトリを指すマクロ変数です。

以下、マクロという言葉が二つの意味で使われますので注意してください。マクロ変数といったとき、これはLDFの文法が規定する変数です。この変数には任意のファイル名をコンマで区切って代入できます。マクロ変数はファイル名を置ける場所であれば自由に使うことができます。一方マクロ・シンボルといったとき、それはプリプロセッサが#defineあるいはコマンドライン・オプションで定義した定数シンボルです。マクロ・シンボルは条件リンクの制御に使います。

最後のコメント部分はADSP-2191の物理メモリーの配置を示しています。64KWのメモリーが4ブロックから構成されていることがわかります。異なるブロックにあるメモリーはデュアル・データ転送が可能です。デュアル・データ転送に関してはDAGを参照してください。

// $Revision: 1.1.4.1 $

ARCHITECTURE(ADSP-2191)

SEARCH_DIR( $ADI_DSP\219x\lib )

// ADSP-2191 Memory Map (Internal)
//
// Block  Start Address  End Address  Page  Content 
// ===================================================
// 0      0x00 0000      0x00 3fff    0     24 Bit Program Memory RAM
// 1      0x00 4000      0x00 7fff    0     24 Bit Program Memory RAM
// 2      0x00 8000      0x00 bfff    0     16 Bit Data Memory RAM
// 3      0x00 c000      0x00 ffff    0     16 Bit Data Memory RAM

シンボルの保護

リンカーの動作には「DXEファイルから呼ばれていないコードをすべて削除する」というものがあります。これはコードサイズを減らすためですが、実際には呼ばれていないように見えても間接的に呼ばれているラベルなどもあります。なんにせよ、重要なラベルは保護したほうがいいでしょう。

KEEP宣言によって、重要なラベルとそれに続くコードを削除対象からはずすことができます。ここでは_mainと__ctor_NULL_markerラベルが削除対象からはずされています。この二つのラベルから到達可能なラベルはすべて保護されると考えても結構です。

// do not allow linkers -e(elimination) various symbols
//  _main - C/C++ application main() function
//  __ctor_NULL_marker - static ctor list null terminator
KEEP(_main,__ctor_NULL_marker)

割り込みベクトル・テーブル

割り込みテーブルの構築は組み込みプロセッサのプログラミングの中でも退屈な部分です。そこでVisualDSP++はC/C++言語から割り込みを管理できるようにしています。この部分は標準割り込みテーブルを$INTTABマクロ変数に割り当てています。

// Example interrupt vector table
$INTTAB    = 219x_int_tab.doj;

IOライブラリ

VisualDSP++環境ではC/C++の標準IOを使うことができます。しかしながらこれらのIOは多くのステップを必要とするため、シミュレーターでは速度が低下します。そこで、シミュレーターには標準IOの処理をフックしてPC側で処理してしまう高速実行機能が装備されています。

この高速実行を使うために__USING_LIBSIMマクロによる条件リンクを行い、高速IOライブラリのリンクが行えるようにしています。使用する場合にはリンカーのオプションからコメントで指示されている"-MD__USING_LIBSIM=1" コマンドライン・オプションを与えます。

// libsim provides fast, mostly host emulated IO only supported by
// the simulator. The libio library provides IO processing mostly
// done by the 219X target that is supported by the emulator and
// simulator. Libio is the default used, but if __USING_LIBSIM is
// defined libsim will be used.
//   from the driver command line, use options,
//          "-flags-link -MD__USING_LIBSIM=1"
//   in the ide, add -MD__USING_LIBSIM=1 to the linker additional
//   options

#ifdef __USING_LIBSIM
$IOLIB     = libsim.dlb;
#else  // !__USING_LIBSIM
$IOLIB     = libio.dlb;
#endif //  __USING_LIBSIM

C/C++ライブラリ

C言語とC++言語は使用するライブラリが異なります。また、スタートアップ・ランタイムも異なります。そのため、両対応するADSP-2191.LDFは条件リンクを使ってライブラリとランタイムを構築します。それぞれは以下のマクロ変数にまとめられます。

CLIBS
C/C++言語から使用するライブラリ
START
スタートアップ・ランタイム
STARTEZ
EZ-KIT Lite専用スタートアップ・ランタイム

これらのマクロはLDFの後ろのほうで参照されます。条件リンクが参照するマクロ・シンボル__cplusplusはIDDEが設定してリンカーに渡します。

// When an object that was compiled as C++ is included on the
// link line the __cplusplus macro is defined to link with the
// C++ libraries and runtime mechanisms. The compiler driver
// (cc219x) should be used to link C++ compiled objects to
// ensure that any static initialisations and template C++
// matters are resolved.

#ifdef __cplusplus
$CLIBS     = libc.dlb, libdsp.dlb, libcpp.dlb, libetsi.dlb, libcpprt.dlb;
$START     = 219x_cpp_hdr.doj;
$STARTEZ   = 219x_cpp_ezkit_hdr.doj;
#else  // !__cplusplus
$CLIBS     = libc.dlb, libdsp.dlb, libetsi.dlb;
$START     = 219x_hdr.doj;
$STARTEZ   = 219x_ezkit_hdr.doj;
#endif //  __cplusplus

EZ-KIT Lite用オブジェクト

EZ-KIT LiteはUSBマイコン上のモニタープログラムがJTAGポート越しにDSPにアクセスすることで、DSP側に余計な常駐コードを持つことなくデバッグ環境を構築します。しかしながら、DSP側で次の条件を満たさなければデバッグができません。

たいした条件ではありません。これらの条件を設定するコードを実行するスタートアップ・ランタイムが用意されおり、条件リンクでそちらを選ぶことになっています。条件として参照するマクロ・シンボル__EZKIT_LICENSE_RESTRICTION_21xx__はIDDEが設定してリンカーに渡します。

// EZ-Kits:
// The linker, upon invocation, will determine if the user has a restricted
// license or not.  If the user has a restricted license, the preprocessor
// macro __EZKIT_LICENSE_RESTRICTION_21xx__ shall be defined.  This default
// LDFs uses this macro to support linkage with a restricted licenses.
// Furthermore, for those EZ-Kits that require an on-target monitor program,
// the LDF will also ensure that this monitor is not overwritten by the users
// executable image.
// It will also ensure that the DSP is correctly initialised for
// the monitor program by using alternate CRT objects.

#ifdef __EZKIT_LICENSE_RESTRICTION_21xx__  
$OBJECTS   = $STARTEZ, $INTTAB, $COMMAND_LINE_OBJECTS;
#else
$OBJECTS   = $START, $INTTAB, $COMMAND_LINE_OBJECTS;
#endif

ライブラリのまとめ上げ

条件リンクによって選択したC/C++ライブラリや入出力ライブラリはLDFのマクロ変数に格納しています。これらを一つのマクロ変数$LIBRARIESにまとめます。

// Libraries from the command line are included in COMMAND_LINE_OBJECTS.
$LIBRARIES = $IOLIB, $CLIBS;

コンパイラが生成するセクションについて

以下の長いコメントは、コンパイラが生成するデフォルト・セクションに対応する物理メモリーの分割を説明しています。対応は以下のとおりです。

役割 セクション名 物理メモリー・セグメント名 語長
プログラム program mem_code 24
データ data1 mem_data1 16
PMアクセス・データ data2 mem_data2 16
ヒープ heap mem_heap 16
スタック stack mem_stack 16

data2はデュアル・アクセス時にPMバス経由でアクセスされます。配置は16ビット語長の物理メモリー上ですが、ADSP-2191のアーキテクチャーは問題なくアクセスすることができます。この辺の話はポストモディファイDM転送ポストモディファイPM転送を参照してください。

// Compiler defaults:
// The default program memory used by the compiler will be in
// a section called program, and gets placed in a memory segment
// being defined below as mem_code.
//
// The default DM data memory used by the compiler will be in
// a section called data1, and gets placed in a memory segment
// being defined below as mem_data1.
//
// The default PM data memory used by the compiler will be in
// a section called data2, and gets placed in a memory segment
// being defined below as mem_data2.
//
// The memory used for mem_data2 is actually DM/16 bit memory but
// as the compiler will not require the full 24 bits 16 bit memory
// can be used. This is good because 24 bit memory is in short
// supply and it avoids having to change the DMPG registers for each
// read/write as the two data sections are on the same page.
// The dual access instructions behave as expected as long as the
// memory being accessed is in different block, as is the case
// here with the way mem_data and mem_data2 are defined.
//
// The memory segment used for dynamic memory used by allocation
// routines such as malloc will is called mem_heap.
//
// The memory segment used for the software stack pointed to by
// STACKPOINTER(I4) and FRAMEPOINTER(I5) is called mem_stack.
//
// The memory segment used to store a null terminated list of
// C++ function pointer that are static constructors to be called
// before main, is called mem_ctor.

物理メモリーを区分けする

ここでは先のコメントに従って物理メモリーを区分けしています。区分けはメモリー・セグメント宣言の羅列です。メモリー・セグメント宣言は次の形式になっています。

<セグメント名> { <型宣言> <開始アドレス> <終了アドレス> <語長> }

はじめにメモリー・セグメント宣言をまとめるMEMORY節から始まります。

// memory map:
// This memory map is set up to facilite testing of the tool
// chain -- code and data area are as large as possible. Code
// is placed in page 0, starting with space reserved for the
// interrupt table. All data is placed in page 1. Note that
// the run time header must initialize the data page registers
// to zero to match this placement of program data. All pages are
// 64K words.

MEMORY
{

メモリー・セグメントの最初の宣言はリセット・ベクトルです。ここにはリセットが起きたときに実行される命令が配置されます。ベクトルの要素は32ワードですので、最大32命令を配置することができます。C/C++の場合、 219x_int_tab.dojにこのベクトルの定義が記述されています。

       // The memory section where the reset vector resides
   mem_INT_RSTI     { TYPE(PM RAM) START(0x000000) END(0x00001f) WIDTH(24) }

ADSP-2191_ASM.LDFの場合もこの宣言を持っています。しかし、対応するセクションはライブラリやオブジェクト・ファイルには定義されていません。従って、プログラマが自分でこのセグメントに配置するコードを書かなければなりません。

割り込みベクトルの宣言が続きます。この部分はリセット・ベクトルと同様です。

       // The memory sections where the interrupt vector code and an
       // interrupt table used by library functions resides. The
       // library functions concerned include signal(), interrupt(),
       // raise(), and clear_interrupts()
   mem_INT_PWRDWN   { TYPE(PM RAM) START(0x000020) END(0x00003f) WIDTH(24) }
   mem_INT_KERNEL   { TYPE(PM RAM) START(0x000040) END(0x00005f) WIDTH(24) }
   mem_INT_STKI     { TYPE(PM RAM) START(0x000060) END(0x00007f) WIDTH(24) }
   mem_INT_INT4     { TYPE(PM RAM) START(0x000080) END(0x00009f) WIDTH(24) }
   mem_INT_INT5     { TYPE(PM RAM) START(0x0000a0) END(0x0000bf) WIDTH(24) }
   mem_INT_INT6     { TYPE(PM RAM) START(0x0000c0) END(0x0000df) WIDTH(24) }
   mem_INT_INT7     { TYPE(PM RAM) START(0x0000e0) END(0x0000ff) WIDTH(24) }
   mem_INT_INT8     { TYPE(PM RAM) START(0x000100) END(0x00011f) WIDTH(24) }
   mem_INT_INT9     { TYPE(PM RAM) START(0x000120) END(0x00013f) WIDTH(24) }
   mem_INT_INT10    { TYPE(PM RAM) START(0x000140) END(0x00015f) WIDTH(24) }
   mem_INT_INT11    { TYPE(PM RAM) START(0x000160) END(0x00017f) WIDTH(24) }
   mem_INT_INT12    { TYPE(PM RAM) START(0x000180) END(0x00019f) WIDTH(24) }
   mem_INT_INT13    { TYPE(PM RAM) START(0x0001a0) END(0x0001bf) WIDTH(24) }
   mem_INT_INT14    { TYPE(PM RAM) START(0x0001c0) END(0x0001df) WIDTH(24) }
   mem_INT_INT15    { TYPE(PM RAM) START(0x0001e0) END(0x0001ff) WIDTH(24) }
   mem_itab         { TYPE(PM RAM) START(0x000200) END(0x000241) WIDTH(24) }

続く部分はprogramセクションを配置するためのmem_codeセグメントです。

EZ-KIT Liteライセンスでは、programセクションのサイズが8KWに制限されています。そこでこの範囲を超えないように__EZKIT_LICENSE_RESTRICTION_21xx__が定義されているときにはサイズを8KWに絞っています。一般ライセンスの場合は32KWを割り当てています。

#ifdef __EZKIT_LICENSE_RESTRICTION_21xx__  
       // see comments above wrt this macro
// The license that is shipped with the EZ-KIT Lite imposes an
// 8K words Program Memory (PM) limitation.
   mem_code         { TYPE(PM RAM) START(0x000242) END(0x001fff) WIDTH(24) }
#else  // !__EZKIT_LICENSE_RESTRICTION_21xx__
   mem_code         { TYPE(PM RAM) START(0x000242) END(0x007fff) WIDTH(24) }
#endif // __EZKIT_LICENSE_RESTRICTION_21xx__

のこりはデータやヒープ、スタックのための領域宣言です。ただし、__cplusplusが定義されているときにはC++用に特別の領域を確保しています。

   mem_data2        { TYPE(DM RAM) START(0x008000) END(0x00aeff) WIDTH(16) }

#ifdef __cplusplus
   mem_ctor         { TYPE(DM RAM) START(0x00af00) END(0x00afff) WIDTH(16) }
   mem_heap         { TYPE(DM RAM) START(0x00b000) END(0x00b7ff) WIDTH(16) }
#else
   mem_heap         { TYPE(DM RAM) START(0x00af00) END(0x00b7ff) WIDTH(16) }
#endif

   mem_stack        { TYPE(DM RAM) START(0x00b800) END(0x00bfff) WIDTH(16) }

   mem_data1        { TYPE(DM RAM) START(0x00c000) END(0x00ffff) WIDTH(16) }

} // end of memory map

以上が物理メモリーの分割です。ここではメモリーを分割するだけで上に述べたような対応付けは行わないことに気をつけてください。

セクションをマッピングする

セクションを物理メモリーにマッピングするのはLDFの後半で行います。ここはやや特殊な様相を示しています。というのは、もともとこの部分はSHARC DSPのマルチ・プロセッサ・アプリケーションに対応するため、ADSP-2191には余計な記述があるのです。とにかく見てみましょう。

マッピングはPROCESSOR節で行います。ここはマルチプロセッサ用に複数のプロセッサ節を記述できるようになっていますが、ADSP-2191の場合は知らない顔をしてコピー&ペーストで終わりです。

PROCESSOR p0
{
    LINK_AGAINST( $COMMAND_LINE_LINK_AGAINST)
    OUTPUT( $COMMAND_LINE_OUTPUT_FILE )

次のSECTIONS節こそが、セクションから物理セグメントへのマッピングを宣言する部分です。読みやすそうなところを抜き出して解説してみましょう。下のコードはdata1セクションのマッピングの宣言です。

       data1_dxe { 
           INPUT_SECTIONS( $OBJECTS(data1) $LIBRARIES(data1) ) }
           > mem_data1

この部分は次のような操作を行うことを宣言しています。

  1. $OBJECTS変数に名前が登録されているファイルから"data1"セクションのオブジェクトを抽出する
  2. $LIBRARIES変数に名前が登録されているファイルから"data1"セクションのオブジェクトを抽出する
  3. 1,2で抽出したオブジェクトをdxeセグメント"data1_dxe"に格納する
  4. dxeセグメント"data1_dxe"を物理メモリー・セグメント"mem_data1"に格納する

この4ステップのうち、3は余計です。どう頭をひねってもこの操作は不要に感じるのですが、文法上そうなっているので仕方ありません。とにかく、1,2,4のような操作を行うよう宣言することによって、"data1"セクションを"mem_data1"セグメントにマッピングしています。

残りの部分も同様の処理を行うことで、コードやデータを物理メモリー上に配置しています。

    SECTIONS
    {
       IVreset_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVreset) $LIBRARIES(IVreset) ) }
           > mem_INT_RSTI

       IVpwrdwn_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVpwrdwn) $LIBRARIES(IVpwrdwn) ) }       
           > mem_INT_PWRDWN

       IVstackint_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVstackint) $LIBRARIES(IVstackint) ) }
           > mem_INT_STKI

       IVkernel_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVkernel) $LIBRARIES(IVkernel) ) }       
           > mem_INT_KERNEL

       IVint4_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVint4) $LIBRARIES(IVint4) ) }
           > mem_INT_INT4

       IVint5_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVint5) $LIBRARIES(IVint5) ) }
           > mem_INT_INT5

       IVint6_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVint6) $LIBRARIES(IVint6) ) }
           > mem_INT_INT6

       IVint7_dxe { 
         INPUT_SECTIONS( $OBJECTS(IVint7) $LIBRARIES(IVint7) ) }
           > mem_INT_INT7

       IVint8_dxe { 
          INPUT_SECTIONS( $OBJECTS(IVint8) $LIBRARIES(IVint8) ) }
           > mem_INT_INT8

       IVint9_dxe { 
          INPUT_SECTIONS( $OBJECTS(IVint9) $LIBRARIES(IVint9) ) }
           > mem_INT_INT9

       IVint10_dxe { 
          INPUT_SECTIONS( $OBJECTS(IVint10) $LIBRARIES(IVint10) ) }
           > mem_INT_INT10

       IVint11_dxe { 
          INPUT_SECTIONS( $OBJECTS(IVint11) $LIBRARIES(IVint11) ) }
           > mem_INT_INT11

       IVint12_dxe { 
           INPUT_SECTIONS( $OBJECTS(IVint12) $LIBRARIES(IVint12) ) }
           > mem_INT_INT12

       IVint13_dxe { 
           INPUT_SECTIONS( $OBJECTS(IVint13) $LIBRARIES(IVint13) ) }
           > mem_INT_INT13

       IVint14_dxe { 
           INPUT_SECTIONS( $OBJECTS(IVint14) $LIBRARIES(IVint14) ) }
           > mem_INT_INT14

       IVint15_dxe { 
           INPUT_SECTIONS( $OBJECTS(IVint15) $LIBRARIES(IVint15) ) }
           > mem_INT_INT15

       lib_int_table_dxe { 
           INPUT_SECTIONS( $OBJECTS(lib_int_table) $LIBRARIES(lib_int_table) ) }
           > mem_itab

       program_dxe { 
           INPUT_SECTIONS( $OBJECTS(program) $LIBRARIES(program) ) }
           > mem_code

       data1_dxe { 
           INPUT_SECTIONS( $OBJECTS(data1) $LIBRARIES(data1) ) }
           > mem_data1

       data2_dxe { 
           INPUT_SECTIONS( $OBJECTS(data2) $LIBRARIES(data2) ) }
           > mem_data2

スタックとヒープについてはやや特殊な処理をしています。スタックやヒープは実行時にランタイムがアプリケーションに対して提供する領域であり、あらかじめ決まった大きさのオブジェクトを割り付けるわけではありません。従って、ランタイムやソースの中にはこの二つの領域に配置すべきセクションはありません。

逆にスタックやヒープの位置はリンク時に決定されるので、その値をランタイムに教えてあげなければなりません。そこで、LDFでは次の5つのシンボルに値を与えてランタイムに情報を渡しています。

        // provide linker variables describing the stack (grows down)
        //   ldf_stack_limit is the lowest address in the stack
        //   ldf_stack_base is the highest address in the stack
       sec_stack {
         ldf_stack_limit = .;
         ldf_stack_base = . + MEMORY_SIZEOF(mem_stack) - 1;
       } > mem_stack

       sec_heap {
         .heap = .;
         .heap_size = MEMORY_SIZEOF(mem_heap);
         .heap_end = . + MEMORY_SIZEOF(mem_heap) - 1;
       } > mem_heap

C++言語が使用するctorセクションはC言語では使いません。従って、ctorをmem_ctorにマップする宣言は条件リンクになっています。

ctorが何かをずいぶん調べたのですがはっきりしたことはわかりませんでした。どうやらconstructorの略らしく、スタート・アップ時にクラスに対して何らかの一括初期化を行うための仕組みのようです。

#ifdef __cplusplus
       sec_ctor {
         __ctors = .;    /* points to the start of the section */
         INPUT_SECTIONS( $OBJECTS(ctor) $LIBRARIES(ctor))
         INPUT_SECTIONS( $OBJECTS(ctor_end) $LIBRARIES(ctor_end))
       } > mem_ctor
#endif

    } // SECTIONS
} // PROCESSOR p0

// end of file

終わりに

LDFはVisualDSP++の中でもっとも複雑なファイルです。上に説明したのは簡単な場合の話であり、全容を理解するのは並大抵のことではありません。興味がある場合には以下のような文書にあたってください。

ee-69はAnalog Devicesが出している技術ノートです。このLDFの解説はアナログ・デバイセズによる日本語訳が技術資料ページにアップロードされています。

一番のドキュメントはLinker and Utilities Manualでしょう。VisualDSP++のオンライン・マニュアルとして提供されています。

2191空挺団 | プログラム | EZ-KIT | こぼれ話 | アーキテクチャー | 命令 | レジスタ | DSP掲示板 | FAQ |