ARM
UKサイト Chinaサイト 投資家情報 イベント情報 プレスルーム 採用情報 Document Center


THE ARCHITECTURE FOR THE DIGITAL WORLD
メールサービス
会社概要 マーケット プロダクト&ソリューション テクニカルサポート ドキュメント
テクニカルサポート
TOP > テクニカルサポート > よくある質問-FAQ-
ARM技術サポート
よくある質問-FAQ-
トレーニング
購入方法
ARM開発ツールダウンロード

よくある質問-FAQ-

境界整列アクセスと非境界整列アクセス、および__packedの利用。


はじめに

他のRISCアーキテクチャと同様に、ARMプロセッサは、境界整列データを効率的にアクセスできるように設計されています。境界整列データというのは、4の倍数のアドレスに配置されるワードや、2の倍数のアドレスに配置されるハーフワードです。このようなデータは、自然なサイズ境界に配置されます。

ARMのコンパイラは通常、グローバル変数をこれらの自然なサイズ境界に整列させるので、LDR/STR命令によって変数に効率的にアクセスできます。

これは、命令が非境界整列データに直接アクセスできるほとんどのCISCアーキテクチャとは対照的です。 つまり、非境界整列アクセスを実行する既存のコードをARMに移植する場合には注意が必要です。

非境界整列ポインタ

ARMコンパイラは、通常のCのポインタはメモリ内の境界整列されたワードを指していると想定します。この想定によってコンパイラが効率の良いコードを生成できるためです。

たとえば、ポインタ「int *」がワードの読み出しに使われる場合、ARMコンパイラは、生成するコードの中でLDR命令を使用します。 このコードは、アドレスが4の倍数(ワード境界)の場合に、期待された動作をします。 しかし、アドレスが4の倍数でない場合、LDRは本来の非境界整列ワードのロードを行わず、ロテートされた結果を返します。 ロテートされた結果は、オフセットやシステムのエンディアンによって異なります。

たとえば、0x8006を指すポイントからロードが行われる場合、0x8006、0x8007、0x8008、0x8009からバイトの内容がロードされることが期待されます。 しかし、ARMでは、このようなアドレスのロードは、0x8004、0x8005、0x8006、0x8007から複数バイトの内容がロテートされてロードされます。

つまり、任意のアドレスに存在する可能性のある(つまり、自然な境界ではない位置に存在する可能性のある)ワードを指すポインタを定義したい場合には、ポインタを定義する際に、以下のように「__packed」限定子を使用する必要があります。

  __packed int *pi; // pointer to unaligned int

この場合、ARMコンパイラはLDRを使用せず、ポインタのアライメントに関係なくその値に正しくアクセスできるコードを生成します。 生成されるこのコードは、(コンパイラのオプションによって)複数のバイトアクセスになるか、または、境界に依存するシフト操作が行われた変数とマスク処理の組み合わせになります。そのため、性能が低下し、コードサイズが大きくなります。

メモリマップされたペリフェラルのレジスタには、__packedを使用してアクセスしないでください。ARMコンパイラが、データの取得に複数のメモリアクセスを使用する可能性があり、これによって、他のペリフェラルのレジスタに対応する近隣の位置もアクセスされることがあるためです。 ビットフィールドが使用される場合、現在のARMコンパイラは、指定されたフィールドだけでなく、そのフィールドを含むワードやハーフワード全体をアクセスします。

構造体の中の非境界整列フィールド

グローバル変数が自然なサイズ境界に配置されるのと同様に、構造体の中のフィールドも自然なサイズ境界に配置されます。 つまり、コンパイラは、フィールドを整列させるために、場合によってはフィールド間にパディングを行う必要があります。

また、要求された配置と異なる場合には、__packed限定子を使用してフィールド間のパディングが行われない構造体を生成できます。この場合には、非境界整列アクセスが必要です。

ARMコンパイラが、特定の構造体のアライメントを理解している場合、ARMコンパイラはアクセスするフィールドがパックされた構造体内で整列されるかどうかを判断できます。 このような場合には、可能な限り効率的な境界整列ワード(ハーフワード)アクセスを実行するコードが生成されます。効率的なコードが生成できない場合には、固定シフトおよびマスク機能と組み合わせられた複数の境界整列メモリアクセス (LDR/STR/LDM/STM)が使用されて、メモリ内の正しいバイトにアクセスするコードが生成されます。

これらの非境界整列データへのアクセスがインラインで行なわれるか、あるいは関数呼び出しで行われるかは、「-Ospace」オプション(デフォルト設定。関数呼び出し)と「-Otime」(インラインでの非境界整列アクセス)のコンパイラオプションで制御されます。 以下の簡単な例を参照してください。

__packed struct mystruct {
  int aligned_i;
  short aligned_s;
  int unaligned_i;
};

struct mystruct S1;

