AUTOSARによる開発では、Memory Mappingという仕様により、コンパイラを抽象化した上でコードや変数をメモリ上に配置する仕組みが規定されています。今回はこのMemory Mappingの概要について紹介します。
Memory Mapping
Memory Mappingでは、特定のヘッダファイルに、コンパイラに依存したコードを集約することで、コンパイラを抽象化します。Memory Mappingの仕様書(※1)に記載されている例を見てみましょう。
※1 出典:Specification of Memory Mapping
https://www.autosar.org/fileadmin/user_upload/standards/classic/4-0/AUTOSAR_SWS_MemoryMapping.pdf
For example (BSW Module): 1 #define EEP_START_SEC_VAR_INIT_16 2 #include "MemMap.h" 3 static uint16 EepTimer = 100; 4 static uint16 EepRemainingBytes = 16; 5 #define EEP_STOP_SEC_VAR_INIT_16 6 #include "MemMap.h"
「Specification of Memory Mapping」 (34、35ページ)
上記の例では、Eep(EEPROM Driver)というモジュールの実装において、EepTimer (3行目)とEepRemainingBytes (4行目)というグローバル変数を定義しています。この変数定義の前後に、EEP_START_SEC_VAR_INIT_16 (1行目) /EEP_STOP_SEC_VAR_INIT_16 (5行目)というマクロ定義および"MemMap.h" (2、6行目)というヘッダファイルのインクルードを行っています。
MemMap.hの例はMemory Mappingの仕様書に以下のように記載されています。
For instance: 1 #ifdef EEP_START_SEC_VAR_INIT_16 2 #undef EEP_START_SEC_VAR_INIT_16 3 #define START_SECTION_DATA_INIT_16 4 #elif 5 /* 6 additional mappings of modules sections into project 7 sections 8 */ 9 ... 10 #endif 11 12 13 #ifdef START_SECTION_DATA_INIT_16 14 #pragma section data "sect_data16" 15 #undef START_SECTION_DATA_INIT_16 16 #undef MEMMAP_ERROR 17 #elif 18 /* 19 additional statements for switching the project sections 20 */ 21 ... 22 #endif
「Specification of Memory Mapping」 (36ページ)
上記のコードをコンパイラ(プリプロセッサ)に入力すると、変数の前に定義したマクロEEP_START_SEC_VAR_INIT_16によって、MemMap.hでは、以下のpragmaが有効になることが分かります。
14 #pragma section data "sect_data16"
pragmaでのセクション指定方法はコンパイラによって異なりますが、上記の例では「section data」の後に、対象の変数を配置するセクション名を指定しています。この仕組みにより、前述のFor example (BSW Module)のように、ソースコードをコンパイラに依存させずに開発することができ、実際に使用するコンパイラに応じてMemMap.hを用意することで、自由にセクションを指定することが可能となります。
ただし、gcc(※2)のように、セクションの指定方法がpragmaではないコンパイラなども存在しますので、この方法であらゆるコンパイラに対応できるとは限りません。Memory Mappingの仕様書には、GreenHillsやTASKINGといった5つの特定のコンパイラのみを考慮しており、前述の仕組みをサポートしないコンパイラはAUTOSARではサポートされないと書かれています。(gccがMemMap.hに対応できないというわけではありません)
※2 Using the GNU Compiler Collection (GCC)
6.34.1 Common Variable Attributes
https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-section-variable-attribute
Memory Allocation Keyword
EEP_START_SEC_VAR_INIT_16/EEP_STOP_SEC_VAR_INIT_16といったマクロは、「Memory Allocation Keyword」と呼ばれ、対象とするセクション(コード、変数、CONST変数など)ごとにシンタックスが規定されています。以下は、変数に対するMemory Allocation Keywordのシンタックスの一例です。
<PREFIX>_START_SEC_VAR_<INIT_POLICY>_<ALIGNMENT> <PREFIX>_STOP_SEC_VAR_<INIT_POLICY>_<ALIGNMENT>
※「Specification of Memory Mapping」 (30ページ)
前述の例では、<INIT_POLICY>が「INIT」となっていますが、これは初期値のある変数であることを意味します。他にも、NO_INIT(初期化しない)やCLEARED(ゼロ初期化)などの選択肢があり、初期化するポリシーに応じて、.bssや.dataなど、どのセクションに配置するかを選択することができます。
<ALIGNMENT>は変数を配置するアライメントを意味します。前述の例では「16」となっていますが、EepTimerとEepRemainingBytesは16bitの符号なし整数であるので、アライメントに16を指定しています。同じアライメントの変数を同じセクションにまとめることで、コンパイラによっては、最適なメモリ配置を実現することが可能となります。
その他、Memory Allocation Keywordのシンタックスについて詳細に規定されていますので、ご興味のある方はMemory Mappingの仕様書をご参照ください。
MemMap.hの生成
MemMap.hは、BSW向けに1ファイルで用意され、すべてのBSWのMemory Allocation Keywordに対する定義が含まれます(※3)。SWC向けには、"Test_MemMap.h"のように、 ファイル名にSWC(SwComponentType定義)のショートネームが入り、SWCごとに用意されます。
※3 R4.1以降では、BSWごとにファイルを分ける仕様となっています。
各MemMap.hに含まれる情報は、使用するすべてのBSW、SWCに対して用意する必要があり、すべてのMemMap.hを手作業で作成するのは容易ではありません。 そこで、Memory Mappingには、BSW、SWCごとに定義された情報(※4)と、コンパイラごとに設定した情報(※4)から、ジェネレータによって、MemMap.hを生成する仕様も規定されています。
※4 どちらもarxml形式、AUTOSAR仕様で標準化されています。
BSW、SWCごとに定義された情報には、MemorySectionという情報が設定されています。MemorySectionは、そのコンポーネントの実装に登場するMemory Allocation Keywordごとに設定されます。また、各MemorySectionには、SwAddrMethodへの参照情報が含まれています。SwAddrMethodは、MemorySectionを共通的に扱うグループのようなものです。
一方、コンパイラごとに設定した情報は、使用するSWC、BSWのMemorySection、SwAddrMethodを踏まえて、割り付けるセクションやアライメントを設定するpragmaを設定します。ここで、pragmaを設定する単位は、MemorySection単位(MemMapSectionSpecificMapping)と、SwAddrMethod単位(MemMapGenericMapping)のどちらでも可能です。
例えば、「VAR_NO_INIT_8」「VAR_NO_INIT_16」「VAR_NO_INIT_32」というMemorySectionがあり、すべてのMemorySectionが「VAR_NO_INIT」というSwAddrMethodを参照していたとします。3つのMemorySectionに所属する変数を、それぞれ別のセクションに配置したい場合は、MemMapSectionSpecificMappingを使用して、それぞれのpragmaを設定することになります。
すべて同じセクションに配置してよい場合は、MemMapGenericMappingを使用することで、1つのpragmaのみ設定すればよくなります。
このように、実際にメモリ配置を行う際に、使用するコンパイラに応じて、適切なメモリ配置を実現します。
セクション指定における注意事項
関数内のstatic変数
C言語では、関数内にstatic変数を定義することが可能ですが、Memory Mappingの仕様書では、関数内のstatic変数の使用を禁止しています。以下のコードを例に説明します。
1 #define Test_START_SEC_CODE 2 #include "Test_MemMap.h" 3 void test_func(void) { 4 static uint32 count = 0; 5 ... 6 } 7 #define Test_STOP_SEC_CODE 8 #include "Test_MemMap.h"
関数内のstatic変数は、コンパイル時のスコープとして機能しますが、コンパイル後の実体はグローバル変数と変わりませんので、上記のcountは、RAM上に配置されます。ここでTest_START_SEC_CODE/Test_STOP_SEC_CODEで設定するpragmaによって、関数内のstatic変数の配置先を指定できないコンパイラを使用する場合、countは標準セクションにしか配置できないことになってしまいます。
そこで、関数内のstatic変数の使用を禁止し、以下の例のように、関数の外へ移動させて実装するように、Memory Mappingの仕様書で規定しています。
1 #define Test_START_SEC_VAR_INIT_32 2 #include "Test_MemMap.h" 3 static uint32 count = 0; 4 #define Test_STOP_SEC_VAR_INIT_32 5 #include "Test_MemMap.h" 6 7 #define Test_START_SEC_CODE 8 #include "Test_MemMap.h" 9 void test_func(void) { 10 ... 11 } 12 #define Test_STOP_SEC_CODE 13 #include "Test_MemMap.h"
コンパイラの最適化
あるコンパイラで意図した通りのセクションに配置できたとしても、異なるコンパイラでは意図しない現象が発生することがあります。例えば、サイズ優先のコンパイルオプションを指定した場合に、サイズ削減のために、コンパイラ独自の共有関数がリンクされ、実装した関数内から呼び出されることがあります。
この際、共有関数が配置されるセクションが、標準セクション固定になっており、呼出し元の関数を配置するセクションと異なってしまうといったことが起きます。
近年のコンパイラは最適化のために様々な機能を有しており、この他にも、コンパイラによって意図しないセクションが登場することがあり得るため、リンク後の最終的なセクション情報を確認することが重要です。
AUTOSAR開発体験キット「メモリ保護編」
本コラムで紹介したメモリマッピングは、AUTOSAR開発体験キットの「メモリ保護編」で実際に使用することが可能です。メモリ保護の実現のためには、コードや変数を意図したメモリ領域に配置することが重要となります。その手段として、AUTOSARで規定されたメモリマッピングを使用しています。MemMap.hを生成するためのジェネレータも同梱していますので、メモリマッピングを使用したAUTOSAR開発を体験することが可能です。