はじめに
自分は CS の知識があまりなく、学習のため プログラムはなぜ動くのか 第3版 知っておきたいプログラミングの基礎知識 を読みました。
特に面白かったのは以下です。
-
レジスタ
数えられるぐらいの個数のレジスタで複雑な処理が実現されている -
マシン語の主な命令
ジャンプ、リターン、コールなどの命令で、普段使う if や while が作られている -
API
OS の違いが何か理解していなかったが、OS が違うと API が違ってくるとわかった -
プログラムの実行
ソースコードからネイティブコードが生成されるまでに何が行われているかわかった
コンピュータ
コンピュータの5大装置
- 制御(CPU)
- 演算(CPU)
- 記憶(メモリ)
- 入力(周辺装置)
- 出力(周辺装置)
CPU
IC(集積回路)と呼ばれる電子部品。内部は数百万〜数億個のトランジスタで構成されている。
あるCPU(例えばIntelのx86など)は、そのアーキテクチャ専用の命令セット(マシン語)しか理解できない。
周辺装置との入出力のために専用のI/Oアドレス空間を備えている。そのアドレスの割り当てはコンピュータの機種・設計によって異なる。
CPU の 4 要素
- レジスタ(CPU 1 つにつき数個〜数十個ある)
命令やデータを格納する記憶領域。演算もできる。レジスタ名で区別されている。
CPUの種類によって、レジスタの数、種類、格納できる値のサイズは異なる。
プログラムから直接操作できないレジスタもある。 - 制御装置
メモリ上の命令やデータをレジスタに読み出し、命令の実行結果に応じてコンピュータ全体を制御する。 - 演算装置
メモリからレジスタに読み出されたデータを演算する。 - クロック
CPUが動作するタイミングとなるクロック信号を発生させる。CPU外部にある場合もある。
| レジスタ | 説明 |
|---|---|
| プログラムカウンタ | OS はプログラムをハードディスクからメモリにコピーした後で、プログラムカウンタに開始位置を設定する。CPUが1つの命令を実行するごとにプログラムカウンタの値が増加していく |
| フラグレジスタ | CPUがデータの読み込みや演算を行うたびに、フラグレジスタの値が自動的に設定される。ジャンプ命令の前の比較演算などで参照される |
| ベースレジスタ | インデックスレジスタと併せて使うことで、メインメモリ上の特定の領域を区切って配列のように使える。 (例)実際のアドレス = ベースレジスタの値 + インデックスレジスタの値 |
| インデックスレジスタ | 配列のインデックスに相当する値 |
| マシン語の主な命令 | 説明 |
|---|---|
| データ転送命令 | レジスタとメモリ、メモリとメモリ、レジスタと周辺装置の間でデータの読み書きをする |
| 演算命令 | アキュームレータで算術演算、論理演算、比較演算、シフト演算を行う |
| ジャンプ命令 | 条件分岐や繰り返し、無条件のジャンプを行う |
| コール命令 | 関数の入り口のアドレスをプログラムカウンタへと設定する前に、関数呼び出しの次に実行すべき命令のアドレスをメインメモリ上のスタックに保存する |
| リターン命令 | 関数の処理が完了したらリターン命令を実行し、スタックに保存されたアドレスをプログラムカウンタに設定する |
メモリ
PCの主な記憶装置。CPUとは制御用ICなどを介してつながっている。ディスクに比べると高価で高速。
読み書き可能なメモリ素子で構成されていて、1バイトずつにアドレスがついている。CPUはこのアドレスを指定して、格納された命令やデータを読み出し・書き込みする。
メインメモリの命令やデータはPCの電源を切ると消える。
| メモリの種類 | 説明 |
|---|---|
| DRAM | リフレッシュ必要。安価で低速。 |
| SRAM | リフレッシュ不要。高価で高速。 |
メモリの 4 要素
- 電源(VCC、GND)
- アドレス信号
- データ信号
- 制御信号(RD、WR)
メモリの書き込み・読み出し手順
- VCCに+5V、GNDに0Vの電源をつなぐ
- データの格納場所をアドレス信号で指定
- データの値をデータ信号で入力
- WR信号を1(読み出しならRDを1)にする
VRAM
VRAMというメモリに、ディスプレイに表示される情報を記憶する。
GPUは画面表示専用のプロセッサ。
| OS | VRAM |
|---|---|
| Windows時代 | メインメモリとは他にグラフィックボードがあり、グラフィックボードの上にGPUやVRAMがある |
| MS-DOS時代 | メインメモリ上にVRAM領域があった |
ディスク
PCの主な記憶装置。メモリに比べて低速で安価。
ハードディスクに格納されたプログラムは、メインメモリにロードされてから実行される。なぜならCPUはプログラムカウンタでメインメモリのアドレスを指定しているから。仮にハードディスクとCPUが直接やりとりすると、ハードディスクが低速なためにプログラムの実行速度が下がる。
ディスクキャッシュ
一度ディスクから読み出されたデータを保存しておくメモリ内の領域のこと。次に同じデータが読み出されるときには、実際のディスクではなくディスクキャッシュの内容を読み出す。
(※)現在ではハードディスクのアクセス速度が向上したため、ディスクキャッシュはそれほど効果を上げなくなった。
仮想記憶
ディスクの一部を仮想的にメモリとして使う。これによって、メモリが不足している状態でもプログラムを実行できる。
(例)50MBのメモリ空き容量で100MBのプログラムを実行できる。
実メモリの内容と、ディスク上の仮想メモリの内容を部分的に置き換えながらプログラムを実行する。仮想メモリのサイズは実メモリの1〜2倍が適切。
ページング方式(Windows)
実行されるプログラムを、その構造に関係なく一定の大きさのページに分割し(意味のある単位で分割するのはセグメント方式)、ページ単位でメモリとディスク間の置き換えを行う(ページイン、ページアウト)。
Windowsではページサイズは4KBで、仮想記憶を実現するためにディスク上に仮想メモリとなるファイル(ページングファイル)を用意する。
SSD
読み書きができて、かつ電源を切っても内容が消去されないフラッシュメモリをハードディスクとして使う。実体はメモリだが、利用者からはハードディスクに見える。
ディスクの物理構造
トラック(円周)をセクタ(領域)に区切る。セクタがディスクを物理的に読み書きする最小単位(512バイト)だが、実際には1クラスタ(セクタの整数倍)で行う。
同一クラスタ内に複数ファイルを格納することはできない。小さなファイルでも必ず1セクタの領域は占有してしまう。かといってクラスタサイズを小さくしすぎると、ディスクのアクセス回数が増えてファイルの読み書きが遅くなったり、セクタの区切りを示すための領域が増えてディスク全体の記憶容量が減ったりするため、バランスが重要である。
I/O
I/Oコントローラ
PCのコネクタの奥にある、コンピュータ本体と周辺装置の電気的特性を相互に変換するIC。
1つのI/Oコントローラで1つの周辺装置を制御することも、複数の周辺装置を制御することもある。
ポート
I/Oコントローラの中にある、入出力されるデータを一時的に格納しておくための一種のメモリ。ポートはレジスタと呼ばれることもあるが、CPUのレジスタと違ってデータ演算機能はない。
それぞれのポートはポート番号(I/Oアドレス)で識別される。
| I/Oの命令 | 説明 |
|---|---|
| In命令 | 指定したポート番号のポートからデータを入力し、CPU内のレジスタに格納。 (例)キーボード→データの形式や電圧などの変換→ポート→CPUレジスタ |
| Out命令 | CPUのレジスタに格納されているデータを、指定したポート番号のポートに出力 (例)CPUレジスタ→ポート→データの形式や電圧などの変換→ディスプレイ |
割り込み要求(IRQ)
実行中のプログラムを一旦停止して、他のプログラムに実行を移す仕組み。
I/Oコントローラが割り込み処理を要求し、CPUが実行する。
割り込み要求を受け付けたCPUは、現在実行中のメインプログラムが使っているレジスタの値をすべてメモリ上のスタック領域に退避。そして割り込み処理プログラムの中で周辺装置との入出力が完了したら、最後にスタックに退避しておいた値をレジスタに戻し、メインプログラムの処理を続行する。
| 用語 | 説明 |
|---|---|
| 割り込み番号 | 割り込み要求を行った周辺装置を特定するために使う、I/Oポート番号とは別の番号 |
| 割り込みコントローラ | これが複数の周辺装置からの要求を順番にCPUに伝えてくれる (例)CPU→割り込みコントローラ→I/Oコントローラ |
| ポーリング | 複数の周辺装置の状態を順番に調べること。割り込みがあまり発生しないシステムに適した処理 |
DMA
CPUを仲介させずに周辺装置がメインメモリとデータ転送を行うこと。これにより、大量のデータを短時間でメインメモリに転送できる。
DMAコントローラ
DMAを行うための窓口をいくつか備えていて、それらをDMAチャネルという番号で識別する。DMAを行う周辺装置は、装置に割り当てられたDMAチャネルで識別される。
PIO
DMAと違い、CPUを使って周辺装置とメインメモリでデータ転送を行うこと。
| DMA | PIO |
|---|---|
| 周辺装置(DMAチャネル番号付与済) | 周辺装置 |
| ↓↑ | ↓↑ |
| DMA | CPU |
| ↓↑ | ↓↑ |
| メインメモリ | メインメモリ |
競合
複数の周辺装置に同じポート番号、IRQ、またはDMAチャネルが設定されていると、競合が起こる。
コンピュータの起動手順
- コンピュータの電源を入れる
- BIOSがハードウェアの正常動作を確認
- 問題なければブートストラップローダーを起動
BIOS
ROMに記憶され、あらかじめコンピュータ本体に内蔵されているプログラム。キーボードやディスク装置などの基本制御プログラムのほか、ブートストラップローダーを起動する機能を持つ。
ブートストラップローダー
起動ドライブの先頭領域に記憶された小さなプログラム(起動ドライブは一般的にハードディスクだが、光ディスクやUSBメモリにもできる)。
ハードディスクなどに記録されたOSをメモリにロードして実行する。OSは自分では起きられないため、ブートストラップローダーが起こす。
OS
Windows
Windowsにおいて、アプリケーションの機能はコンピュータのハードウェアを直接操作しない。 OSに命令を与えることで間接的に実現する。
これによって機種専用アプリケーションを作成する必要がなくなり、WindowsOS専用アプリケーション1つで機種に関係なく共用できるようになった。
注意
Windows自体は機種(≒CPU)専用のものを選ぶ必要がある。なぜなら市販のWindowsアプリケーションは、特定のCPUのネイティブコードの形で提供されているから。
MS-DOS
アプリケーションの機能の中にメモリやI/Oなどハードウェアを直接操作している部分があったため、機種専用アプリケーションが必要だった。
| Windows | MS-DOS | |
|---|---|---|
| アプリケーション | Windows用の単一アプリ | ハードに合わせた専用アプリ |
| OS | ハードに合わせた専用Windows | ハードに合わせた専用MS-DOS |
| ハードウェア | ハード | ハード |
API
アプリケーションからOSへの命令のやり方を定め、関数の形で提供したもの(MessageBox()など)。OSの種類ごとにAPIが異なる。ハードウェアを制御するAPIを呼び出すことをシステムコールという。
- 同じアプリケーションを他の OS で動かしたい
→APIの部分を書き換える必要がある - 同じアプリケーションを、OSは同じだがCPUが違うマシンで動かしたい
→APIはOS間で共通なのでアプリケーションのソースコードはそのまま使える。しかしCPUの種類が異なればマシン語が異なるため、ネイティブコードは共有できない。それぞれのCPUに合わせたネイティブコードを生成するコンパイラを使ってソースコードをコンパイルし直す必要がある。
| OSの進化 | 説明 |
|---|---|
| モニタープログラム | OSの原型。プログラムをロードする機能と実行する機能だけを備えたプログラム。あらかじめこれを起動させておけば、さまざまなプログラムをメモリにロードして実行できる |
| 初期OS | 多くのプログラムに共通する、基本的な入出力機能を持ったモニタープログラム |
| 現在のOS | 初期OSに、ハードウェア制御、言語プロセッサ(アセンブラ、コンパイラ、インタプリタ)、ユーテリティなどの機能が追加されたOS。複数のプログラムの集合体。 OSはCPUに命令を与えてハードウェアを制御する。アプリケーションはOSを介して間接的にハードウェアを制御する。アプリケーションはOSが提供するシステムコール(=API)を使う。個々のAPI関数の実体はDLLファイルに格納されている。 |
WindowsOSの特徴
- 32ビット版と64ビット版
最も効率的に処理できるデータのサイズを意味する。64ビット版は32ビット版のアプリケーションを動作させる機能があるため、現状多くのアプリケーションは32ビット版になっている。 - APIと呼ばれる関数セットでシステムコールを提供
DLLファイルとして提供される。実態はC言語で記述された関数。
Java仮想マシン
Javaのソースコードをコンパイルして生成されるのは、特定のCPUのネイティブコードではなくバイトコードである。そのバイトコードの実行環境をJava仮想マシンという。
Java仮想マシンは、バイトコードを逐次ネイティブコードに変換しながら実行する。例えばWindowsなら、Java仮想マシンはバイトコードをWindowsCPU用のネイティブコードとWindows用のAPIに変換する。さまざまなOSやハードウェアに合わせたJava仮想マシンを用意しておけば、同じバイトコードのアプリケーションがどの環境でも動作する。
プログラム
ストアドプログラム方式
プログラムが記憶装置に格納されていて、順次読み出されて実行される方式。それ以前は、コンピュータの配線を変えることなどでプログラムを変更していた。
プログラムの実行
- 高水準言語でプログラムを記述(ソースコード)
- コンパイラがコンパイルして、オブジェクトファイルを生成
- リンカがリンクして、実行可能ファイル(マシン語のEXEファイル)を生成
- プログラム起動時に、EXEファイルのコピーをメモリ上に作成
- CPUがプログラムの内容を解釈・実行
リンク
複数のオブジェクトファイルを結合して、1つの実行可能ファイルを生成する処理。
リンクされるファイルの例
- オブジェクトファイル
- スタートアップ
- スタティックリンクライブラリから抽出してきたオブジェクトファイル(sprintf()など)
- インポートライブラリから抽出してきたDLLファイルの情報(MessageBox()などを呼び出すための情報)
リンクで作成された実行可能ファイル
実行可能ファイルは、実行時にDLLファイルから関数を呼び出す
コンパイル
以下の変換のこと。
- ソースコード→ネイティブコード
- ソースコード→アセンブリ言語のソースコード
高水準言語は特定のOSに依存しない。OSが違っても同じソースコードを使い、コンパイルされる際に、該当するOSのシステムコールに変換される。言い換えると、高水準言語で記述されたアプリケーションをコンパイルすると、システムコールを利用するネイティブコードになる。
コンパイルに必要な3要素
- なんというプログラミング言語で記述されたソースコードか?
- どの種類のCPUのためのネイティブコードを生成したいか?
- コンパイラを使う動作環境(OS+ハードウェア)は?
ライブラリファイル
複数のオブジェクトファイルをまとめて1つのファイルに格納したもの。
リンカにライブラリファイルを指定すると、その中から必要なオブジェクトファイルだけを抽出し、その他のオブジェクトファイルと結合して実行可能ファイルを生成する。
ライブラリファイルにまとめておくことで、何百個もオブジェクトファイルを指定する必要がなく、まとめて指定できる。
標準関数の内容はソースコードには記述されず、ライブラリファイルに入っている。
インポートライブラリ
ライブラリファイルの一種。オブジェクトファイルの実体を持たない代わりにDLLファイルの情報を持ち、プログラムの実行時にDLLファイルから該当するオブジェクトファイルを抽出し、実行可能ファイルに結合する。
複数のアプリケーションで同じDLLファイルを共有できるため、メモリが節約できる。EXEファイルを変更せずにDLLファイルだけをバージョンアップすることもできる。
Windows APIはDLLファイルに格納されている。
スタティックリンクライブラリ
ライブラリファイルの一種。オブジェクトファイルの実体を持ち、実行可能ファイルに直接結合する。例えば標準関数「sprintf()」など。
スタティックリンクの場合、2つのアプリケーションの実行ファイルの中に1つの同じ関数をそれぞれスタティックリンクし、アプリケーションを同時に実行すると、メモリ上に同じ関数のプログラムが2つ存在することになってしまう。
プログラムがロードされたメモリ領域に作られるもの
- (OSのための領域)
- 変数グループ(実行可能ファイルからコピー)
- 関数グループ(実行可能ファイルからコピー)
- スタック(プログラム実行時に確保。ローカル変数や関数呼び出し時の引数を格納。)
- ヒープ(プログラム実行時に確保。任意のデータを格納。)
| 用語 | 説明 |
|---|---|
| スタック | スタックへのデータの格納と破棄を行うコードは、コンパイラが自動生成する。関数呼び出し時にメモリの領域が確保され、処理終了時に解放される(クリーンアップ処理) |
| ヒープ | 領域の確保と解放は、ソースコードに記述する必要がある。解放しないとメモリリークが起こる |
_stdcall呼び出し
プログラムのサイズを小さくする手法。
C言語において、スタックのクリーンアップ処理は、コンパイラよって関数を呼び出す側(main等)に付け加えられる。しかし何度も呼び出す場合、クリーンアップ処理が回数分だけメモリ上に存在することとなる。そこで_stdcallを置くことで、クリーンアップ処理を呼び出された関数側で行うように変更できる。
なぜC言語において、デフォルトで_stdcallではないのか?
Cが可変長引数の関数に対応していて、呼び出す側でないと引数の個数がわからず、クリーンアップ処理ができないから。
しかしCであっても固定長引数なら_stdcallできる。
実行可能ファイルの中で、メモリアドレスの値はどうやって決まるのか?
仮のメモリアドレスが与えられており、プログラム実行時に実メモリアドレスへと変換される。リンカは、実行可能ファイルの先頭に、メモリアドレスの変換が必要な部分を示す情報を付加(=再配置情報)。
個々の変数のメモリアドレスは、変数グループの先頭を基点としたオフセット(相対位置)で表す。個々の関数のメモリアドレスも、関数グループの先頭を基点としたオフセットで表す。
グループの基点のメモリアドレスはプログラム実行時に決定される。
関数を呼び出す仕組み
- (呼び出す側)スタックに引数を格納
- (呼び出す側)スタックに戻り先のアドレスを格納
- (呼び出される側)スタックに格納された引数を読み出して演算
- (呼び出される側)eaxレジスタに戻り値を格納
- (呼び出される側)スタックに格納された戻り先アドレスを読み出して戻る
- (呼び出す側)スタックの解放
その他知らなかった知識
-
ビット(bit)
データの最小単位 -
バイト(byte)
データの基本単位。メモリやハードディスクにはバイト単位でデータが読み書きされる。ビット単位ではできない。
ファイルはバイトデータの集まり。1バイトで表せるバイトデータは256種類。個々のデータが文字を表しているなら文書ファイル、グラフィックスパターンなら画像ファイル
| 右シフト | 左シフト | |
|---|---|---|
| 論理 | 空いた下位桁に0が入る | 空いた下位桁に0が入る |
| 算術 | 空いた上位桁に1が入る | 空いた下位桁に0が入る |
-
符号拡張
空いている上位桁に符号ビットの値が入る
-
配列
同じデータ型(サイズ)の複数のデータがメモリ内に連続して並んだもの -
リスト
配列の個々の要素に、次の要素のインデックス情報を持たせる。並び替えが楽 -
2分探索木
配列の個々の要素に、左右2つの子のインデックス情報を持たせる。探索が楽
-
K(大文字、ケー)
1024 -
k(小文字、キロ)
1000
-
ランレングス法
データ×繰り返し回数で表す。AAAAAABB → A6B2。
同じデータが続いている場合が多いときに効果を発揮する(ファクシミリの画像圧縮など)。例えば白が○回、黒が○回、また白が○回…などのように表せる。 -
ハフマン法
ハフマン木を用いて作成。出現頻度が多いデータほど少ないビット数で表される。
ハフマン法を応用したのがZIP形式。
-
SaaS
アプリケーション -
PaaS
OS -
IaaS
ハードウェア -
MicrosoftAzure
マイクロソフトが持つ強大なサーバー群
-
動作環境
OS+ハードウェア
-
コンパイラ
実行前にソースコード全体を解釈して処理する(C、C++) -
インタプリタ
実行時に1行ずつ解釈して処理する(Python)
-
アセンブリ言語
低水準言語。電気信号である個々のマシン語命令に、その動作を表す英語の略語(ニモニック)を割り当てたもの。マシン語と1対1対応。
アセンブリ言語のソースコード = ネイティブコードに変換される通常の命令 + アセンブラに対する命令である擬似命令
AT&T記法とインテル記法がある。 -
擬似命令
プログラムの構成やアセンブルの方法をアセンブラに指示するもの
| 用語 | 説明 |
|---|---|
| アセンブル | アセンブリ言語のソースコード→ネイティブコード |
| 逆アセンブル | ネイティブコード→アセンブリ言語のソースコード |
| 用語 | 説明 |
|---|---|
| オペコード | 命令の動作 |
| オペランド | 命令の対象となるデータの値、メモリアドレス、レジスタ名など。オペランドが2つある場合もある |
| 用語 | 説明 |
|---|---|
| 命令を格納するセクション | アセンブリ言語のソースコードのセクション。読み出し専用 |
| データを格納するセクション | アセンブリ言語のソースコードのセクション。読み書き可能 |
-
グローバル変数
プログラムのデータのセクションにあらかじめ付加されている。プログラムの実行中はずっとメモリ上に存在するため、グローバル変数は全ての関数から使える -
ローカル変数
関数呼び出しの際にスタック上に一時的に用意される
-
ネイティブコード
マシン語になっているプログラム。多くの場合、アプリケーションはネイティブコードの形式で提供される(WindowsならEXEやDLL) -
ソースコード
テキストファイルであり、どのような環境でも表示や編集ができる。ソースコードをコンパイルすることでネイティブコードが得られる
-
クロスコンパイラ
動作環境の CPU とは違う CPU 用のネイティブコードを生成する -
分割コンパイル
ソースコードを分割してそれぞれコンパイルして、最後に1つの実行可能ファイルにリンクする
-
EXEファイル
Windowsで実行可能なアプリケーションファイルの拡張子
-
ポインタ
データが格納されているメモリのアドレスを持つ変数 -
リングバッファ
リング状に配置されたバッファ。一時的にデータを貯めておくバッファ領域のうち、終端と先端が論理的に連結され、循環的に利用されるようになっている。最も古い内容を最新の内容で上書きし、常に一定の数の過去までのデータを蓄えるような用途に用いられる
-
ICのピン
直流電圧0Vか1Vのいずれかの状態をとる(=2つの状態しか表せない)
-
浮動小数点数
符号×仮数×基数^指数
| 用語 | 説明 |
|---|---|
| ①符号 | 1ビット |
| ②仮数 | 52ビットか23ビット。小数点以上の値を1に固定する正規表現。 (例)1011.0011→1.0110011 |
| ③基数 | 0ビット(2で固定のため) |
| ④指数 | 11ビットか8ビット。イクセス表現。指数部で表せる範囲の中央の値を0とみなすことにより、符号ビットを使わずにマイナスの値を示す。 (例)01111111(=127)を0とみなす |
(例)0.75 → 0-01111110-10000000000000000000000
(例)0.1 → 0-01111011-10011001100110011001101←こっちは循環する
| 用語 | 説明 |
|---|---|
| 倍精度浮動小数点数型 | 64ビット double |
| 単精度浮動小数点数型 | 32ビット float |
誤差の回避方法
小数点を整数に置き換えて計算する
おわりに
日々の業務とダイレクトに関係があるわけではないのですが、教養として知っておくべき知識がたくさん記載されており、とても勉強になりました。