int foo (int a, short b)
{
  S1.aligned_i=a;
  S1.aligned_s=b;
  return S1.unaligned_i;
}

上記のコードが「armcc -c -Otime foo.c」でコンパイルされると、生成されるコードは以下のようになります。

コンパイラに多くの情報を渡すことで、境界整列されるフィールドと、されないフィールドを判断させることができます。 そのためには、非境界整列フィールドを__packedで宣言し、struct自体からは__packedを削除する必要があります。 この方法が推奨される方法です。また、この方法は、struct内の自然な境界で整列されたメンバに対して高速にアクセスを行うことが可能な唯一の方法です。 さらに、プログラマが、どのフィールドが非境界整列かを判断できます。しかし、structにフィールドを追加する場合や削除する場合には注意が必要です。
つまり、構造体の定義が以下のように修正された場合を考えてみます。

struct mystruct {
  int aligned_i;
  short aligned_s;
  __packed int unaligned_d;
};

fooをコンパイルすると、以下のような効率的なコードが生成されます。

同じことがunionにもあてはまります。 unionのコンポーネントに対し__packed属性を使用すると、unionがメモリ内で非境界整列の状態になります。

注意: ポインタを介してアクセスされる__packedオブジェクトは、パックされた構造体の場合も未知のアライメントとなります。

ハーフワードをアクセスするための非境界整列LDR

場合によっては、ARMコンパイラが意図的に非境界整列データをアクセスするLDR命令を生成することがあります。 特に、メモリからのハーフワードのロードを行う場合にこのような命令を生成します。 これは、非境界整列のアドレスを使用して、必要なハーフワードをレジスタの上位半分にロードした後、下位半分にシフトすることができるためです。 LDRBを使用して同じ操作を行なうと2回のメモリアクセスとその2バイトを統合する命令が必要ですが、この方法では1回のメモリアクセスで済みます。 ARMアーキテクチャv3以前では、すべてのハーフワードのロードでこの方法が用いられます。 アーキテクチャv4以降では、あまり頻繁にこの方法は用いられません。これは、ハーフワード専用のロード命令があるためです。しかし、非境界整列データをアクセスするLDRは生成される場合があります。たとえば、パックされた構造体の中にある非境界整列のショートにアクセスする場合です。

このような非境界整列データへのアクセスを行うLDRは、「-memaccess +L41」オプションを指定してADS/RVCT/RVDSコンパイラを利用した場合にのみ生成されます。

[SDT 2.5xでは、コンパイラは非境界整列データのロードをデフォルトで生成することに注意してください。 これは、「-zal」オプションで無効に設定できます。}

コードの移植と非境界整列アクセスの検知

他のアーキテクチャ用(x86 CISCなど)の従来のCコードでは、非境界整列データへのアクセスが、ARMでは動作しないポインタを使用して行われることがあります。 これは移植性のないコードです。境界整列データを前提とするRISCアーキテクチャ上で動作させるには、このようなアクセスを特定し修正する必要があります。

非境界整列アクセスを特定するのは困難な場合があります。非境界整列アドレスへのロードやストアを使用すると、正しく動作しないためです。 正しく動作しなくても、Cソースのどの部分が原因かをトレースするのは困難です。

MMUを搭載したARMプロセッサでは、オプションでアライメントチェック機能がサポートされています。プロセッサによって、アクセスが境界整列されているかどうかがアクセスごとにチェックされます。 境界整列されていないアクセスが行なわれると、MMUはデータアボートを生成します。

ARM7TDMIなどのシンプルなコアを使用しているいくつかのARMパートナーは、自分たちのASIC/ASSP用にアライメントチェック機能を実装しています。 アライメントチェック機能は、ハードウェアブロックをARMコアの外部に付加することで実装できます。アクセスサイズと、アドレスバスの最下位ビットを、すべてのデータアクセスで監視します。 ASIC/ASSPは、非境界整列アクセスが行なわれた場合にABORTシグナルを生成するように設定できます。 ARMでは、他のアーキテクチャからコードが移植されるASIC/ASSPデバイスには、このようなロジックを搭載することを推奨しています。

システムが非境界整列アクセスでアボートを生成するように設定されている場合には、データアボート例外ハンドラをインストールする必要があります。 非境界整列アクセスが行なわれると、データアボートハンドラが起動されます。これにより、エラーのあるデータアクセス命令を特定できます。データアクセス命令は(r14-8)の位置にあります。

特定できた後は、Cソースを変更することで、データアクセスを修正する必要があります。 この変更は、以下の手法を用いて条件付きで行うことができます。

ifdef __arm
  #define PACKED __packed
#else
  #define PACKED
#endif
:
  PACKED int *pi;
:

非境界整列データへのアクセスは最小限に抑える必要があります。コードサイズと性能のオーバーヘッドがあるためです。


<< FAQへ戻る

page top

UKサイト お問い合わせ サイトマップ このサイトについて
Copyright