コンピュータの基本構成と動作原理に関わる専門用語を解説します。新たに覚える言葉が多いですが,頑張ってついてきてください。
1.コンピュータの基本構成
コンピュータは基本的には次のような構成をしています。
それぞれの構成要素は次の通りです。
- CPU は,Central Processing Unit (中央処理装置)の略で,プロセッサ (processor) ともいいます。プログラムにしたがってさまざまな数値計算や情報処理,機器制御などを行います。コンピュータの司令塔の役割を担っています。レジスタ (register) は,CPUの中にある小規模な記憶装置です。詳しくは後で説明します。
-
メモリ (memory)は,主記憶装置とも呼ばれます。メモリは アドレス (address: 番地)と データ (data) で表現されます。データは文字や画像などの何かを数値のまとまりとして表現したものです。アドレスはデータを書いたり読んだりする時のメモリ上の位置です。
イメージとしては郵便箱がずらりと並んだ様子を想像してみてください。郵便には宛先の住所を書きますが,アドレスも同様の役割をします。データの読み書きは,アドレスとして与えられた番号の郵便箱に郵便物を入れたり出したりすることに相当します。C言語の ポインタ はアドレスを抽象化した概念です。
データは コード (code) の形で記録されています。コードは数値になんらかの意味を対応づけたものです。そのまま整数として扱うだけではなく,文字など他の種類のデータも数値として記録されます。プログラムもコードとして記録されています。メモリは大きく分けて,読み書きができる RAM (random-access memory) と読み出し専用の ROM (read-only memory) の2種類に分かれます。 - I/O は,input / output を略した単語で,入出力装置 (input / output device) ともいいます。キーボードやマウス,ディスプレイ,ハードディスクなど,コンピュータにつながるあらゆる機器のことです。機器とコンピュータの間などの境界のことを インタフェース (interface) と呼びます。I/O にもアドレスとデータの概念があります。アドレスを指定することで制御対象を決め,データを入出力することで実際の制御を行います。メモリの読み書きと同じように I/O を制御する方式を メモリマップドI/O ,入出力用の特別な制御を行う方式を ポートマップドI/O といいます。 ポート (port)は,入出力用の特別なアドレスのことです。
- バス (bus) は CPU, メモリ,I/O の間のあらゆる信号を流す共通の信号回路のことです。乗り物のバスに由来する言葉で,さまざまな行き先の乗客が1台のバスに乗り合わせる様子にたとえられています。バスには アドレスバス と データバス の2種類があり,メモリや I/O に対してそれぞれアドレスとデータの信号のやり取りをします。
2. CPU のできること
コンピューターの司令塔の役割をしている CPU は,いかにも複雑な振る舞いをすると思うかもしれません。しかし,原理を突き詰めて考えると実は単純な処理をひたすら行っているのに過ぎません。 CPU が行えることは,基本的にはデータのコピー,四則演算や論理演算などの単純な計算,あるいは比較などの単純な判断のみです。 複雑な機能を持ったプログラムは全て,これらの単純な処理の組み合わせで実現されます。
みなさんの中には本当にそんな単純な処理の組み合わせで,複雑なコンピューターの振る舞いが本当に実現できるのか,不思議に思っている人がいると思います。
たとえば複雑な科学計算はどうでしょう。 アルゴリズム という言葉を聞いたことはないでしょうか。アルゴリズムが定義できる計算はどんな計算でも,原理上は必要な空間と時間を用意しさえすれば実現できます。アルゴリズムを構成する1つ1つの処理は,容易に前述の単純機能の組み合わせで記述できます(そうでないとアルゴリズムとは言えません)。アルゴリズムはそれらの処理を組み合わせているので,やはり単純機能の組み合わせで表現できると言えます。
もちろん,あまりに複雑すぎて,メモリが何TBあっても足りない計算や,1日1週間どころか地球や宇宙が滅亡するまでかかっても終わらないような,とてつもない計算もあり得ます。そもそも計算によって求めることができない問題もあります。
このあたりの話は, 計算論 (あるいは計算理論,theory of computation)と呼ばれる学問領域で論じられています。計算論は,計算でできることは何か,そもそも計算とは何か,といった問題を出発点としています。コンピューター (Computer) は,もともと計算機という意味の単語ですが,計算論の立場からすると,コンピューターとは原理上,計算しかできない機械なのです。こういうと,みなさんはコンピューターの可能性はそんなに小さなものなのかと思うかもしれませんが,むしろ計算という言葉が,みなさんの思っているイメージよりも,ずっと広い可能性を持っていると考えた方がいいです。
ちなみに,原理上不可能,あるいは複雑すぎて事実上不可能な計算は,どんな高級言語をもってきてもコンピューターでは実現できません。逆に言うと,高級言語で実現できる計算は,必ず計算可能で,単純機能の組み合わせとして表現できるはずです。
画面の描画はどうでしょう。パソコンの画面はドットと呼ばれる小さな点の集まりであることはみなさんも知っていると思います。また全ての色は光の3原色 (赤,緑,青: RGB) を一定の割合で混ぜることで表現できることも知っているでしょう。実は,あるアドレスに3原色それぞれの明るさに対応した数値を書き込むと,そのアドレスに対応した画面のドットに色がつく仕組みになっています。気の遠くなるようなたくさんのドットに対して,CPU が根気強く数値を設定することで,画面の描画を実現しています。
ちなみに最近は,CPU が直接画面を描画するのではなく,画面描画に特化した特殊なプロセッサ (GPU: Graphics Processing Unit) に処理を代行させることが一般的になりました。しかし,その GPU にし てもやっぱり,基本的にはデータのコピー,四則演算や論理演算などの単純な計算,あるいは比較などの単純な判断を組み合わせています。GPU では,たとえば三角関数など多少高度な計算を行いますが,それも原理を突き詰めると単純な計算を集めたものです。前述の通り,複雑な科学計算は全て単純な計算を集めたものでしたよね!
また,マウスからの入力は,マウスから送られる2次元の座標信号の数値を読み取ることで実現します。マウスは,ユーザーが動かした相対的な移動距離を認識して数値化します。 一般に I/O は外界とのやりとりを数値データの読み書きとして実現しています。制御と言っても,CPUはI/Oに対してタイミングよく数値を読み書きするだけです。 ロボットや自動車,飛行機,ロケットなど,どんなに高度な制御も基本はすべてこれです。
どうでしょう? みなさんの知っているコンピューターの機能が,単純機能の組み合わせで実現できそうか,想像してみてください。
3. CPUの原理としくみ
さて,CPU の可能性についての話はこのくらいにして,実際にどういった原理としくみになっているか紹介しましょう。
一般的な CPU は レジスタマシン (register machine) と呼ばれる計算モデルに基づいています。今回の説明はレジスターマシンを前提にしています。他の代表的な計算モデルは,計算論でよく用いられるオートマトン (automaton) やチューリングマシン (Turing machine) ,ラムダ計算 (lambda calculus),そしてコード生成でよく用いられるスタックマシン (stack machine) などがあります。
3.1 レジスタ
前述の通り,CPUは レジスタ (register, processor register) と呼ばれる小規模な記憶装置を持っています。1つ1つのレジスタは 8bit, 16bit, 32bit,最近では 64bit といった数値を記憶することができます。CPUは数個から多くとも数十個程度のレジスターを持っています。
レジスターはメモリと比べて記憶容量が小さいですが,その代わりとても高速に読み書きすることができ,レジスターに記憶されている値を使って四則演算や論理演算などもできます。CPU が行う処理は,メモリからレジスタに値を読み込んで( ロード :load),何らかの演算をし,レジスタに格納されている結果をメモリーに書き込みます ( ストア: store)。基本的には,この繰り返しです。
3.2 アドレッシングモード
レジスタの記憶する数値を,そのままの数値として解釈する場合と,アドレスとして解釈する場合があります。アドレスとして解釈する場合にはさらに,絶対的なアドレスとして解釈するのか,相対的なアドレスとして解釈するのか,あるいは簡単なたし算やかけ算を行った結果のアドレスとして解釈するのか,などといった違いがあります。このような解釈の指定のしかたを アドレッシングモード (addressing mode) といい,機械語命令で指定できます。
CPU によっては,アドレスを指定するためのレジスタとデータを記憶するためのレジスタが分かれていることがあります。しかし最近のCPU ではたいていアドレスもデータも扱える 汎用レジスタ (general purpose register) を採用しています。簡単のために,この授業では汎用レジスタであることを前提に話を進めます。
具体的なアドレッシングモードの例は次の通りです。
- 即値 (immediate または literal): 指定されている数値そのものを指します。典型的には,レジスタにそのままの数値をロードする場合に用います。
- 絶対アドレス (absolute address): 指定されている数値で示されるメモリー上のアドレスを指します。典型的には,絶対アドレスで指し示すデータをレジスタにロードする場合に用います。
- レジスタ直接 (register direct) あるいは単にレジスタ(register): レジスタの数値そのものを指します。典型的には,あるレジスタの数値をコピーして別のレジスタにロードする場合に用います。
- レジスタ間接 (register indirect): レジスタの数値で示されるメモリー上のアドレスを指します。典型的には,あるレジスタの数値をアドレスとして解釈して,メモリ上のデータをロードする場合に用います。
- ベース・オフセット (base plus offset または base plus displacement) 指定したレジスタが指すアドレス (ベース) に符号つき数値 (オフセット) を加えたアドレスを指します。レジスタ間接に機能を足したものです。
- レジスタ自動加算 (register auto increment) 指定したレジスタのアドレスを指すが,命令が終わった後,符号付き数値を加算・減算します。レジスタ間接に機能を足したものです。演算を伴いますが。一般的なCPUではこのときにフラグは変化しません。(フラグについては後述)
- プログラムカウンタ相対アドレス (PC-relative address): 現在のプログラムカウンタが指すアドレスに,符号つき数値を加えたアドレスを指します。主にコード中の分岐命令で用いられるアドレッシングモードです。ベース・オフセットの特殊な場合と考えることができます。(プログラムカウンタについては後述)
3.3 特殊レジスタ
レジスタには特殊な用途のものがあり,特殊レジスタあるいは専用レジスタ (special purpose register) といいます。どのCPUでも装備している特殊レジスタとして,プログラムカウンタ (program counter, instruction pointer, instruction address register) とスタックポインタ (stack pointer, stack register) があります。
CPU によってはスタックポインタを特殊レジスタではなく汎用レジスタの1つとして位置づけられる場合もあります。
3.4 プログラムカウンタ
CPU はプログラムカウンタの指し示すメモリから機械語コードを読み取り ( フェッチ: fetch),その意味を解析し ( デコード: decode), 実行し (execute),結果を書き込みます ( ストア: store)。フェッチが終わったときにプログラムカウンタを1つ進めます。また,命令によってはプログラムカウンタに特定の値を代入する,つまりプログラムカウンタを移動させるものもあります ( 分岐命令 といいます)。
この,フェッチ/デコード/実行/ストアのサイクルを 命令サイクル (instruction cycle, fetch-and-execute cycle, fetch-decode-execute cycle, FDX) といいます。CPU は,1つの機械語命令実行で必ず命令サイクルを1通り行っています。
1つの機械語命令を実行するにあたり,必ず命令サイクルは一巡します。しかし,フェッチ→デコード→実行→ストア→フェッチ→デコード→実行→ストア,と律儀に順番を守る必要はなく,たとえば,ある命令の実行をしている間に,次の命令のフェッチを行う,という具合に効率よく進めることも可能です。このような方式は, パイプライン方式 (instruction pipeline), スーパースカラー方式 (superscalar) など,さまざまなバリエーションがあります。しかし,このことに初めて気づいた人は本当に賢いですね。筆者が初めて知ったときには,その巧妙さにビックリしたものです。
3.5 スタックポインタ
スタックポインタは,データ構造の1つ,スタック構造で登場する概念です。 レジスタのスタックポインタは,関数や手続き,サブルーチンの呼び出しの実現に用います。 関数呼び出しの様子を思い出してみてください。関数fの中で関数gを呼び出し,さらに関数hを呼び出したとき,関数hが終了すると関数gに戻り,関数gが終了すると関数fに戻りますね。この呼び出し関係を見てみると,後入れ先出し (LIFO: Last In First Out) になっています。スタックも後入れ先出しのデータ構造です。したがって,CPUで関数呼び出しを実現するのにスタックを用いています。
最も単純なサブルーチンの呼び出しを説明します。サブルーチン呼び出し (サブルーチンコール,あるいは単にコール: 英語だと subroutine call, subroutine invocation) 命令をフェッチ・デコードすると,実行・ストアのサイクルで,スタックポインタの指し示すアドレスに現在のプログラムカウンタの値を書き込み,スタックポインタを増やし,プログラムカウンタに呼び出す先のサブルーチンのアドレスを代入します。
一方,サブルーチンを終了して復帰する命令 (リターン命令: return instruction) をフェッチ・デコードすると,実行・ストアのサイクルで,スタックポインターを減らし,スタックポインターの指し示すアドレスから値を読み取ってプログラムカウンターに代入します。
CPU の種類によって,スタックポインターの増減の方向や,順番などが異なりますが,基本的にはこの原理でサブルーチンの呼び出しと復帰を実現します。引数や戻り値,再帰呼び出しをどう実現するかについては,後で詳しく説明します。
3.6 フラグ
フラグは計算結果を格納する特殊レジスタです。一般的には次のようなフラグがあります。
- N(negative)フラグ: 計算結果が負だと1,それ以外で0になります。
- Z(zero)フラグ: 計算結果が0もしくは等しかった場合に1,それ以外で0になります。
- C(carry)フラグ: 符号なしの整数だと思って計算したときに,桁上がりもしくは桁下がりが起こった場合に1,それ以外で0になります。
- V(overflow)フラグ: 符号つきの整数だと思って計算したときに,桁あふれが起こった場合に1,それ以外で0になります。
なお,負の整数は 2の補数 を用いて表現することが多いです。2の補数はビットを反転して1を加算したものです。2の補数を用いる理由は,加算回路と減算回路をうまく統合できるためです。
3.7 機械語
先ほどからたびたび 機械語 (マシン語: machine language, machine code) や 命令 (instruction) という言葉が出ていて,具体的にどのようなものか気になっている人もいるでしょう。
CPU が解釈する命令は,実際には対応する数値で表されます。もともと数値に何らかの意味を対応づけたものを コード (code) といいます。CPU が解釈する命令の場合でも,命令と数値が対応しています。このことからコードという言葉がプログラムのことも意味するようになりました。
命令の集合のことを 命令セット (インストラクションセット: instruction set) といいます。機械語とは何か正確に言うと,CPU によって直接実行される命令とデータの体系のことで,命令セットを含んでいます。命令セットがどのような構成になっているかを大別する意味で使うときは, 命令セットアーキテクチャ (インストラクションセットアーキテクチャ, ISA: instruction set architecture) といいます。
よくコンピュータ関連の記事で,x86 とか IA-32 などといった言葉をインテル製の CPU やその互換 CPU を指すときに使います。これらの語源をたどると,x86 はインテルの CPU の型番が昔は 8086,80286, 80386, Intel 486 (i486) などと表されていたことから来た言葉です。IA-32(Intel Architecture, 32-bit) は,もともと 80386 以降の i486, Pentium, Celeron, Xeon, Atom (とその互換 CPU) といった 32bit の CPU が用いる命令セットアーキテクチャを指す言葉です。
機械語命令は大きくわけて,データ転送命令,算術演算命令,論理演算命令,分岐命令,特殊命令,疑似命令からなっています。
- データ転送命令 (data transfer instruction): レジスタ同士,あるいはレジスタとメモリの間で,データをコピーする命令です。
- 算術演算命令 (arithmetic operation instruction): 四則演算などの算術演算を行う命令です。
- 論理演算命令 (logical operation instruction): and, or, xor やシフトなどの論理演算を行う命令です。
- 分岐命令 (branch instruction): プログラムカウンタを操作する命令です。ジャンプ命令 (jump instruction) ともいいます。
- 特殊命令: さまざまな特殊機能を持つ命令です。
- 疑似命令 (pseudo-ops): 実際の機械語命令には変換されないのですが,プログラムを構成する上で重要な役割を担います。
機械語は実際にはコード,すなわち数値なのですが,それを人間が理解するのは難しいので, アセンブリ言語 (assembly language) と呼ばれる機械語と1対1対応したプログラミング言語で記述します。高級言語と違ってアセンブリ言語は CPU の種類に依存しており,通常異なる種類の CPU では互換性がありません。また, アセンブラ (assembler) の種類によっても違いがあります。アセンブラは,アセンブリ言語を機械語に変換するツールです。
GNU アセンブラ (gas) で IA-32 向けに書いた命令の例を次に示します。
movl $4, %eax
addl %ebx, %eax
movl
や addl
は オペコード (オペレーションコード: opcode, operation code) と呼ばれ,どのような操作を行うかを表します。$4
や %eax,%ebx
は オペランド (operand) と呼ばれ,操作対象を表します。
オペコード movl
は,”move long word” の略で,32bit のデータ転送命令の一種を意味します。オペランド $4
は,整数値 4 を意味します。%eax
は IA-32 の CPU が持つ汎用 32bit レジスタの1つです。movl $4, %eax
は 全体として,レジスタ %eax
に整数値 4 を代入するという意味です。
オペコード addl
は,"add long word" の略で,32bitの加算命令(算術演算命令の一種)を意味します。%ebx
も汎用32bitレジスタの1つです。 addl %ebx, %eax
は全体としてレジスタ %ebx の値を %eax に加えるという意味になります。
結局この2つの命令の意味するところは,レジスタ%eax
にレジスタ%ebx
と整数値4を加えた値を代入する,つまり%eax = %ebx + 4
です。単純な処理を書きたいだけなのに,アセンブリ言語で書くと,なかなかまどろっこしいですね。
CPU にどのような命令があるかは,その CPU のメーカーが公開しています。英語ですが,検索エンジン で CPU の名称 (たとえば IA-32, ARM など)と Instruction Set Reference といったキーワードで探すとよいでしょう。主要な命令を日本語で紹介しているページもあります。