CPUとプログラムの関係

CPUとプログラム

CPUの内部について

作成されたプログラムがコンピュータの中で実行されるまでの流れは下図のようになります。これは、プログラムが動作する仕組みを知る上での前提知識となるものです。

ここで、CPUは最終的にマシン語に変換されたプログラムの内容を解釈して実行する装置になります。

CPUやメモリの実体は、数多くのトランジスタから構成されたIC(集積回路)と呼ばれる電子部品です。CPUの内部は「レジスタ」「制御装置」「演算装置」「クロック」の4つの要素から構成されている。これら4つの要素は電気的に接続されています。

「レジスタ」:処理対象となる命令やデータを格納する領域で、メモリと似たような働きをします。種類によりますが、ひとつのCPU内部には20〜100個ほどのレジスタがあります。

「制御装置」:メモリー上の命令やデータをレジスタに呼び出し、命令の実行結果に応じてコンピュータ全体を制御します。

「演算装置」:メモリからレジスタに読み出されたデータを演算する役割を果たします。

「クロック」:CPUが動作するタイミングとなるクロック信号を発生させるものです。クロックがCPUの外部にあるコンピュータも存在します。

「クロック信号」:「クロックパルス」とも呼ばれ、周波数:GHzで表します。(1GHz=10億回/秒)です。クロック周波数が大きいほど、CPUの動作は速くなります。

メイン・メモリ(主記憶装置)

CPUと制御チップなどを介してつながっており、この場所に命令とデータを格納します。メイン・メモリは読み書く可能なメモリー素子で構成されていて、1バイト(=8ビット)ずつにアドレス(番地)と呼ばれる番号がついています。

CPUはこのアドレスを指定してメイン・メモリに格納された命令やデータを読み出したり、書き込んだりします。しかし、メイン・メモリに格納されている命令やデータはメモリに使用されている素子に揮発性があるために、パソコンの電源をジャットダウンすると消去されてしまいます。

CPUやメモリの構造から、プログラムが動き出すと、クロック信号に合わせて、制御装置がメモリから命令やデータを読み出します。その命令を解釈・実行することで、演算装置でデータが演算され、その結果に応じて制御装置がコンピュータを制御します。ここで制御とはデータ演算以外の処理のことで、主にデータの入出力のタイミングを管理することです。キーボードやマウスからの入力、ディスプレイやプリンタなどへの出力も「制御」によって行われています。

レジスタの集合体であるCPU

4つのCPU内部要素の中で、プログラミングで特に意識しなければならないものは、「レジスタ」です。プログラムはこのレジスタを対象として記述されます。

上図右側は「アセンブリ言語(アセンブラ)」で記述されたプログラムの一部になります。

アセンブリ言語は、本来ならば電気信号である個々のマシン語(上図左側)命令に、その動作を表す英語のような略語(ニーモック)を割り当てたものです。movやsubは、データ格納やデータ合計という動作を表す略語であり、アセンブリ言語と機械語は、基本的に1対1で対応しています。

この部分が、C言語やJavaなどの高級言語と異なるところであり、CPUの動作説明をするのにアセンブリ言語が適している大きな理由です。

アセンブリ言語を機械語に変換することをアセンブル、その逆変換を逆アセンブルと呼びます。

ここで上図右側のアセンブリの項目のさらに右側のに記述されているeaxやebpがレジスタを表し、それに対応する左側のpushやmovといった命令がレジスタを操作して、データの格納や合計などを行っています。

上図で機械語に変換されたプログラムの命令どおりにCPU内部でレジスタを操作してデータを処理する手順を人間が理解しやすい命令語に割り当てているものがアセンブラ言語であり、それをさらに人間にとって記述しやすい言語として作られたものが、高級言語(C言語、Javaなど)となります。

つまりプログラムは主記憶装置に格納されたデータをCPU内部でのレジスタの集合体を利用してどのように制御・演算処理していくのかという手順を指し示しているとイメージすることが出来るかと思います。

さらに言えば、プログラミングとはコンピュータにさせたい作業ををメモリやCPU内部構造(レジスタ)を理解した上で、その仕様に沿って作業を行うための作業手順書を作成する(プログラミングする)ということです。

CPUの種類によって、その内部のレジスタの数や種類、格納できる値のサイズも異なってきます。上図は主なレジスタの種類です。レジスタに格納されている値は、「命令」と「データ」を表す場合があり、「データ」の値には「演算」と「メモリアドレス」の2種類があり、値の種類によって格納するレジスタの種類が異なります。CPU内部のレジスタにはそれぞれに役割が異なります。(上図参照)

プログラマはこれらのレジスタを意識してプログラムを組みます。

プログラムカウンタによってプログラムの流れを決める

CPUのイメージがつかめたところで、今度はプログラムが記述された通りに実行される仕組みを説明します。

上図は、プログラムが起動されたときのメモリの内容のイメージです。OSは、ユーザーからプログラムの起動が指示されると、HDDに保存されているプログラムをメモリにコピーします。上図の場合プログラムは、123と456という2つの値の加算結果をディスプレイに表示するものです。

メモリには命令やデータの格納場所を示すアドレスが割り振られています。実際には、1つのデータや命令が複数のアドレスにまたがって格納されるのが一般的ですが、上図では便宜上説明を簡単にするために1つのアドレスに命令やデータが収まるものとします。

アドレス0100番地が、プログラム開始の位置であり、OSはプログラムをHDDからメモリにコピーした後で、レジスタのひとつであるプログラム・カウンタに0100番地を設定します。これによりプログラムの実行が開始となり、CPUがひとつのメモリを実行すると、プログラムカウンタの位置が自動的に1つ増加します。

