はじめに
当記事は、プログラムがコンピュータ内部でどのように動作しているのか
理解することが目的である。
最初に、プログラムの4つの呼称の違いを理解する。
次に、プログラム実行方式について理解する。
そして、メモリ領域の割り当て方を理解する。
最後に、CPUの内部構成と命令実行手順を理解する。
これらによってプログラム自身と、
それがコンピュータ内部のハード/ソフトウェアとどのような関係を持っているのか
を理解することで目的を達成する。
プログラム実行単位
プログラム1は実行して処理される単位によって呼称が異なり、
それぞれジョブ、プロセス、タスク、スレッドの4つである。
注意:
各用語の厳密な定義は、プラットフォームや使用する人によって異なる。
そのため、呼称毎の定義範囲は曖昧である。
概要図
1. ジョブ
ユーザからOSまたはシェルに対して行われる命令の単位のこと。
例:
アプリケーションアイコンをダブルクリック。
シェルでコマンドを入力する。
2. プロセス
ユーザの命令を処理するため(またはOS自身が自動で行う処理)に必要である、
メモリにロードされるデータ一式のこと。
言い換えると、OSやシェルが管理する単位のこと。
例:
wordやexcelなどを実行すると、
メモリ領域に実行ファイル(exe)やアプリに関わるデータ一式がロードされる。
つまり、実行中のアプリケーションプログラムがプロセスである。
3. タスク
プロセスと同義。
4. スレッド
プロセス内で発生する一連の処理のことであり、
CPUがプログラムを実際に処理する単位のこと。
例:
wordの場合、文字入力や図形描画、印刷処理、文章保存など。
プロセスとの違い:
現実における1日の仕事がプロセスで、その中で行う作業1つ1つがスレッド。
例えば営業職の場合、勤務中がプロセスであり、
その過程で行う朝礼やルート営業、新規営業、見積書作成などがスレッド。
プログラム実行方式
プログラムはソースコードで記述されている。
そのため、プログラムを実行するには機械語に翻訳する必要がある。
翻訳の方法はコンパイラ方式、インタプリタ方式、中間コード方式の3つあり、
言語毎に採用している方式は異なっている。
1. コンパイラ方式
まず、コンパイラでソースコードをオブジェクトコードに変換する。
次に、リンカでオブジェクトコードとライブラリを連結させロードモジュールを作る。
最後に、ローダでロードモジュールをメモリに読み込む。
コンパイラとは:
プログラミング言語で書かれたソースコードを、
コンピュータが実行可能な形式であるオブジェクトコードに変換する
ためのソフトウェア。
オブジェクトコードとは:
バイナリ形式(0と1の羅列)のファイル。
リンカとは:
複数のオブジェクトコードやライブラリを連結して、
1つのロードモジュール(プログラム)を作成するソフトウェア。
ロードモジュールとは:
実行可能ファイル。
windowsの場合、拡張子がexeのファイル。
ローダとは:
プログラムをメモリに読み込むソフトウェア。
コンパイラの処理内容
aからeの順番で処理が行われていく。
a. 字句解析
コードに記述されている命令を字句(トークン)単位に分解し、
間違いがないかチェックする。
字句単位とは:
キーワード・・if, function, constなどの予約語
識別子・・・・変数や関数などに付けた名前
演算子・・・・+, =などの記号
区切り文字・・括弧やコンマなどの記号
リテラル・・・""など数値や文字列を直接コードに書き込む記法
a = b * c + d
を字句解析すると、
"a" "=" "b" "*" "c" "+" "d"
b. 構文解析
対象のプログラミング言語の文法に従っているかを確認するために、
BNFを用いてトークンを構文木に変換する。
BNF(バッカス・ナウア記法)とは:
プログラミング言語の文法をコード化して定義するためのメタ言語。
構文木とは:
ツリー状のデータ構造。
a = b * c + d
を構文木で表現すると、
a = b * c + d
をBNFで表現すると、
※あくまでも例であるため正確ではない
<assign> ::= <variable> "=" <expression>
<expression> ::= <term> { "+" <term> }
<term> ::= <factor> { "*" <factor> }
<factor> ::= <variable>
c. 意味解析
1つの文(命令)として矛盾がないかチェックする。
例:
変数の型と変数に格納されている値をチェックする。
d. 最適化
コードの中にある無駄を排除する。
目的は、プログラムの実行時間を短縮するため。
例:
コードの重複を削除。
プログラム実行結果に影響しない命令の削除。
ループ処理内にある、値の変化しない式をループの外に移動。
e. コード生成
オブジェクトコードプログラムを作成する。
2. インタプリタ方式
ソースコードをそのままメモリに読み込み、
それを実行する時に、1行ずつ字句解析や構文解析を行いながら処理する。
そのため、実行時にインタプリタ処理系のソフトウェアが必要である。
コンパイラ方式の場合:
コンパイラ後のオブジェクトコードをメモリに読み出し、
CPUが直接実行する。
3. 中間コード方式
ソースコードをコンパイラを使って中間コードに変換する。
そして、中間コードを専用のインタプリタ(仮想マシン)を使って解釈しながら実行する。
中間コードとは:
特定の機械語に依存しないコード。
機械語は、異なるプラットフォーム(OSやCPUなど)間で互換性がない。
メモリ領域の割り当て
OSがプロセスに対して、一定量のメモリ領域を割り当てる。
そのメモリ領域は、テキスト、静的、ヒープ、スタックの4つの領域に分かれている。
1. テキスト領域
上図のtext。
機械語に翻訳されたプログラムのソースコードを配置する領域。
つまり、CPUが取り出す命令が格納されている。
2. 静的領域
上図のdata。
グローバル変数や、C言語におけるstatic変数などの静的変数を配置する領域。
またオブジェクト指向言語では、クラス情報も格納される。
クラス情報:
クラス名、クラス変数、クラス定数、メソッド定義、フィールド定義など
クラス情報がロードされるタイミング:
事前に全てのクラス情報をロードする方式と、
クラスを使用する時に、そのクラス情報をロードする方式がある。
3. ヒープ領域
上図のdataとその下の矢印部分。
プログラム実行中に動的に確保や開放を行う領域。
オブジェクト指向言語では、インスタンス(インスタンス変数)が格納される。
ガベージコレクション:
どこからも参照されなくなったインスタンスがあると、
ガベージコレクター(プログラム)が自動的に解放する。
これによってヒープ領域のメモリリークを防ぐことができる。
変数はポインタを格納する:
var = new Animalのようにインスタンスを変数に入れると、
変数には、インスタンスが配置されているヒープ領域のアドレスを示す
ポインタが格納される。
4. スタック領域
上図のstackとその上の矢印部分。
関数内の引数やローカル変数、戻り値などを配置する領域。
関数の呼び出し時に確保され、関数の戻り時に解放される。
スレッドとの関係:
1つのスレッドに1つのスタック領域が割り当てられる。
スレッドに複数の関数がある場合、
スタック領域にその関数が積み上げられ、上から順番に解放されていく(LIFO)。
CPU内部構造
CPUは演算装置と制御装置で構成されている。
概要図
1. 制御装置
役割は、プログラムの命令を解釈し、その命令処理に対応する装置に伝達する。
構成は、命令レジスタ、プログラムカウンタ、命令デコーダ。
レジスタとは:
CPU内部にある記憶装置であり、
処理に必要なデータや処理結果を一時保存する。
概要図では、命令レジスタ、プログラムカウンタ、汎用レジスタが該当する。
・命令レジスタ
メモリから取り出した命令を一時保存する。
命令は、命令部とオペランド部で構成されている。
命令部:
命令の種類が格納されている。
オペランド部:
格納されているのは、処理対象データやそれが格納されたメモリアドレス、
図にはないが他のベースレジスタやインデックスレジスタの値と組み合わせて
アドレスを求めるための値。
・プログラムカウンタ
次に取り出す命令が格納されているメモリアドレスを記憶する。
・命令デコーダ
命令部のコードを解読し、必要な装置に伝達する。
2. 演算装置
役割は、演算処理を行う。
構成は、ALUと、汎用レジスタ。
・ALU(算術論理演算装置)
演算処理を行う装置。
・汎用レジスタ
演算に必要なデータや結果を一時保存する。
CPUの命令実行手順
大まかな流れは、
最初に、メモリから命令を取り出し命令レジスタに一時保存する。
次に、その命令を解読しメモリから対象データの読み出す。
最後に、命令を実行する。
1. 命令の取り出し(フェッチ)
プログラムカウンタを参照しメモリから命令を取り出す。
そして、それを命令レジスタに保存する。
最後に、プログラムカウンタの値が1つ増加される。
2. 命令の解読
命令レジスタの命令部が、命令デコーダに送られる。
命令デコーダは、それを解読して必要な装置に制御信号を送る。
3. 対象データの読み出し
命令レジスタのオペランド部を参照して、命令処理に必要なデータを読み出す。
もし命令の内容が演算処理なら、読み出したデータを汎用レジスタに伝達する。
または変数の値を変更する処理なら、メモリに伝達する。
というように、命令の目的に応じて読み出したデータを対処する。
4. 命令実行
仮に演算処理なら、
汎用レジスタから対象データを取り出して演算装置が処理を行う。
その結果を汎用レジスタに書き戻す。
まとめ
プログラムが実行されると、OSによってメモリ領域を割り当てられる。
そこに、実行するのに必要なデータ一式を、プロセスとして読み込まれる。
CPUがメモリ領域から1つずつ命令を取り出し解読し、
処理に対応するハードウェアに対処させる。
以上がプログラムの内部的な実行過程である。
-
コンピュータに対する命令をまとめて記述したもの。 ↩