tatsumack blog

エンジニアによる雑記です

「はじめて読む486」を読んだ

はじめて読む486―32ビットコンピュータをやさしく語る

はじめて読む486―32ビットコンピュータをやさしく語る

世間では機械学習・ディープランニングが盛り上がっている中、低レイヤの勉強がマイブームになっている。
この本はIntel486のアーキテクチャについて解説した本で、1994年に書かれた20年以上も前の本ではあるが、名著だと評判なので読んでみた。
(会社の図書スペースにたまたま置いてあったというのもある)

年齢のせいか、1回読んだ本の内容を忘れることが多くなってきたと感じるので、1回読んだ後に再度読み直して、要点をまとめてみた

1. プロローグ

486の3つの動作モード

486は当時広く普及していた8086(x86アーキテクチャの最初のCPU)との互換性を保ちながら、新しい機能と採用するために3つの動作モードがあった。

  • リアルモード
    • 8086との互換性を保つためのモード
    • MS-DOS(マイクロソフトが開発した8086系CPU向けのOS)用アプリケーションを動かす
  • プロテクトモード
    • メモリ管理やタスク管理などの拡張機能を利用できるモード
  • 仮想8086モード
    • プロテクトモードの恩恵を受けながら8086向けのアプリケーションを動かすモード

32bitサポート

486では32bitサポートされ、16bit CPUの8086よりも大きなデータサイズを扱えるようになった。

  • 16bitCPUの64KBの壁
    • 16bitの限界 = 2^16 bit = 64KB
    • 連続して扱える領域は64KBまでに制限される→配列の最大サイズに影響を与える
  • 16bitCPUの640KBの壁
    • 8086ではアドレスを20bitで表すため、利用できるメモリサイズの限界は1MB
    • 360KBをシステムで利用するので、アプリケーションが使えるのは1MB - 360Kb = 640KB
  • 32bit CBUではより大きなデータサイズ、メモリを扱うことができるようになる(物理的な制限は4GB)

2. 486の履歴書

  • 8086から486までのCPUの発展
    • プロテクトモードは286から導入された
    • 32bitサポートは386から
  • 486DX
  • キャッシュメモリ
    • 486の性能向上に最も寄与している
    • CPUの近くに高速なメモリを置いて、一部のデータをキャッシュすることで高速なメモリアクセスを実現した
    • 高速な分、高価なのでデータサイズは小さい(486では8KB)
    • 書き出しのときにキャッシュだけでなく、本来のメモリにも書き込むライトスルー方式を採用している
    • これに対して、キャッシュに書き込むのとは別のタイミングで本来のメモリに書き込むライトバック方式という方法がある
    • ライトバック方式は書き込みの速度を上げることができるが、データの一貫性を保つのが難しい
  • 486SX
    • 486DXの廉価版
    • 実態は486DXと全く一緒で浮動小数点演算機能を無効にしたもの
    • 無効にした分、検査が不要になり、製造コストを下げることができた
  • 486DX2 / ODP
    • 「クロックダブラー」を用いて高速化を実現
    • CPU内部においてクロック速度を2倍にする
    • 低速な外部システムに引きずられることがなくなる

3.オペレーティング・システム

  • プロセス管理
    • Windows 3.1はイベント駆動
      • アプリケーション側でイベント待ちのときに他のタスクに切り替える
      • お行儀の悪いアプリケーションが暴走すると、システム全体が引きずられて停止状態になることがある
    • Windows NTやOS/2プリエンプティブマルチタスク
      • 一定時間ごとのハードウェア割り込みを利用して、割り込み時にタスクの実行権を取り上げ、他のタスクに切り替える
      • 他のタスクに影響されなくなる
  • メモリ管理

4.プロテクトモード

  • CR0(コントロールレジスタ0)のPEビット(Protection Enable)を1にするとプロテクトモードに移行、0にするとリアルモードに移行
  • 移行する際はJMP命令でパイプラインをクリアする必要がある
    • パイプラインとは、命令実行の工程を読み出し・解釈・実行といった複数の段階に分け、ある命令を「実行」している間に次の命令の「解釈」次の次の命令の「実行」を並行して行う処理
    • リアルモードからプロテクトモードに移るとき、リアルモードの命令がパイプラインに残ってしまっているので、パイプラインを無効化する必要がある

5.セグメント

  • セグメントとは、メモリを一定のサイズで区画割りする方式
    • セグメントによって、タスク間のメモリ分離・保護ができる
    • 8086では、メモリ分離・保護機能はなかったものの、16bitレジスタで64KB以上のアドレスにアクセスするためにセグメント機能を採用していた
  • セグメントアドレスとオフセットアドレス
    • CPUが実行するマシン語プログラムでは物理アドレスでメモリを指定することはできず、セグメントアドレスとオフセットアドレスを用いて指定する
    • セグメントアドレスはセグメントに付けた番号、オフセットアドレスはセグメントの先頭から数えた番号
    • セグメントアドレスはセグメントレジスタに格納し、個々の命令ではオフセットアドレスを指定する
  • リアルモードのセグメント
    • セグメントベースは、セグメントアドレスを物理アドレスの上位16bitに当てはめ、下位4bitを0としたものになる
    • アドレスは20bitで表せる範囲=1MBが上限になる
  • プロテクトモードのセグメント
  • 32ビットセグメント
    • リアルモードではオペランドサイズ・アドレスサイズのデフォルト値は16bit
    • プロテクトモードは16bit・32bitのどちらも設定可能
      • CSレジスタが16bitセグメントを指すときはサイズのデフォルトが16bit、32bitセグメントを指すときは32bitになる
      • コードセグメントが16bitか32bitかは、セグメントディスクリプタ中のDビットで決まる

