名前はまだない

とりあえずの思考や学習のアウトプットの場

プログラムが動く仕組みをまとめた

はじめに

プログラムが動く仕組みをまとめた。 Z80エミュレータで実際に手を動かす部分までまとめたフルバージョンは、Qiitaに投稿した。

qiita.com

以下では、コードにかかわらない基本的な知識の説明を転載しておく。 Qiitaとの最適な使い分けってどうするのか謎すぎる。

対象読者

  • パソコンそのものの仕組みに少しでも興味がある人

本編:プログラムが動く仕組み

何も知らない人のために1からざっくりと説明していきます。

機械語とは

プログラムを書いて実行するとき、そのままの言語だと実行されません。基本的にコンピューターは、機械語と呼ばれる数字の列でしか物事を認識できないからです。例えばC言語でプログラムを書いても、実行するためにはコンパイルという翻訳作業が必要になります。その翻訳された言語がアセンブリ言語というものです。アセンブリ言語機械語と1対1対応の英単語が割り当てられています。これをニーモニックと呼びます。流れとしては、

人間のアイデアC言語アセンブリ機械語→CPUが理解、実行する

という感じですね。本稿ではこの機械語を通じてパソコンの動く仕組みを理解しようという試みを行っているわけです。ちなみにPythonなど一部の言語ではコンパイル(翻訳)なしにプログラムが作動します。実はこれは勝手にリアルタイムで命令をコンパイルして実行してくれているからで、このような言語はインタプリタ言語とかインタプリタ型言語と呼ばれたりします。

プログラムが動く仕組み

プログラムに含まれる命令はCPUで解釈され、演算が実行されます。CPUのスペックによって、パソコンのスペックがだいたい決まります。CPUが脳みそに例えられるのはそのためです。ただし、基本的にCPUは命令を解釈し演算を実行する場所であって、記憶をする場所ではありません。CPUが行った演算結果はメモリという場所に保管されます。CPUについては後で詳しく説明しますが、とりあえず今回扱うのは8ビットのパソコンのCPUだということだけ覚えておいてください。

メモリについて

RAMやROMは本稿では触れなくてもいいので、説明を割愛しました。知らない人はまた他の文献等で出てきたときに調べてみてください。

ビット( bit )とバイト( byte )

さて、次に一度は聞いたことのある「ビット」と「バイト」の説明です。

ビット(bit)とはコンピューターが扱う情報を構成する最小単位で、「0」または「1」のどちらかを取ります。「0」か「1」かという形を取る理由は、パソコンの制御に電流が使われているからです。例えば、「電気が流れれば1、流れなければ0」のように、電流のオン/オフという2つの状態に数字を割り当てています。

次にバイトですが、ビットを1つのまとまりにした単位のことで、1バイト=8ビットです。次のアドレスの項目で説明するように、8ビットを1バイトとした方が都合が良かったからそうなってるそうです。詳しく知りたい人はこちらの記事を読んでみてください。

アドレス

先程話したとおり、CPUが行った演算の結果など、様々なデータが保管される場所をメモリと呼びます。メモリにはアドレスという場所を表す情報があり、アドレスを指定してデータの入出力を行います。モノを入れるためのいくつもの箱がズラリと並んでいて、それぞれの箱に位置情報が紐づけされているのを想像するとわかりやすいです。大量に並べられた箱(メモリ)にそれぞれ個別の箱を識別する位置情報(アドレス)がなければ,モノの出し入れを依頼する人(CPU)は思うようにモノの出し入れができず困ってしまいます。アドレスは重要です。

さて、今回扱うZ80のような8ビットのCPUですが、その名の通り一度に8ビットの情報を処理できます。この単位が1バイトと定められました。8ビットのパソコンに搭載されているメモリには、16進法で0000〜FFFFまでの65,536個ものアドレスが存在し、各アドレスには1バイト(=8ビット)の情報が入出力されます。

ちなみに65,536個と聞くとかなり多いように思いますが、

65,536 = 64 * 1024 = 64 * 210

なので、64キロバイト(64Kbyte)ということになります。当時のパソコンのメモリは64Kbyteしかなかったということになります。ちなみに今のメモリはだいたい8GB〜16GBが主流です。Windows2000の電卓アプリの容量が約90Kbyteらしいですので、それと比べてもいかに64Kbyteが小さいかが見て取れます。

2進法と16進法

先程出てきた16進法について話をしておきます。

まず、上述したとおりパソコンは「0」か「1」かで情報を扱っています。一般的にこれを2進法で表された数として解釈します。2進法ついて知らない方はこちらをご覧ください。2進法は英語でbinaryと言います。「バイナリ」とか「バイナリエディタ」という言葉を聞いたことがある人がいると思いますが、それは2進法の形でデータを扱うことに関係する文脈だったはずです。

16進法では0〜Fまでの16個の英数字を使って数値を表します。

2進法 10進法 16進法
0 0 0
1 1 1
10 2 2
11 3 3
100 4 4
101 5 5
110 6 6
111 7 7
1000 8 8
1001 9 9
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F
10000 16 10
10001 17 11
10010 18 12
10011 19 13