例えば、0100番地の命令を実行すると、カウンタの値は0101となります。(複数のメモリ・アドレスを占める命令を実行した場合は、カウンタの値はその命令のサイズ分だけ増加します。)CPUの制御装置は、プログラムカウンタの値を参照して、メモリから命令を読み出して実行します。つまり、プログラム・カウンタが、プログラムの流れを決定づけているのです。

条件分岐と繰り返し

プログラムの実行の流れには、「順次進行」「条件分岐」「繰り返し」の3種類があります。

「順次進行」:アドレス値の順に命令を実行すること

「条件分岐(if)」:条件に応じて任意のアドレスの命令を実行すること

「繰り返し(for)」:同じアドレスの命令を何度か繰り返して実行すること

順次進行の場合は、カウンタの値が1ずつ増加していくだけですが、条件分岐や繰り返しの場合は、それらの機械語命令が、プログラムカウンタの値を任意のアドレスに設定することになります。

これにより、前のアドレスに戻って同じ命令を繰り返したり、任意のアドレスに移動して分岐することが出来ます。

上図は、メモリに格納された値「123」の絶対値をディスプレイに表示するプログラムが、メモリに格納された状態を示しています。0010番地から順番に開始して、0102番地に着くと、そのときのアキュムレータの値が正ならば0104番地にジャンプするようになっています。この時点でアキュムレータの値は「123」と正の値になっているので、0103番地はスキップされ、0104番地にジャンプします。

実際には0104番地にジャンプせよという命令は、間接的に「プログラム・カウンタに0104番地を設定せよ」ということと同じ意味になります。

条件分岐や繰り返しで行われる「ジャンプ命令」は、その直前に行われた演算の結果を参照して、ジャンプするかどうかを判断します。この役割を果たすCPU内のレジスタを「フラグ・レジスタ」といいます。「フラグ・レジスタ」は、直前に実行した演算の結果によるアキュムレータの値が負、ゼロ、正のいずれかになったかを記憶する役割を持つものです。

CPUが何らかの演算を行うたびに、その結果に応じて、フラグ・レジスタの値が自動的に設定されます。条件分岐では、ジャンプ命令の以前に何らかの比較演算が行われ、その結果の値をCPUが参照してジャンプ命令を実行するかどうかを判断します。

演算結果が正、ゼロ、負の3つのどの状態になったかは、フラグ・レジスタの3つのビット(上図)で表します。上図は32ビットのフラグ・レジスタです。このレジスタでは、0ビット目、1ビット目、2ビット目がそれぞれ正、ゼロ、負になったことをそれぞれが1という値を表すことで示しています。

CPUが比較を行う仕組みは、アキュームレーターに格納されたAという値と汎用レジスタに格納されたBという値を比較するとき、比較のための命令が実行されると、CPU内部の演算装置は「AーB」という減算を行い、その結果が正か負かのどちらになったかが、フラグ・レジスタに記録されます。つまり正はA>B、ゼロはA=B、負はA<Bで表されます。比較命令はCPU内部で減算として処理されます。

関数を呼び出すときの仕組み

プログラムにおいて、関数を呼び出す処理は、プログラムカウンタの値を、関数が格納されたアドレスに設定することで実現されます。但し、条件分岐や繰り返しの仕組みとは異なります。関数呼び出しは、関数内部の処理が完了したら,関数の呼び出し元の次のアドレスに処理の流れを持ってこないといけません(上図参照)

あるアドレスに関数を設定したら、関数の入り口のアドレスでその関数が呼び出されます。そして関数が処理されたら、最初に関数を設定したアドレスの次のアドレスに戻って再びプログラムの流れに乗る。ということです。

上図では、変数a,bにそれぞれ123,456という値を代入してからそれらに引数(パラメータ)をあたえてMyfunkという関数を呼び出す。C言語のプログラムになります。アドレスには、C言語のプログラムがコンパイルされてマシン語に変換されたときのアドレスを仮定しています。C言語 1行のプログラムは、複数行のマシン語に変換されることが多いのでアドレスがバラバラになっています。

コール命令とスタック命令

関数を指定のアドレスから呼び出す命令を「コール命令」関数が処理された後、次に実行すべき命令をその関数の呼び出し元の次のアドレスに設定する命令を「リターン命令」と呼びます。

「コール命令」:関数の入り口のアドレスをプログラム・カウンタに設定する前に、関数の呼び出しの次に実行すべき命令のアドレスをスタックというメインメモリ上の領域に保存します。関数処理が終了したら、関数の最後(関数の出口)でリターン命令を実行します。

「リターン命令」:スタックに保存されたアドレスをプログラム・カウンタに設定する機能を持っています。上図の場合、Myfunc関数が呼び出される前にアドレス0154という値がスタックに保存されます。Myfunc関数が処理を終了すると、スタックから0154という値が読み出され、プログラム・カウンタに設定されます。

プログラムをコンパイルすると、関数呼び出しがコール命令に変換され、関数処理が終了すると同時にその処理がリターン命令に変換される

CPUの動作のイメージ

CPUで使われるマシン語命令の種類を大きく分類したものが上図です。ここに示すとおり、CPUに出来ることは意外に少ないのですが、この命令の組み合わせで、複雑なプログラムを処理しています。

コンピュータの仕組みやCPUは一見すると難しそうに思えますが、ひとつひとつは簡単な命令を積み重ねているだけである。

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です