6.保護

  • セグメントリミット
    • すべての命令に先立ち、メモリのオフセットアドレスがリミット値を超えないかチェックする
    • リミット値はセグメントディスクリプタにセットされている
  • セグメント属性
    • セグメント属性によって、使用できるレジスタが制限されている
      • コードセグメントはCSレジスタ、データセグメントはCS以外のセグメントレジスタ(DSなど)
      • リアルモードでは制限されていなかった
  • 特権レベル
    • 実行できる命令の種類とアクセスできるメモリの範囲を規定する
      • 特権レベル0が一番強く、3が一番弱い
      • 0はOS、1~2はデバイスドライバ、3はアプリケーション
    • セグメントごとに特権レベルが定義されており、それをDPLと呼ぶ
    • プログラム実行中の動作レベルをCPLと呼び、実行中のコードセグメントのDPLによって決まる
      • 異なる特権レベルのセグメントに直接ジャンプすることはできない
      • セグメントレジスタセレクタ値をロードする瞬間に、セグメントアクセスのチェックが行われる
      • セグメントの特権レベルが動作レベルよりも高い場合はフォールトを発生させる
    • 上位レベルのコードセグメントを呼ぶときは、コールゲートを介して呼ぶ
      • コールゲートはディスクリプタセグメント中に作成され、セグメントと同様の呼び出し方をする

7.割り込み

  • 割り込み要因
    • ハードウェア割り込み、トラップ、フォールト、アボート
    • 割り込み要因に対応した「割り込み番号」があり、それに対応する割り込み処理ルーチンがある
  • リアルモードでの割り込み処理
    • 割り込み番号と処理ルーチンの対応を「割り込みベクタテーブル」によって設定する
    • 割り込みベクタテーブルは物理アドレス00000Hから固定で1KB確保されている
  • プロテクトモードでの割り込み処理

8.タスク

  • TSS
    • タスク状態の保存・復元に使用するデータ構造
      • タスク1つにつき必ず1つ持つ
      • タスク切り替え時のCPUレジスタの値、OSのファイル・デバイスの管理状況などを持つ
      • TSSを指し示すTSSディスクリプタがある
  • TR
    • 現在のTSSのセレクタ値を保持するレジスタ
    • タスク切り替えでは、jmp命令を用いてTRの指す先を変え、切り替え先のTSSからレジスタ内容を復元し、タスク切り替えを行う

9.ページング

  • ページング方式
    • メモリを固定長に分割して扱う方式
      • セグメント方式はメモリを任意の大きさで区分けする
    • 論理空間におけるページ(論理ページ)を物理メモリのページ(物理ページ)に対応付けてアドレス変換を行う
      • 論理ページと物理ページの対応はタスクごとに独立している
  • 仮想記憶
    • 物理メモリに紐付けられていない論理ページアクセスした際にページフォルトを発生させ、使用されてない論理ページに対応する物理ページをディスクに退避し(ページアウト)、空いた物理ページにディスクから読み込む(ページイン)
    • 実際の物理メモリよりも大きなメモリ空間を扱うことができる
  • 486ではセグメント方式とページング方式を組み合わせてメモリ管理をしている
    • 物理メモリを論理ページに対応付けする(リニアアドレス空間)
    • リニアアドレス空間をセグメント方式によって管理する。つまりページ単位で管理する。
      • セグメント方式のメモリ保護やタスク間のメモリ分離と、ページングの仮想記憶のいいとこ取り
  • ページング機構
    • CR0のPGビットを立てるとページング機構が有効になる
    • リニアアドレスから物理アドレスへの変換では、ページディレクトリテーブル→ページテーブル→ページ→物理メモリと辿っていく
      • 各ページテーブルも仮想記憶の対象とし、最近アクセスのあったページテーブルのみをメモリに置くことで、メモリ効率を高めている
  • PTE
    • ページングに使用されるメモリ上のデータ構造
    • ページ番号
      • PTEの上位20bitに記述され、下位12bitを0で埋めるとページの先頭アドレスになる
    • Pビット
      • PTEに対応するページがメモリに存在するか
    • Aビット
      • 読み書きすると1がセットされる。OSがページアウトの対象を選定するときに用いる。
    • Dビット
      • 書き込み時に1をセットする。ページアウトの際にディスクに書き込むかを判定するのに用いる
  • TLB

10.セキュリティ

  • 要求者特権レベル
    • 本来そのセグメントにアクセスしようとしたプログラムの特権レベル
      • 特権レベルの乗っ取りを防ぐ
  • コンフォーミングセグメント
    • 特権レベルを移行することなく、特権0のプログラムをアプリケーションから呼び出せるしくみ
    • コードセグメントの一種
      • 特権レベル移行時のオーバーヘッドを気にする必要がなくなる
    • コンフォーミングセグメント呼び出し時は動作レベルが変化しない = CPUの動作レベルとセグメントの特権レベルが異なる
  • LDT(ローカルディスクリプタテーブル)
  • I/O許可マップ
    • タスクごとにIOポートのアクセス制限をする
    • TSS内にI/O許可マップを保持する

このあと「11.仮想8086モード」、「12.DOMエクステンダーとDPMI」、「13.486応用プログラミング」と続くが、疲れてきたので割愛。

CPUの挙動の理解が深まる良い本だった。

この本を読み終わった後に、以下の記事などでIntel CPUの歴史を追ってみたりもした。
@IT:PCエンサイクロペディア:第7回 PCのエンジン「プロセッサ」の歴史(1)〜i8088からIntel386までの道のり 1. IBM PCシリーズに採用された86系16bitプロセッサたち
@IT:頭脳放談:第27回 RISCの敗因、CISCの勝因