バスエラー

バスエラー (: bus error) とは、コンピュータなどのバスへのアクセスに、何らかの問題があるため、読み書きのリクエストに応じられない、といったようなエラーである。

要因はおおざっぱに、論理的なもの(ソフトウェア的なもの)と物理的なもの(ハードウェア的なもの)に分けられる。論理的なものとしては、セグメンテーション違反やアラインメント違反(後述の「不整列アクセス」)など、物理的なものとしては、コンピュータの検出した一般的なデバイスが故障することが原因でも起きることがあるほか、まれにコンピュータハードウェアが物理的に壊れたことを示すことがある。

いずれにしても、詳細や細かい分類の違いはハードウェアおよびオペレーティングシステムのアーキテクチャや実装によって異なる(場合によっては、プロセッサの提供元とオペレーティングシステムの提供元で違う用語を使っているようなこともあるだろう)。いくつかの例を詳解する。

存在しないアドレスへのアクセス
CPUはソフトウェアによってある特定の物理メモリアドレスに対して読み書きを行うよう命令される。したがって、CPUはこの物理アドレスをアドレスバスに設定し、CPUに接続されている他のすべてのハードウェアに対して結果を返すように要求する。もし、この指定のアドレスに対してなんらかのハードウェアが返答するならばCPUは結果を受け取る。もし反応するハードウェアが何もなければCPUは例外を発生させ、要求された物理アドレスはコンピュータシステム全体として認識できないという合図を出す。これは、単に「物理」メモリアドレスのみで機能されることに注意する。ソフトウェアが未定義の仮想メモリアドレスに対してアクセスを試みると、CPUは通常バスエラーよりむしろセグメンテーション違反を発生させるだろう。
不整列アクセス (misaligned memory access / unaligned memory access)
たいていのCPUはバイト単位でアドレッシングを行い、それぞれ固有のメモリアドレス1バイトは8bitからなる。たいていのCPUは個々のメモリアドレスから単独のバイトデータにアクセスすることができるが、より大きな単位(16bitや32bit、64bitなど)を特別な境界、例えば、16bit(番地が2の倍数すなわち0, 2, 4, ...の場合は整列されているが、そうでない1, 3, 5, ...は整列されていない)や32bit(番地が4の倍数すなわち0, 4, 8, 12, ...の場合は整列されているが、その他は整列されていない)に整列されることなしにアクセスすることは通常できない。

CPUは普通どんな時もデータバス幅いっぱいにデータをアクセスする。バイトデータにアクセスするために、CPUはデータバス幅いっぱいにメモリアクセスをして、ここのバイトをマスクしたりシフトしたりする。これは非効率的ではあるが、特に順番に処理を行うたいていのソフトウェアにとって本質的な特性だとして大目に見られている。バイトではなく、2つのアライメントにまたがる程より大きな単位のデータの場合は、データバスで2回以上データを取ってくる必要がある。CPUがサポートしていれば可能だが、この機能は機械語レベルで直接必要になることはめったにないので、CPU設計者は普通このような実装を避け、その代わりに不整列メモリアクセスとしてバスエラーを発行する。CPUのメモリアクセス機構とコンパイラの進歩により、以前よりも問題として目立たなくなってきている。多くの分野で64bit CPUへの移行が行われ、ワード幅が増えた関係で、バスエラーが多発しやすくなっている[要出典]

バスエラーが発生すると、POSIX環境ではSIGBUSシグナルが送信される。

[編集]

これはC言語で書かれた不整列メモリアクセスの例である。

Note: すべてのアーキテクチャでテストしていないので、この例にはまずい所があるかもしれない。

#include <stdlib.h>
int main(void) {
  /* iptr is a pointer to an integer, usually 32 or 64 bits in size. It is currently undefined. */
  /* iptrはintへのポインタ。普通32bitまたは64bitのサイズがある。この時点では内容は未定義 */
  int x, *iptr;

  /* cptr is a pointer to a character (the "smallest addressable unit" of the CPU, normally a byte) */
  /* cptrはcharへのポインタ。CPUがアドレッシングできる最小単位(バイト)で、普通はオクテットすなわち8bit */
  char *cptr;

  /* malloc() gives us a valid, aligned memory address. put this in cptr */
  /* malloc()が成功した場合、返ってきたメモリアドレスはアライメントされている。このアドレスをcptrに入れる */
  cptr = (char *) malloc(33);
  if (!cptr) return 1;

  /* cptrを1増やす。これで不整列なアドレスになった */
  cptr++;

  /* 不整列アドレスで個々のバイトにアクセスするのは問題ない */
  x = *cptr;

  /* iptrに不整列アドレスを代入 */
  iptr = (int *) &cptr[1];
  /* ここでバスエラーが発生するはず。不整列アドレスで1バイト以上のデータにアクセスしている */
  x = *iptr;

  free(cptr);

  return 0;
}

x86アーキテクチャでは、既定で不整列メモリアクセスを自動的に補正する[1]。ただしこの自動補正には性能の損失(パフォーマンス・ペナルティ)が伴う。EFLAGSレジスタのAlignment Check (AC) フラグとCR0レジスタのAMビットがセットされるとアライメントチェックが有効になるが、アプリケーションソフトウェアでこのシステムフラグを変更してはならない[2]

ダブル・フォールト

[編集]

オペレーティングシステムのカーネルがバス・エラーに対して処理を行っているあいだに、さらなるバス・エラーが発生した場合をダブル・フォールトという。この語はDECのミニコンピュータで定義され、ほとんどのアーキテクチャにおいてこの語が使われる。バスエラー処理はリエントラントにすることができない。なぜならCPUのコンテキストがメモリに書き出され、再開するための準備が行われている状態にあるからである。

バス・エラー処理中に新たなバス・エラーが発生すると、新たなCPUコンテキストがメモリに書き出されることになるが、このコンテキストを再びCPUに書き戻すと、その直前で発生したバス・エラー状態を再現してしまい、無限ループに陥る。多くのオペレーティングシステムは、この状態に至った場合を「異常事態」とみなし、WindowsではブルースクリーンUNIXではカーネルパニックを発生させ、オペレーティングシステムは停止し、再起動以外に再開する方法はなくなる。

この現象はデバイスドライバのバグなどで顕著に発生しており、Windowsではクラッシュの大半が、UNIXではほとんどの場合がダブル・フォールトによる、カーネル空間上のコードで発生したバス・エラーである。

脚注

[編集]

関連項目

[編集]