AUTOSARによる開発 ~メモリ配置の仕組み Memory Mapping(メモリ マッピング)

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-2/AUTOSAR_SWS_MemoryMapping.pdf

Example 7.2
For example (BSW Module):
  1 #define EEP_START_SEC_VAR_INIT_16
  2 #include "Eep_MemMap.h"
  3 static uint16 EepTimer = 100;
  4 static uint16 EepRemainingBytes = 16;
  5 #define EEP_STOP_SEC_VAR_INIT_16
  6 #include "Eep_MemMap.h"

「Specification of Memory Mapping」 (35ページ)

上記の例では、Eep(EEPROM Driver)というモジュールの実装において、EepTimer (3行目)とEepRemainingBytes (4行目)というグローバル変数を定義しています。この変数定義の前後に、EEP_START_SEC_VAR_INIT_16 (1行目) /EEP_STOP_SEC_VAR_INIT_16 (5行目)というマクロ定義および"Eep_MemMap.h" (2、6行目)というヘッダファイルのインクルードを行っています。

Eep_MemMap.hの例はMemory Mappingの仕様書に以下のように記載されています。

Example 7.4
   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」 (37ページ)

上記のコードをコンパイラ(プリプロセッサ)に入力すると、変数の前に定義したマクロEEP_START_SEC_VAR_INIT_16によって、Eep_MemMap.hでは、以下のpragmaが有効になることが分かります。

  14 #pragma section data "sect_data16"

pragmaでのセクション指定方法はコンパイラによって異なりますが、上記の例では「section data」の後に、対象の変数を配置するセクション名を指定しています。この仕組みにより、Example 7.2のように、ソースコードをコンパイラに依存させずに開発することができ、実際に使用するコンパイラに応じてEep_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}[_{safety}][_{coreScope}]_{ALIGNMENT}
{PREFIX}_STOP_SEC_VAR_{INIT_POLICY}[_{safety}][_{coreScope}]_{ALIGNMENT}

※「Specification of Memory Mapping」 (25ページ)

前述の例では、{INIT_POLICY}が「INIT」となっていますが、これは初期値のある変数であることを意味します。他にも、NO_INIT(初期化しない)やCLEARED(ゼロ初期化)などの選択肢があり、初期化するポリシーに応じて、.bssや.dataなど、どのセクションに配置するかを選択することができます。

{ALIGNMENT}は変数を配置するアライメントを意味します。前述の例では「16」となっていますが、EepTimerとEepRemainingBytesは16bitの符号なし整数であるので、アライメントに16を指定しています。同じアライメントの変数を同じセクションにまとめることで、コンパイラによっては、最適なメモリ配置を実現することが可能となります。

その他、Memory Allocation Keywordのシンタックスについて詳細に規定されていますので、ご興味のある方はMemory Mappingの仕様書をご参照ください。

MemMap.hの生成

Eep_MemMap.hの"Eep"の部分は、Mip(Module Implementation Prefix)と呼ばれ、BSW(Basic Software)モジュールの実装の識別子となります(※3)。SWC(Software Component)の場合は、Mipではなく、SWC(SwComponentType定義)のショートネームが入ります。

※3 R4.0.3までは、BSW向けのファイル名は"MemMap.h"に固定され、1ファイルに集約する仕様となっています。

MemMap.hは、使用するBSW、SWCごとに用意する必要があり、すべてを手作業で作成するのは容易ではありません。 そこで、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"

コンパイラの最適化

あるコンパイラで意図した通りのセクションに配置できたとしても、異なるコンパイラでは意図しない現象が発生することがあります。例えば、サイズ優先のコンパイルオプションを指定した場合に、サイズ削減のために、コンパイラ独自の共有関数がリンクされ、実装した関数内から呼び出されることがあります。

この際、共有関数が配置されるセクションが、標準セクション固定になっており、呼出し元の関数を配置するセクションと異なってしまうといったことが起きます。

近年のコンパイラは最適化のために様々な機能を有しており、この他にも、コンパイラによって意図しないセクションが登場することがあり得るため、リンク後の最終的なセクション情報を確認することが重要です。

  鴫原 一人博士(情報科学) 鴫原 一人(Kazuto Shigihara) ASI事業部 次長 / エグゼクティブフェロー
この記事を読んだ人はこちらの記事も読んでいます。