ARM Linuxでの非整列メモリアクセス(Alignment trap警告の理由)


ARMv 5命令セットのCPU(一般的にarm 9アーキテクチャ)はデフォルトでは非整列メモリアクセスをサポートしていないが、ARMv 6以上のCPUはデフォルトでは処理の大部分の非整列メモリアドレスアクセスをサポートしている.整列とは、開始アドレスがwordの長さの整数倍であり、通常は4バイト整列を指す./proc/cpu/alignmentファイルの内容を設定することで、カーネル内の非整列アドレスへのアクセスの処理を変更できます.
root@(none):~# cat /proc/cpu/alignment
User:           3905290
System:         0
Skipped:        0
Half:           0
Word:           0
DWord:          2672136
Multi:          1233154
User faults:    2 (fixup)

このファイルの最後の行「User faults」は、カーネル内で非整列メモリアドレスアクセスをどのように処理するかです.この値はビットマップです.
#define UM_WARN  (1 << 0)
#define UM_FIXUP (1 << 1)
#define UM_SIGNAL (1 << 2)

UM_WARN:「Alignment trap」の警告のみを与えます.UM_FIXUP:位置合わせされていないメモリアドレスを正しく処理してみます.UM_SIGNAL:非整列アドレスアクセスが発生した場合、SIGBUS信号を送信して該当プロセスに通知する.いくつかの処理方法は、例えば(UM_WARN|UM_FIXUP)に設定することができ、fixupと同時に警告情報を与える.0に設定するとignoreになります.
ARMv 5のCPUの場合、User faultsの値はデフォルトで0です.つまり、位置合わせされていないアドレスアクセスは無視されます.この場合、プロセスが位置合わせされていないアドレスにアクセスすると、プログラムの実行に異常が発生します.プログラムに非整列のアドレスアクセスが必要な場合はfixupに設定します.
echo 2 > /proc/cpu/alignment

これにより、カーネルは、非整列メモリアドレスにアクセスして正しい結果を得るために追加の作業を行います.
ARMv 6以上のCPUについては、CPU自体が非整列アドレスへのアクセス処理のサポートを要求するため、/proc/cpu/alignmentに設定された値にはほとんど関心を持たないが、LDM、STM、LDRD、STRDのような複合命令による非整列アドレスアクセスについては、ソフトアシスト処理が必要であり、これらのCPUでは、/proc/cpu/alignmentがデフォルトで2(fixup)に設定されている.
具体的な処理方法はカーネルコードarch/arm/mm/alignmentを参照する.c:alignment_init(), hook_fault_code(), do_alignment().
 
非整列メモリアクセスがサポートされていないチップでは、次の例のような強制タイプ変換を行うことに注意してください.
#include 
#include 
#include 

void sigbus_handler(int sno)
{
 printf("signal %d captured
", sno); exit(1); } int main(int argc, char *argv[]) { char intarray[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; signal(SIGBUS, sigbus_handler); printf("int1 = 0x%08x, int2 = 0x%08x, int3 = 0x%08x, int4 = 0x%08x
", *((int *)(intarray + 1)), *((int *)(intarray + 2)), *((int *)(intarray + 3)), *((int *)(intarray + 4))); return 0; }

非整列アクセスをサポートするシステムでは、次の結果が得られます(スモールエンドシステム).
root@NVR:~# ./testalign
int1 = 0x55443322, int2 = 0x66554433, int3 = 0x77665544, int4 = 0x88776655

非整列アクセスがサポートされていないシステムでは、/proc/cpu/alignmentの設定によって値の表現が異なります.
~ # echo 0 > /proc/cpu/alignment
~ # ./testalign
int1 = 0x11443322, int2 = 0x22114433, int3 = 0x33221144, int4 = 0x88776655
~ #
~ # echo 2 > /proc/cpu/alignment
~ # ./testalign
int1 = 0x55443322, int2 = 0x66554433, int3 = 0x77665544, int4 = 0x88776655
~ #
~ # echo 1 > /proc/cpu/alignment
~ # ./testalign
Alignment trap: testalign (979) PC=0x0000860c Instr=0xe5931000 Address=0xbef6fc99 FSR 0x001
Alignment trap: testalign (979) PC=0x0000860c Instr=0xe5931000 Address=0xbef6fc99 FSR 0x001
int1 = 0x11443322, int2 = 0x22114433, int3 = 0x33221144, int4 = 0x88776655
~ #
~ # echo 4 > /proc/cpu/alignment
~ # ./testalign
signal 7 captured
~ #

もちろん3に設定して、非整列アドレスアクセスを正しく処理しながら警告を与えることもできます.
 
charタイプを使用して、非整列メモリの読み書きを行い、非整列アドレスの処理を回避したり、memcpyのような関数を使用して、付与や強制型変換による問題を回避したりすることができます.次の例を見てください.
#include 
#include 
#include 
#include  /* memcpy */

struct pack_info {
 unsigned char sno;
 unsigned int len;
} __attribute__((packed));

void sigbus_handler(int sno)
{
 printf("signal %d captured
", sno); exit(1); } int main(int argc, char *argv[]) { signal(SIGBUS, sigbus_handler); struct pack_info * mem_cos = (struct pack_info *)malloc(sizeof(struct pack_info)); if (!mem_cos) return 1; unsigned int * cy_len = &mem_cos->len; mem_cos->sno = 0x12; mem_cos->len = 0x55667788; /* 1. */ mem_cos->len = 0xaabbccdd; /* 2. cy_len */ *cy_len = 0xaabbccdd; /* 3. memcpy */ unsigned int cy_len_i = 0xaabbccdd; memcpy(cy_len, &cy_len_i, 4); printf("sno = %#x, len = %#x
", mem_cos->sno, mem_cos->len); free(mem_cos); return 0; }

struct pack_infoに__を付けましたattribute__()プロパティなので、lenメンバーのアドレスは4バイトで整列されていません.上記のコードはlenメンバーに3つの方法で値を割り当て、非整列アクセスシステムをサポートしない場合、2つ目の方法では問題があり、予想される結果が得られません.