i386アーキテクチャの基礎知識
i386アーキテクチャ
コンピュータの心臓部である命令解釈装置と演算装置を内蔵した部品を CPU(中央処理装置; Central Processing Unit)と言う。 一般に,機械語命令はCPUごとに異なる。 「どういうビットパターンを読み出したとき,どういう動作をするか」が論理回路として作り込まれているからだ。
第O部では架空のCPU『ミクロン』の機械語プログラムを扱った。
この先,第I部では Intel 80386 CPU(略して i386),第II〜III部では ARM CPU を扱う。
これら(ミクロン,i386,ARM)の機械語命令は大きく異なり,互換性はない。
例えば「01 01 55
はレジスタAに 0x55 を格納する命令」といった話はミクロンにのみ通用し,他のCPUには当てはまらない。(ちなみに i386 での同等の命令(レジスタEAXに 0x55 を格納する命令)は b8 55 00 00 00
。)
Intel社のCPUを搭載したPC用の機械語プログラムはARMを搭載したスマートフォンでは動かないし,逆も同じだ。
つまり,あるCPUについて機械語プログラムを学んでもちがうCPUのプログラミングには直接は役立たない。しかし,実際に複数のCPUの機械語を学んでみれば,「表面的なちがいを除けば各CPUの基本構造は非常によく似ている」ということがわかるだろう。 以下のような事柄はどのCPUも同じである。
- レジスタという一時保管場所が数個ある
- レジスタ中の2進数の加減算(CPUによっては乗除算も)ができる
- フラグと条件付きジャンプ命令を使って繰り返しや条件分岐ができる
これ以降は『ミクロン』を離れ,もっと現実的な,市販のコンピュータに搭載されているCPU(つまり i386 と ARM)の機械語について学ぶ。 さまざまなCPUについて学ぶことで,CPUの基本構造(アーキテクチャ)についての理解を深めることができるだろう。
(補足:「i386」も「ARM」も一つのCPUを指すのではなく,同じ機械語命令セットを持つCPU群を指している。各群に属するCPUを「i386アーキテクチャのCPU」「ARMアーキテクチャのCPU」とも言う。)
参考:i386, ARM以外の「身近な」CPU:
- 「情報処理技術者試験」で用いられていた COMET II(アセンブリ言語の名前は CASL II)。 『試験で使用する情報技術に関する用語・プログラム言語など』ver4.3 の「別紙2 アセンブラ言語の仕様」を参照。 たった8ページの仕様書で定義されており,CPUが提供すべき機能(= 命令)を非常に簡潔に述べた例になっている。
- 科目「論理回路」の教科書『論理回路』(今井 編著, オーム社, 2016)の付録「CPUの設計」(pp.209―222) に記述されているCPU。 極限まで命令を減らした上でその回路構成を述べている。 下記の「CPUの内部」の回路構成と見比べれば,基本的な回路構成は共通であることや,下記「CPUの内部」では省略されている部分がどう作られているか等がわかるだろう。
コンピュータの内部
コンピュータ内部は,単純に言えば,CPUとその他の装置がバス (bus) という信号線で互いに接続された構造をしている。
- 参考:コンピュータシステムの構成 (Tanenbaum 著『Modern Operating Systems』2nd Ed., Prentice-Hall, 2001, p.21, Fig.1-5を参考に作成)
コンピュータによって行われていることは,基本的にすべて「CPUと他の装置の間の情報の送受信」だ。プログラムは主記憶装置の中に格納され,1命令ずつCPUに読み出される。その命令に従って,CPUの中で計算を行ったり,他の装置に情報を送信したり逆に受信したりする。例えば画像コントローラに情報を送れば,それが画像となって表示される。
CPUの内部
CPUは基本的に,以下の3要素を内部に持つ。
- CPU内のデータ保存場所である レジスタ,
- 加算や減算等を行う 算術論理演算装置 (Arithmetic logic unit; ALU),
- 機械語命令に応じて各装置に動作信号を送る 制御装置。
下図は,i386 の前身である 8086 CPUの内部構造を示している(i386やそれより新しいCPUはもっと複雑なので,わかりやすさのため,構造が単純な古いCPUの図を用いた。ただし,8086の基本データ幅は16ビット,i386は32ビットなので注意)。
- 参考:Intel 8086 の内部ブロック図 (相沢 著『8086ファミリ・ハンドブック』CQ出版社, 1989, p.16, 図2-1を基に作成)
- 参考:WikiPedia:8086
- 参考:WikiPedia:74181 / 74LS181 Datasheet — (70年代の)ALU単体製品
左側に汎用レジスタ (general registers) とALUがあり,右側に特殊レジスタ(CS, DS等)がある。 それ以外のBus Control LogicやEU Control System, Instruction Queueなどが制御装置に当たる。
CPUの外にある主記憶装置から機械語命令が読み出され,右下のInstruction Queue(8086では6バイト)に格納される。機械語命令のビットパターンに応じて,EU Control Systemが実行ユニット (EU) 内の各装置を動作させる。
「機械語命令を読み出す位置」は命令ポインタ (Instruction Pointer; IP) という特殊レジスタに格納され,1命令実行するたびに更新される。「機械語命令を読み出す位置を変更」とは IP の値を変更することに当たる。(CPU製造者によっては IP ではなく PC (= Program Counter) と呼ぶこともある。)
i386のレジスタ
レジスタは,被演算数や演算結果などの一時的な保存場所として使われる。 C言語やJavaでの変数のような「何をするにも必ず使う」存在だ。 従って,機械語・アセンブリ言語を扱う際には何よりもまず,「何ビットのレジスタが何個使えるか,アセンブリ言語ではどういう名前で表すか」を知らなければならない。
i386は32ビットの汎用レジスタを8個持っている(下図)。 その他に特殊レジスタとしてEIP, EFLAGSがある。
アセンブリ言語では,レジスタ番号ではなく,EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP という名前でレジスタを表す(大文字でも小文字でもよい)。
汎用レジスタのうち,ESPとEBPは特別な用途(スタックの操作)に使うことが多い。その場合,通常の計算には残りの6レジスタを使う。
16ビットレジスタ,8ビットレジスタ
i386では,EAXの下位16ビットを AX という名前のレジスタとして使うことができる(下図)。さらに,AX の上位8ビット,下位8ビットをそれぞれ AH, AL という名前のレジスタとして使うことができる。EBX, ECX, EDXも同様に,下位16ビットやさらにその上位8ビット・下位8ビットをBX, BH, BL, CX, CH, CL, …という名前のレジスタとして使うことができる。
あまり使う機会は多くないが,主記憶装置や周辺装置に8ビットだけ,または16ビットだけ情報を書き込みたいときには必要となる(この授業でも,ASCII文字コードを1文字分ずつ主記憶装置に書き込むときに必要になる)。
なお,ESI, EDIの下位16ビットもSI, DIという名前のレジスタとして使うことができるが,SI, DIの中を8ビットレジスタに分けることはできない。
参考: 特殊レジスタ
EIPは,ミクロンのIP(命令ポインタ)と同じく,次に実行する命令の番地を指す。1命令実行するごとに自動的に更新される。またジャンプ命令によって変更できる。
EFLAGS は,ZF や CF などのフラグの集合体である。 (本科目においては重要ではないが)以下の図のような内部構成になっている。
ただし上位16ビットは図では省略。
ZF, CFのほかに,演算結果を反映するフラグとして,SF (sign), OF (overflow), AF (adjust), PF (parity) がある。 特に SF, OF は,符号付き整数の大小比較において主要な役割を果たす。多くのCPUは,ZF, CF, SF, OF の4つに相当するフラグを持っている。(ミクロンは符号付き整数を直接扱う機能がなかったが,多くのCPUは符号付き整数を扱う機能を持っている。本科目でも第I部の中で符号付き整数について学ぶ。)
バイト,ワード,ダブルワード
ビット幅を表す用語を覚えておこう。 i386では,8ビット,16ビット,32ビットのビット列をそれぞれ バイト (byte),ワード (word),ダブルワード (doubleword) と呼ぶ。 レジスタEAX, EBX, ... はダブルワードを格納できる。AX, BX, ... はワードを,AH, AL, BH, BL, ... はバイトを格納できる。
ビット列の中の1ビットだけを指定するときは,下位から順に,ビット0,ビット1,ビット2,…と付けた番号で指定する。バイトの最上位ビットはビット7,ワードの最上位ビットはビット15,ダブルワードの最上位ビットはビット31だ(下図)。
主記憶装置(メモリ)
CPUとともにコンピュータに欠かせない装置が主記憶装置(メモリ)だ。 レジスタと同様の2進数列を記憶する素子を大量に並べた装置であり,機械語プログラムとデータを保持するために使われる。 読み出しも書き込みも可能なメモリであるRAM (Random Access Memory) が主に用いられるが,「電源投入時に実行する固定のプログラム」(補助記憶装置からOSを読み出すプログラム等)はROM (Read Only Memory) に入れて搭載される。
多くのコンピュータでは,メモリの読み出し・書き込みの最小単位はバイトである。 1バイト分の記憶素子それぞれに番地 (address) と呼ばれる番号がつけられており,番地によって読み出し元や書き込み先を指定する。 i386 CPUにおいて,番地は32ビットである。
- 参考:IDT6116 Datasheet — 2KiB SRAM 製品
- バイト単位で読み出し・書き込みが可能。2048バイト分の各記憶素子を識別するため,番地を11ビットで指定するように作られている(A0〜A10信号線)。
ワードやダブルワードをメモリに格納する場合は,1バイトずつに分けて,連続する番地の記憶素子に格納する(下記の「リトルエンディアン」も参照)。 例えば「x 番地に格納されたダブルワード」と言った場合,そのダブルワードは x 番地から x + 3 番地までの4バイトに分けて格納されている。 「x 番地からダブルワードを読み出す」命令を実行すると,その4バイトが一度に読み出される。 同様に,「x 番地にダブルワードを書き込む」命令を実行すると,x 番地から x + 3 番地の4バイト分が一度に書き換えられる。 特に後者には注意が必要(1バイトだけ書き込むべきなのにダブルワードを書き込む命令を実行すると,書き換えるべきでない番地も書き換えてしまう)。
リトルエンディアン
i386は,ワードやダブルワードなど,1バイトより大きいビット列を主記憶装置に置く際に,リトルエンディアン (little endian) 方式を採る。
リトルエンディアンとは,1バイトより大きいデータを1バイト単位の記憶媒体(主記憶装置やHDDなど)に記録する際に,下位桁を先,上位桁を後に並べる方式を言う。
例えばi386の「EAXレジスタに 12 を書き込む機械語命令」は b8 0c 00 00 00
という5バイトの列だが,0c 00 00 00
の部分が12を表すダブルワード (0x0000000c) だ。下位のバイトが前,上位のバイトが後ろに並んでいる。
(ビット番号0〜7が第1バイト,8〜15が第2バイト,16〜23が第3バイト,…,のように「ビット番号の小さい順」に並べる方式と言える。)
リトルエンディアンの逆であるビッグエンディアン方式を採るCPUも存在する。設定によって切り替えられるCPUもある。
i386(32ビット)vs. x86-64(64ビット)
(この授業ではあまり重要ではないが,関連知識として説明する。)
i386 (Intel 80386) は「32ビットのアーキテクチャ」と呼ばれる。 レジスタや演算装置が32ビットのビット列を扱うのにちょうどよいように作られている,という意味だ。
i386は,16ビットCPUの8086, 80286を前身とし,その後もi486, Pentium, ...と後継が作られてきた。 現在のパソコンに使われているCPUのほとんどは,正確にはi386アーキテクチャのCPUではなく,64ビットアーキテクチャ(x86-64アーキテクチャ)のCPUだ。 この授業では,x86-64 CPUの「32ビット互換モード」で動作する機械語プログラムを扱っている。
アセンブラ Nasm (version 2以降) は64ビットモードの機械語を出力する機能を持っているので,nasm
のオプション -felf
を -felf64
に,ld
のオプション -m elf_i386
を -m elf_x86_64
に変更すれば,64ビットモードの実行可能ファイルを生成できる。
ただし,成績評価時の自動テストは32ビットモードで行うので,64ビットモード専用プログラムを提出すると正しく評価されないので注意。
x86-64アーキテクチャについては例えば下記の資料を参照。
- https://ja.wikipedia.org/wiki/X64 -- Wikipedia
- http://www.nasm.us/doc/nasmdo12.html -- Nasm 2マニュアル第12章: Writing 64-bit code (UNIX, Win64)
- http://refspecs.linuxfoundation.org/ -- オープンソース開発者向け情報提供ページ