コンピューティングにおいて、バスエラーとはハードウェアによって発生する障害であり、プロセスがCPUが物理的にアドレス指定できないメモリにアクセスしようとしていることをオペレーティングシステム(OS)に通知するものです。アドレスバスのアドレスが無効であることから、この名前が付けられています。現代のほとんどのアーキテクチャでは、バスエラーはセグメンテーション違反よりもはるかにまれです。セグメンテーション違反は主にメモリアクセス違反(論理アドレスやアクセス権限の問題)によって発生します。
POSIX準拠プラットフォームでは、バスエラーが発生すると、通常はエラーの原因となったプロセスにSIGBUSシグナルが送信されます。SIGBUSは、コンピュータが検出する一般的なデバイス障害によっても発生する可能性がありますが、バスエラーはコンピュータのハードウェアが物理的に故障していることを意味することはまれで、通常はソフトウェアのバグが原因です。バスエラーは、その他の特定のページングエラーでも発生する場合があります。詳細は下記を参照してください。
バスエラーの主な原因は少なくとも3つあります。
ソフトウェアは、CPUに特定の物理メモリアドレスの読み書きを指示します。すると、CPUはこの物理アドレスをアドレスバスに設定し、CPUに接続されている他のすべてのハードウェアに対し、このアドレスへの応答があれば結果を返すよう要求します。他のハードウェアが応答しない場合、CPUは例外を発生させ、要求された物理アドレスがコンピュータシステム全体で認識されないことを示します。なお、これは物理メモリアドレスのみを対象としています。未定義の仮想メモリアドレスへのアクセスは、一般的にバスエラーではなくセグメンテーション違反とみなされますが、MMUが別個に実装されている場合は、プロセッサは両者を区別できません。
ほとんどのCPUはバイトアドレス指定方式を採用しており、各メモリアドレスは8ビットのバイトに対応します。ほとんどのCPUは各メモリアドレスから個々のバイトにアクセスできますが、特定の境界に「アラインメント」されていない限り、より大きな単位(16ビット、32ビット、64ビットなど)には一般的にアクセスできません(x86プラットフォームは例外です)。
例えば、マルチバイトアクセスが16ビット境界にアラインされている必要がある場合、アドレス(バイト単位で指定)0、2、4、6などはアラインされているとみなされアクセス可能となりますが、アドレス1、3、5などはアラインされていないとみなされます。同様に、マルチバイトアクセスが32ビット境界にアラインされている必要がある場合、アドレス0、4、8、12などはアラインされているとみなされアクセス可能となりますが、その間のすべてのアドレスはアラインされていないとみなされます。アラインされていないアドレスで1バイトより大きい単位にアクセスしようとすると、バスエラーが発生する可能性があります。
使用するアーキテクチャによっては、これらのハイブリッド方式を採用しているシステムもあります。たとえば、IBM System/360メインフレームをベースとしたハードウェア(IBM System z、富士通 B8000、RCA Spectra、UNIVAC Series 90など)では、命令は 16 ビット境界でなければなりません。つまり、実行アドレスは偶数バイトから開始する必要があります。奇数アドレスに分岐しようとすると、仕様例外が発生します。[ 1 ]ただし、データはメモリ内の任意のアドレスから取得でき、命令によっては 1 バイト以上になる場合もあります。
CPU は通常、常にデータバスの全幅でデータにアクセスします。バイトをアドレス指定するには、データバスの全幅でメモリにアクセスし、マスクとシフトを使用して個々のバイトをアドレス指定します。この非効率的なアルゴリズムは、ほとんどのソフトウェア、特に文字列処理にとって不可欠な機能であるため、システムはこれを許容します。バイトとは異なり、より大きな単位は 2 つのアラインされたアドレスにまたがるため、データバス上で複数回のフェッチが必要になります。CPU がこれをサポートすることは可能ですが、この機能はマシン コードレベルで直接必要とされることはまれであるため、CPU 設計者は通常、これを実装することを避け、代わりにアラインされていないメモリ アクセスに対してバス エラーを発行します。
FreeBSD、Linux、Solarisでは、仮想メモリページをページインできない場合、例えばメモリマップドファイルにアクセスしたり、プログラムの実行中に切り詰められたバイナリイメージを実行したりしてメモリページが消えてしまった場合[ 2 ] 、またはディスクがいっぱいで作成されたばかりのメモリマップドファイルを物理的に割り当てることができない場合などに、バスエラーを通知することができます。
x86には、セグメンテーションと呼ばれる古いメモリ管理メカニズムが存在します。アプリケーションが存在しないセグメントのセレクタを使用してセグメントレジスタをロードした場合(POSIX準拠OSではアセンブリ言語でのみ実行可能)、例外が発生します。一部のOSではこれをスワッピングに使用していましたが、LinuxではSIGBUSが発生します。
これは、 C言語とAT&Tアセンブリ構文で記述された、アラインメントされていないメモリアクセスの例です。
#include <stdlib.h>int main ( int argc , char ** argv ) { int * iptr ; char * cptr ; #if defined(__GNUC__) # if defined(__i386__) /* x86 でのアライメント チェックを有効にする */ __asm__ ( "pushf \n orl $0x40000,(%esp) \n popf" ); # elif defined(__x86_64__) /* x86_64 でのアライメント チェックを有効にする */ __asm__ ( "pushf \n orl $0x40000,(%rsp) \n popf" ); # endif #endif/* malloc() は常にすべての基本型にアラインされたメモリを提供します */ cptr = malloc ( sizeof ( int ) + 1 ); /* ポインタを 1 増やして、アラインメントがずれるようにします */ iptr = ( int * ) ++ cptr ;/* int ポインタとして逆参照すると、アラインメントされていないアクセスが発生します */ * iptr = 42 ;/* 以下のアクセスもsigbusエラーの原因となります。 short *sptr; int i; sptr = (short *)&i; // すべての奇数値の増分に対して、sigbus になります。 sptr = (short *)(((char *)sptr) + 1); *sptr = 100; */return 0 ; }x86上のPOSIX準拠OSでこのサンプルをコンパイルして実行すると、以下のエラーが発生します。
$ gcc -ansi sigbus.c -o sigbus $ ./sigbusバスエラー$ gdb ./sigbus (gdb) rプログラムがシグナル SIGBUS、バスエラーを受信しました。0x080483ba in main () (gdb) x/i $pc 0x80483ba <main+54>: mov DWORD PTR [eax],0x2a (gdb) p/x $eax $ 1 = 0x804a009 (gdb) p/t $eax & (sizeof(int) - 1) $ 2 = 1GDBデバッガは、X86アセンブリ言語を使用して、即値0x2aがEAXレジスタに格納されている場所に格納されていることを示しています。これは、レジスタ間接アドレッシングの一例です。
アドレスの下位ビットを表示すると、それがワード境界(x86用語では「dword」)にアラインされていないことがわかります。