例えば8ビットの情報を扱うということは、「0 or 1」のどちらかを取る情報(ビット)が8つあるということで、その組み合わせは28=256通り存在します。つまり、8ビットは00000000〜11111111最大で256個の情報を伝えることができます。この8ビットの情報を一々2進数で表していると、8桁も記載する必要があり面倒です。しかし16進法を使うとスッキリと表せます。256は162なので、16進法を使うと2桁で8ビットの情報のすべての組み合わせを表すことができます。

先ほど紹介したアドレスの総数も65,536 = 164なので、0000〜FFFFの4桁の16進数ですべて表すことができます。

16進数を表す際には末尾にHをつけて16進数であることを明示することがあり、今回扱うアセンブリでもHを末尾につけます。

CPUについて

CPUの部品とはたらき

今回扱う8ビットのパソコンに搭載されているCPU(Central Processing Unit)は、概ね以下の部品で構成されています。今のCPUでも自作など専門的なことをしない限り、だいたい以下で挙げる部品を覚えておけばなんとかなりそうです。

レジスタ

8ビットか16ビットの情報を一時的に保持する場所で、CPU内にはいくつかのレジスタが存在します。レジスタの数など、構成はパソコンによって違います。今回扱うZ80というCPUには、A, F, B, C, D, E, H, L, SP, PCレジスタがあります。そんなものは覚えなくてもいいです。重要なのはAレジスタは特別なレジスタで、アキュームレジスタと呼ばれるということです。Aレジスタには、唯一演算機能が搭載されており、加減演算、論理演算、比較などが可能です。今回使うのはこのAレジスタだけです。Fレジスタはフラグ・レジスタと呼ばれ、様々な状態(正か負かなど)を保存する場所だとか、SP(スタック・ポインタ)レジスタに一時的な演算結果を保持するだとか、PC(プログラムカウンタ)レジスタにアドレスを保持して様々な条件処理が可能になるだとか、知っておいたほうが良いことはたくさんあります。どれも重要な役割がありますが、今回は使用しないので割愛します。CPUについての説明は探せばいくらでもあるので、知らない人は各自調べてみてください。

バス

CPUとメモリなど、CPUと他の部分とをつなぐ線のことをバス(Bus)と呼びます。ファイルの場所を指すパス(Path)ではなくバス(Bus)です。データを載せる乗り物をイメージすれば良いと思います。バスにはアドレスバスとデータバスが存在します。アドレスバスでは操作するメモリのアドレスの送信を、データバスでは入出力するデータの送受信を行います。

以下の図を使ってCPUがメモリの値を書き換えるまでの過程を見ていきましょう。

f:id:yuuy_blog:20181120183647j:plain

  1. CPUからメモリに、アドレスバスを通じて操作したいメモリのアドレスが送信される
  2. 指定されたアドレスのメモリが選択される
  3. CPUからメモリに、データバスを通じて書き換えたい値が送信される
  4. 指定されたアドレスのメモリの値が書き換えられる

ざっくりとこんな手順でメモリの操作がされています。(簡単のためコントロール信号など詳しい説明は省いています)アドレスバスはCPU→メモリの一方通行ですが、データバスはCPUとメモリを双方向で繋いでいます。なので書き込みだけではなく読み込みも可能なわけです。

ここで復習ですが、1ビットは「1か0か」という2値で表されましたよね。また、メモリのアドレスは65,536個存在しましたよね。65,536=216であることから、メモリのアドレスは16ビットの情報と言えます。16ビットの情報ということは「0か1か」の情報を16個渡せば良いわけです。アドレスバスは、A0〜A15までの16本の線にそれぞれ電流を流すことによって、16ビットであるアドレスの情報をメモリに伝えています。

上図を例に説明すると、伝えたいアドレス情報2FA3は16進数なので、まずは2進数に変換してあげます。すると0010111110100011という値が得られるので、これをもとにアドレスバスに電気信号を送ります。右端の値がA0で左端の値がA15に対応するとしたら、A0〜A1, A5, A7〜11, A13に電気信号を流すことで、2FA3というアドレス情報をメモリに伝えることができます。

同様にして、データバスでもデータのやり取りを行っています。今回扱っているCPUは8ビットのパソコンのものだということを思い出してください。一度に8ビットの情報まで扱うことができるCPUなので、データのやり取りに必要なバスの線の数も8本です。上図の例では78=01001110なので、それに対応するデータバスの線に電気信号が流されデータの送信が行われます。

I/Oポートについて

モニターやキーボードなど、すべての入出力装置はI/Oポートと呼ばれる場所で制御されています。I/Oポートにある入出力装置にはそれぞれアドレスが振り分けられており、メモリと同じようにアドレスを指定して指示を出すことで様々な挙動を実現しています。ここでは深く触れませんが、上で説明したメモリ操作と同じ具合に、アドレスバスでアドレスを指定し、データバスでデータのやり取りを行っています。Z80ではI/Oポートのアドレスを8ビットで表し、00〜FFまでの値を振り分けています。

おわりに

Qiitaとの使い分けが謎すぎる。 とりあえずコードが多いのはQiitaに載せて、こっちはそれ以外にしようかと思っている。