バス -- コンピュータ内部の「道路システム」
コンピュータ内部の各コンポーネントは絶えず情報を交換する必要があります。CPU はメモリから命令を取得し、データを読み書きします。これらの情報はどうやって転送されるのでしょうか?答えは——バス(Bus)。
バスは CPU、メモリ、ハードディスク、グラフィックカードなどのデバイスを接続する共通の通信回線の集まりで、都市の道路システムのようなものです——幹線道路もあれば支線もあり、それぞれの道に明確な用途があります。
この講義では、3種類のバスの役割分担を理解し、Python で完全なメモリ読み出し操作をシミュレートします。
道路システムに例える:3つの道、3つの用途
あなたが大都市で宅配便を届けると想像してください。配達員は3つのことを知る必要があります:どこに届けるか、何を届けるか、受取人が受け取りの署名をするかどうか。
コンピュータ内部にもまったく同様の3つの「道路」があります:
| バスの種類 | 機能 | 転送方向 | 生活でのたとえ |
|---|---|---|---|
| アドレスバス(Address Bus) | アクセスするメモリアドレスを転送する | CPU → メモリ(単方向) | 宅配便伝票の「番地」——受取側に「このアドレスを探しています」と伝える |
| データバス(Data Bus) | 実際のデータ内容を転送する | 双方向 | 配達員が持つ「荷物」——物そのもので、送り出すことも受け取ることもできる |
| コントロールバス(Control Bus) | 読み書き、割り込みなどの制御信号を転送する | CPU ↔ 各コンポーネント(双方向) | 「受け取り伝票」——相手に今は集荷か配達か、そして転送が完了したかどうかを伝える |
アドレスバスの幅は CPU がアクセスできるメモリの大きさを決定します。32 ビットのアドレスバスは 2^32 = 4GB のメモリ空間にアクセスでき、64 ビットのアドレスバスは理論上 2^64 バイト(約 16EB)にアクセスできます。これが 32 ビット OS が最大 4GB のメモリしか使えない理由です。
メモリ読み出し操作の完全な流れ
CPU がメモリアドレス 100 からデータを読み出す必要があると仮定します。全体のプロセスは 4 つのステップに分かれ、3つのバスが協調して動作します:
ステップの詳細
- CPU がアドレスを置く:CPU がアドレス値 100 をアドレスバスに置きます。メモリはこのとき番地を受け取ります。
- CPU が信号を送る:CPU がコントロールバスに「読み出し」信号(READ)を出します。メモリは相手が何かを取りに来ることを知ります。
- メモリがデータを置く:メモリはアドレスと読み出し信号を受け取った後、アドレス 100 に格納されたデータを見つけ、それをデータバスに置きます。
- CPU がデータを受け取る:CPU はデータバスからデータを読み取り、内部レジスタに格納します。転送完了です。
タイミング図
以下は ASCII タイミング図で、この命令実行時のバス状態の変化を示します:
时间 | 地址总线 控制总线 数据总线 ------|------------------------------------------ T0 | 100 IDLE XXXX ← CPU 放上地址 T1 | 100 READ XXXX ← CPU 发出读信号 T2 | 100 READ 0x2A ← 内存放上数据 T3 | XXXX IDLE 0x2A ← CPU 取走数据 ------|------------------------------------------ 结果: CPU 从地址 100 读到了数据 0x2A
コード例:Python でバスの相互作用をシミュレートする
次の Python コードは、メモリ読み出し操作におけるアドレスバス、データバス、コントロールバスの協調プロセスを完全にシミュレートします。
例
バス相互作用シミュレーション - 完全なメモリ読み出し操作(サンプル デモ)
このプログラムは、CPU が3種類のバスを通じてメモリからデータを読み取る完全なプロセスをシミュレートします:
1. アドレスバス: CPU がアクセスするメモリアドレスを送信します
2. コントロールバス: CPU が読み出し/書き込みの制御信号を送信します
3. データバス: メモリがデータを返送/受信します
バス状態が変化するたびに詳細なログを出力し、3つのバスの協調動作を観察しやすくします。
"""
import time
class AddressBus:
"""アドレスバス - 単方向:CPU がアドレスをメモリに送信する"""
def __init__(self):
self.value = None # 現在のバス上のアドレス値
self.width = 16 # 16ビットアドレスバス、64KB をアドレス指定可能
def set_address(self, addr):
"""CPU がアドレスをアドレスバスに置く"""
self.value = addr
print(f" [アドレスバス] CPU がアドレスを置く: {addr} (0x{addr:04X})")
return addr
def clear(self):
"""バスをクリア"""
self.value = None
print(f" [アドレスバス] バスがクリアされ、アイドル状態に入ります")
class DataBus:
"""データバス - 双方向:CPU とメモリはどちらもデータを送受信できる"""
def __init__(self):
self.value = None # 現在のバス上のデータ値
self.width = 8 # 8ビットデータバス、1回に1バイト転送
self.direction = None # 'TO_CPU' または 'TO_MEMORY'
def send_from_cpu(self, data):
"""CPU がデータをバスに送信する"""
self.value = data
self.direction = 'TO_MEMORY'
print(f" [データバス] CPU → バス, データ: {data} (0x{data:02X})")
return data
def send_from_memory(self, data):
"""メモリがデータをバスに送信する"""
self.value = data
self.direction = 'TO_CPU'
print(f" [データバス] メモリ → バス, データ: {data} (0x{data:02X})")
return data
def read_to_cpu(self):
"""CPU がデータバスからデータを読み取る"""
data = self.value
print(f" [データバス] CPU がバスからデータを読み取る: {data} (0x{data:02X})")
self.clear()
return data
def read_to_memory(self):
"""メモリがデータバスからデータを読み取る(書き込み操作時に使用)"""
data = self.value
print(f" [データバス] メモリがバスからデータを読み取る: {data} (0x{data:02X})")
self.clear()
return data
def clear(self):
"""バスをクリア"""
self.value = None
self.direction = None
print(f" [データバス] バスがクリアされ、アイドル状態に入ります")
class ControlBus:
"""コントロールバス - 双方向: 読み書き信号、割り込み信号などを転送する"""
# 制御信号の種類
READ = 'MEM_READ' # メモリ読み出し信号
WRITE = 'MEM_WRITE' # メモリ書き込み信号
IDLE = 'IDLE' # アイドル
def __init__(self):
self.signal = self.IDLE
def set_signal(self, signal):
"""制御信号を設定する"""
self.signal = signal
signal_name = {
self.READ: 'メモリ読み出し (MEM_READ)',
self.WRITE: 'メモリ書き込み (MEM_WRITE)',
self.IDLE: 'アイドル (IDLE)'
}
print(f" [コントロールバス] CPU が信号を送信: {signal_name.get(signal, signal)}")
def clear(self):
"""信号をクリア"""
self.signal = self.IDLE
print(f" [コントロールバス] 信号がクリアされ、アイドル状態に入ります")
class SimpleMemory:
"""簡略化されたメモリモジュール - 256 バイトの記憶領域"""
def __init__(self, size=256):
self.size = size
self.data = {}
# デモ用データを初期化
for i in range(size):
self.data[i] = i % 256 # アドレス i に値 i % 256 を格納
# デモとして特定のアドレスに特別な値を置く
self.data[100] = 42 # アドレス 100: 42 (0x2A) を格納
self.data[200] = 0xAB # アドレス 200: 0xAB (171) を格納
self.data[50] = ord('R') # アドレス 50: 文字 'R' の ASCII コードを格納
self.data[51] = ord('U')
self.data[52] = ord('N')
self.data[53] = ord('O')
self.data[54] = ord('O')
self.data[55] = ord('B')
def read(self, addr):
"""指定されたアドレスからデータを読み取る"""
if 0 <= addr < self.size:
return self.data.get(addr, 0)
else:
raise ValueError(f"アドレス {addr} はメモリ範囲 (0-{self.size-1}) を超えています")
def write(self, addr, data):
"""指定されたアドレスにデータを書き込む"""
if 0 <= addr < self.size:
self.data[addr] = data
else:
raise ValueError(f"アドレス {addr} はメモリ範囲 (0-{self.size-1}) を超えています")
class CPU:
"""簡略化された CPU - バスを介してメモリと相互作用する"""
def __init__(self, addr_bus, data_bus, ctrl_bus, memory):
self.addr_bus = addr_bus # アドレスバス
self.data_bus = data_bus # データバス
self.ctrl_bus = ctrl_bus # コントロールバス
self.memory = memory # メモリ
# CPU 内部レジスタ
self.mdr = 0 # データレジスタ(Memory Data Register)
self.mar = 0 # アドレスレジスタ(Memory Address Register)
def memory_read(self, addr):
"""
完全なメモリ読み出し操作を1回実行する
フロー:
T0: MAR ← addr (内部準備)
T1: アドレスバス ← addr, コントロールバス ← READ
T2: メモリがデータを読み出す, データバス ← data
T3: MDR ← データバス, 各バスをクリア
戻り値: 読み取ったデータ値
"""
print(f"\n{'='*55}")
print(f"操作開始: CPU がメモリアドレス {addr} からデータを読み取ります")
print(f"{'='*55}")
# T0: CPU 内部準備——アドレスを MAR にロードする
self.mar = addr
print(f"\n[T0 - CPU 内部准备]")
print(f" [CPU 内部] MAR (メモリアドレスレジスタ) ← {addr}")
# T1: CPU はアドレスをアドレスバスに置き、同時にコントロールバスに読み出し信号を送る
print(f"\n"[T1 - アドレスと読み出し信号を送信]")
self.addr_bus.set_address(addr)
self.ctrl_bus.set_signal(ControlBus.READ)
# 信号伝送遅延をシミュレート
time.sleep(0.001)
# T2: メモリ応答——アドレスバス上のアドレスに基づいてデータを見つけ、データバスに置く
print(f"\n"[T2 - メモリ応答]")
data = self.memory.read(addr)
self.data_bus.send_from_memory(data)
# 信号伝送遅延をシミュレート
time.sleep(0.001)
# T3: CPU はデータバスからデータを読み取り、MDR に格納する。各バスはクリアされる
print(f"\n"[T3 - CPU がデータを受信]")
self.mdr = self.data_bus.read_to_cpu()
self.ctrl_bus.clear()
self.addr_bus.clear()
# 結果を出力
print(f"\n{'='*55}")
print(f操作完了: アドレス {addr} (0x{addr:04X}) からデータ {self.mdr} (0x{self.mdr:02X}) を読み取りました)
print(f"{'='*55}")
return self.mdr
def memory_write(self, addr, data):
"""
完全なメモリ書き込み操作を1回実行する
フロー:
T0: MAR ← addr, MDR ← data
T1: アドレスバス ← addr, データバス ← data, コントロールバス ← WRITE
T2: メモリはデータバスからデータを取得し、アドレス addr に書き込む
T3: 各バスをクリア
"""
print(f"\n{'='*55}")
print(f"操作開始: CPU はメモリアドレス {addr} にデータ {data} を書き込みます")
print(f"{'='*55}")
# T0: CPU 内部の準備
self.mar = addr
self.mdr = data
print(f"\n"[T0 - CPU 内部の準備]")
print(f" [CPU 内部] MAR ← {addr}, MDR ← {data}")
# T1: CPU はアドレスとデータをバスに置き、書き込み信号を送る
print(f"\n"[T1 - アドレス、データ、書き込み信号を送信]")
self.addr_bus.set_address(addr)
self.data_bus.send_from_cpu(data)
self.ctrl_bus.set_signal(ControlBus.WRITE)
time.sleep(0.001)
# T2: メモリはデータバスからデータを取得して書き込む
print(f"\n"[T2 - メモリ書き込み]")
received = self.data_bus.read_to_memory()
self.memory.write(addr, received)
print(f" [メモリ内部] アドレス {addr} (0x{addr:04X}) ← {received} (0x{received:02X})")
time.sleep(0.001)
# T3: 各バスをクリア
print(f"\n"[T3 - バスをクリア]")
self.ctrl_bus.clear()
self.addr_bus.clear()
print(f"\n{'='*55}")
print(f"操作完了: {data} はアドレス {addr} に書き込まれました")
print(f"{'='*55}")
# ===== メインプログラム: デモ実行 =====
if __name__ == "__main__":
print("=" * 60)
print(" バスインタラクションシミュレーション —— サンプル コンピュータ構成原理デモ")
print("=" * 60)
print()
# 1. バスの初期化
print("【システム初期化】アドレスバス、データバス、コントロールバス、メモリモジュールを作成")
addr_bus = AddressBus()
data_bus = DataBus()
ctrl_bus = ControlBus()
memory = SimpleMemory()
print()
# 2. メモリの初期状態を確認
print("【メモリ初期状態】主要アドレス位置のデータプレビュー:")
for addr in [50, 51, 52, 53, 54, 55, 100, 200]:
ch = chr(memory.data[addr]) if 32 <= memory.data[addr] < 127 else '?'
print(f" アドレス {addr} (0x{addr:04X}): {memory.data[addr]:3d} (0x{memory.data[addr]:02X}) '{ch}'")
print()
# 3. CPU の初期化
cpu = CPU(addr_bus, data_bus, ctrl_bus, memory)
# 4. デモ: メモリからデータを読み取る
print("\n" + "▼" * 60)
print("デモ 1: メモリアドレス 100 からデータを読み取る")
print("▼" * 60)
val1 = cpu.memory_read(100)
print("\n" + "▼" * 60)
print("デモ 2: メモリアドレス 200 からデータを読み取る")
print("▼" * 60)
val2 = cpu.memory_read(200)
print("\n" + "▼" * 60)
print("デモ 3: メモリアドレス 50-55 を連続して読み取り、文字 (サンプル) を組み立てる")
print("▼" * 60)
chars = []
for addr in range(50, 56):
val = cpu.memory_read(addr)
chars.append(chr(val))
print(f"\n組み立てた文字列: {''.join(chars)}")
# 5. デモ: メモリにデータを書き込む
print("\n" + "▼" * 60)
print("デモ 4: メモリアドレス 10 にデータ 77 を書き込む")
print("▼" * 60)
cpu.memory_write(10, 77)
# 6. 書き込み結果を検証
print("\n" + "▼" * 60)
print("デモ 5: 書き込みを検証——アドレス 10 からデータを読み戻す")
print("▼" * 60)
val3 = cpu.memory_read(10)
print(f"\n検証: 書き込み 77, 読み出し {val3}, {'成功' if val3 == 77 else '失敗'}")
print("\n" + "=" * 60)
print(" すべてのデモが完了しました!")
print("=" * 60)
バス幅と実際の性能
アドレスバスの幅は「どれだけのメモリを管理できるか」を決める
アドレスバスの各線は1ビットを転送する。N本のアドレス線は2^N個の異なるアドレスを表すことができる。
| アドレスバス幅 | アドレス可能空間 | 代表的な CPU |
|---|---|---|
| 16 ビット | 2^16 = 64 KB | Intel 8086 (1978) |
| 20 ビット | 2^20 = 1 MB | Intel 8088 (IBM PC, 1981) |
| 24 ビット | 2^24 = 16 MB | Intel 80286 (1982) |
| 32 ビット | 2^32 = 4 GB | Intel 80386 (1985) |
| 64 ビット | 2^64 = 約 16 EB | 現代の x86-64 プロセッサ |
注意: 現代の64ビットCPUの実際のアドレス線は通常64本未満(例えば48本)です。2^64バイトは現在の物理メモリの実際の需要をはるかに超えるためです。48本のアドレス線で256 TBをアドレス指定でき、現在の使用には十分です。
データバスの幅は「1回でどれだけのデータを転送できるか」を決める
8ビットデータバスは毎回1バイト転送し、64ビットデータバスは毎回8バイト転送します。これが、同じタスクでも64ビットシステムが32ビットシステムより速いことが多い理由です——1回の転送データ量が2倍になるからです。
コントロールバスの信号の種類
コントロールバスはアドレスバスやデータバスのように整然とはしていません。それは多種多様な信号線の集合であり、以下を含みます:
- MEM_READ / MEM_WRITE: メモリ読み書きイネーブル信号
- IO_READ / IO_WRITE: I/Oポート読み書き信号
- INTERRUPT: 割り込み要求信号——外部デバイスがCPUに通知する方法
- CLOCK: クロック信号——システム全体を同期させて動作させる「鼓動」
- RESET: リセット信号——CPUを初期状態に戻す
まとめと確認
一言でまとめると:アドレスバスは「どこへ行くか」を担当し、データバスは「何を運ぶか」を担当し、コントロールバスは「何をするか」を担当します——3つのバスがそれぞれの役割を果たし、協力して毎回のデータ転送を完了します。
自己テスト
- CPUがメモリからデータを読み取る場合、アドレスバス上の情報は誰が置いたのですか?データバス上の情報はまた誰が置いたのですか?
- あるコンピュータのアドレスバスが16本しかない場合、最大どれだけのメモリを管理できますか?
- コントロールバスとアドレスバス、データバスの最大の違いは何ですか?(ヒント:コントロールバスの「構成」を考えてみてください)
参考回答: 1. アドレスはCPUが置き、データはメモリが置きます。2. 2^16 = 65536 バイト = 64KB。3. アドレスバスとデータバスは整然としたデジタル信号(アドレス値/データ値)を転送しますが、コントロールバスはそれぞれ異なる個別の信号線の集まりであり、各線は独自の独立した機能を持ちます。
その他の拡張