CPUが動くまで
CPU(Central Processing Unit)は、プロセッサとも呼ばれ、コンピュータの頭脳とかよく称される。要するにコンピュータが動いてるのはこの子のおかげ。
じゃあ、コンピュータとは何か?
→ 演算機である。コンピュータは入力に対して基本的な算術演算や論理演算をものすごい速さで何回も繰り返すことで何かを出力するものである。
演算を行うにあたり、内部的には全ての数値を0(False)か1(True)で表現している。
この0と1は電圧で区別され、それぞれ0Vと+5Vに対応している。
各種演算は論理回路で表現され、プログラムとはこの演算をどの順に行うかを示した命令でしかない。
ここではコンピュータの演算機能の中枢になるCPUが動くまでを深掘りしていく。
安定した電圧を手に入れる
データや演算を電圧で表現している以上、安定して正確な電圧を与える装置が必要になる。
黎明期のコンピュータENIACでは真空管を利用していた。
真空管に熱を加えることで透過する電子の量を制限することで一定の電圧を作り出していた。
しかし、真空管は熱を必要とするため消費電力が大きく破損しやすかった。
トランジスタ
のちにトランジスタが発明され、消費電力や破損しにくさの面で優れたため代用されていった。
まずはこのトランジスタについて見ていく。
トランジスタとは、transfer(伝達) + resister (抵抗) から作られた言葉であり、以下のような機能を持っている。
- 信号の増幅
- 回路のオンオフ(スイッチのような働き)
作りとしては、二種類の半導体をうまく繋ぎ合わせて特定の条件に置いた時のみ電気を通すようにしたもの。
ユニポーラ型トランジスタ(電界効果型(Feild Effect)トランジスタ:FET)
MOS型と接合型の2種類がある。「電荷蓄積効果」がないため高速なスイッチが可能。
MOS型ではG電極に正電圧をかけるとチャネル部に電子がP型半導体から誘起されてS->Dへ電子の流れが生まれる。
これにより、電気をかける(=特定の条件に置く)と電気を通すことができる。
なお、図中のN
、P
はそれぞれ以下の半導体を繋いだもの。
- N (negative)型半導体 : リンのように電子が豊富な不純物が含まれるもの。
- P (positive)型半導体 : ホウ素のような電子の少ない不純物が入ったもの。
ユニポーラ型トランジスタ(電界効果型(Feild Effect)トランジスタ:FET)
挟み込む順によってNPN型とPNP型がある。
C > B > E となるように電圧をかけることで、E → Bへ流れた電子が薄いBを突き抜けてC側にトラップされるようにする。
また、Bに与える電圧によって流す電圧の大きさを変えられることから増幅の用途でも利用される。
参考
- https://article.murata.com/ja-jp/article/what-is-transistor
- https://gigazine.net/news/20160607-how-transistor-work/
- https://sikakuisankaku.hatenablog.acom/entry/2017/12/02/002858
- https://maicommon.ciao.jp/ss/Hardware/ANcircuit/semiCon/BiTran/Circuit40.htm
- https://www.tel.co.jp/museum/exhibition/principle/semiconductor.html
- https://engineer-education.com/transistor-basic/
論理回路を作る
トランジスタの仕組みがわかり、安定した電圧を手に入れたところで論理回路をつくる。
安定した電圧をかけてもCPUでの演算の際に期待するように0V/5Vを切り替えられないと演算にならない。
ここでは、トランジスタが論理回路を構成することで任意の演算ができるようにする。
基本のゲート (NOT / NAND)
参考、画像の引用元)
https://sikakuisankaku.hatenablog.acom/entry/2017/12/02/002858
その他のゲート
その他のゲートはいずれもNOTゲート、NANDゲートから作ることができる。
画像の引用元)
http://www.crl.nitech.ac.jp/~ida/education/computer/project/computer020604.html
ここまでで基本的な演算に必要な NOT / NAND / AND / OR / XOR ゲートを作れた。
これらのゲートを組み合わせることで簡単な演算が実現できる。
算術演算をするのはALUの機能なのでのちに触れることとして、まずは各種ゲートを使って任意の入力のL/Hを切り替えできるようになった。
データを記憶する
ただし、これだと1回の変換ができるだけで過去の演算結果などのデータを保持できない。
ORゲートの特性
ORゲートの出力を一方の入力につなぐとHの出力を記憶できる。ただし上書きができないなどの不便さが残る。
フリップフロップ
そこで、出力をいい感じに入力につなぎかえて値(0/1)を記憶できるようにしたのがフリップフロップ(以下はD-FF)。もちろんこの中身も既存の論理回路(更に言うとこれもNANDゲートだけ)で構成できるもの。
C(CLOCK)の入力が、
- Lの場合は直前の出力を維持し、
- Hの場合はDの入力が通される。
電力が維持される限りの値を記憶し続けるため、SRAM (Static Random Access Memory)としてキャッシュメモリ等に利用される。
※ 反対に、DRAM(Dynamic Random Access Memory)は、記憶素子に内容を記録してから時間が経つと次第に内容が失われていくため、一定時間ごとに同じ内容を記録し直すリフレッシュ動作が必要で、稼働中は大きく電力を消費する。代わりに集積度が高く比較的安価なため主記憶装置に利用される。
このように、演算結果や実行する命令(のアドレス)を記憶しておく装置としてFFが使われており、FFがCPUそのものと言っても過言ではない。
参考
- https://medium-company.com/%E5%9F%BA%E6%9C%AC%E6%83%85%E5%A0%B1-%E3%83%A1%E3%83%A2%E3%83%AA%E3%81%AE%E7%A8%AE%E9%A1%9E/
- http://kogures.com/hitoshi/webtext/hs-cpu-x/index.html
データを操作する
一番簡単な命令
変数Aに与えられた値(0/1)がFFの入力Dに入り、クロックから信号が入ると出力Qからその値が出力される。
出力値が変数Aに書き込まれるようにしておくと、これは MOV A A という変数AからAへの転送命令にあたる。
これはまさしく、movという転送の命令セットを持つ1bitCPUと言える。
さらに、ここにNOTゲートを噛ませると、反転命令も行える1bitCPUになる。
CPUは常に代入と演算を繰り返す装置であり、クロックが立ち上がる際には必ず何かを転送する。何もしないはできない。
この値を記憶しておくための記憶素子がレジスタ
であり、CPUの内部にあって演算や実行状態を一時的に保存しておくために使われている。GPUなどの特殊なプロセッサでは数万個内蔵しているものもある。
ここまでの絵から分かる通り、D-FF自体がレジスタ(=値を記憶する物)として機能している。
MOV命令でレジスタ間でデータを移動する
このままだと同じレジスタ内でしか操作できないので任意のレジスタ間で値をMOVできるようにする。
ここではマルチプレクサ
という新しいやつが出てくる。いわゆる切り替えのスイッチであり、選択制御Sへの入力が1なら入力Aが0なら入力Bが出力されるといったもの。
画像の引用)
https://isle3hw.kuis.kyoto-u.ac.jp/primitive_circuits/index.html
選択制御Sのbit表現できる個数までであれば入力はいくつあってもOK
ここまでで、任意の個数のレジスタからコピー元のレジスタを1つ選択できるようになった。
例えば、レジスタAへの書き込みをA〜Dのどれかを選択する回路はこんな感じ。
ただし、当然これはAの値をそのまま出力に回すか、コピー元の方(B〜D)から取ってきた値を出力に回すかをSで制御するだけで、このままだとレジスタAにしか制御できないので全レジスタの書き込みの直前に配置する必要がある。
画像の引用)
書籍 「CPUの創り方」
https://edu.isc.chubu.ac.jp/naga/dec/dec4.html
→ コピー先のMUXのみスイッチを切り替えて値を維持するだけの回路から書き込み回路に回せばいい。
→ また、選択制御の信号に対してスイッチをつける。ここでつけるスイッチは”デマルチプレクサ”であり、MUXの逆。入力を任意の出力先に振り分けることができる。
制御選択こそが命令
これらの選択制御にどのような信号を送るか = これが命令のオペランドの指定に相当する。
例えば0001
の前半がコピー元、後半がコピー先に対応させるCPUであると仮定した場合、00
の指し示すレジスタA
から01
の指し示す
レジスタB
への転送を実現できる。(当然00
や01
がどのレジスタに対応するかもCPUの決め事。)
このように1bitフリップフロップを並列に並べることで複数のレジスタに対応できる。
例えば複数桁の値を信号で扱う場合にこれは有用となる。
なお、PCの32ビットや64ビットとはこの並列のレジスタの個数を指している。
float型は32bitであることから、1度の演算の際に32個のフリップフロップによるレジスタを使用していると想像できる。更にその中でも最初1bitが符号部
、次の8bitが指数部、次の23bitが仮数部と割り振られている。
floatの詳細は以下
https://sikakuisankaku.hatenablog.com/entry/2017/12/06/211236
四則演算の命令
算術演算や論理演算を行う装置を1つ切り出してALU(Artithmetic Logic Unit)というらしい。
ALUは2つの入力から適切に演算をした結果を出力するものとすると、
任意のレジスタから入力となる変数を選択し(①)、
命令に組み込まれたデータ(Immediate Data)との
演算結果を返し、その書き込み先の変数を選択する(②)
ことでALUに組み込んだ演算に対応させることができる。
ここで、複数の演算から適応させたい演算を自由に切り替えるにはどうするかを考える。
→ 同じく、ALUの内部にも回路をMUXで分岐できるようにしておけばOK。演算1,2…などは、加算や減算など任意の各種演算に対応する。
基本的な演算を実現する回路
算術加算
画像の引用)
http://www.crl.nitech.ac.jp/~ida/education/computer/project/computer020801.html
http://www.crl.nitech.ac.jp/~ida/education/computer/project/computer020802.html
(1) 1bitの演算を考える。1 + 1 = 2 までに対応。 (= 半加算器)
2つのオペランド A と B を加算しその結果を返す。
ここでAもBも入力は0か1なので共に1の入力を受けた際に2を返すために繰り上がりを示す出力が必要になる。
(2) 繰り上がりを考える。(= 全加算器)
→ 1bitの演算を末尾の桁から繰り返したらいい。繰り返しが存在するならその入力を次の桁の計算に組み込めばいい。
つまり、入力は3つ欲しい。2つの数値(A,B)と前の桁の繰り上がり(C)。なお、3つの足し算は2つの足し算を2回して、どちらかで繰り上がりがあれば繰り上がりを出力したらいい。
(3) 任意のbit長の演算を考える。
直前の桁の繰り上がりを次の桁の演算に回せばいい。
算術減算
減算はマイナスの加算と考えることで同じく全加算機を使い回しできる。
マイナスをどう扱うか?
→ ここではマイナスの数を2の補数として考える。※ 符号+絶対値方式で表現したものに2の補数は使えない。
4bitの世界において、0001 + 1111 = 0000 となる。つまり5bit目は消える。
これは負の数-Nは -N + N = 0 になる性質から -0001 = 1111 と捉えることができる。
これは、0001をビット反転して(1110)、1を加算する(1111)ことで期待通りの値とすることができる。
すなわち、A - B = A + (-B) = A + B(反転) + 1 と計算できる。
B(反転)はNOTゲートで、+1はデフォルトでキャリーフラグを1にすることで全加算とすることができる。
ALUにMOV命令を持たせる
当初の回路だと単純なMOVができなくなっている。
書籍「CPUの創り方」ではIm Dataを0として加算することで実質MOVにしているが、単純な移動にも演算が入るため消費電力の面からALU内に素通りするパスを組み込む設計もあるらしい。
参考
- https://e-words.jp/w/%E3%83%AC%E3%82%B8%E3%82%B9%E3%82%BF.html
- https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%AB%E3%83%81%E3%83%97%E3%83%AC%E3%82%AF%E3%82%B5
- https://edu.isc.chubu.ac.jp/naga/dec/dec4.html
- https://e-words.jp/w/ALU.html
- http://www.crl.nitech.ac.jp/~ida/education/computer/project/computer020801.html
- http://www.crl.nitech.ac.jp/~ida/education/computer/project/computer020802.html
- http://www.cc.kyoto-su.ac.jp/~kbys/kiso/number/int-op.html
制御
ここまでで、任意の演算をクロックのタイミングで実施し、記憶することに成功した。しかし、どの演算をするかの制御は手動でH/Lの電圧調整をしなければ動作しない。
例えば、A+BをCに格納するなどの手続き(プログラム)をしたい時に、A = A + BのためのADD制御をして、C = AのMOV制御をするというのを手動でやるのは嬉しくない。
→ クロックの立ち上がりと共に、どこか(メモリ)からプログラム
を取ってきて、あらかじめ決めた順に実行できれば助かる。
プログラム
冒頭に記載した通り、
各種演算は論理回路で表現され、プログラムとはこの演算をどの順に行うかを示した命令でしかない。
プログラムはこの演算をどの順に行うかを示した命令群になる。
なお、プログラムカウンタは、要するに単なる1つのレジスタで、プログラムの位置を格納しておく専用の箱になる。
命令を1個実行する(回路が1周する)たびに加算していけばいいので難しいものではない。
命令を読み込む
データを読み込む場合、アドレスバスでメモリの番地を指定し、データバスを通して出力されるIm Data
をデコーダで読み出し、ALUに流す。書き込む際は汎用レジスタからそのまま出力する。
なお、デコーダの役割はメモリから読み出した01の並びを命令とIm Data
として解釈するもの。
図の引用元)
https://sikakuisankaku.hatenablog.com/entry/2017/12/07/225632
ROMから読み込む
DRAMではなくROMから読み込む場合を考える。
つまり、PC上で後から自作したプログラムなどれはなく、PCが最初から焼き込まれているプログラムを起動時にどう読むのかを考える。
実際、BIOSなどの消えては困るプログラムは不揮発性のROMに書かれている。
※ ROM(Read Only Memory)は書き換え不可であることは要件ではなく、不揮発であることが重要。しかしPCの生まれた当時は不揮発メモリはROMしか存在しなかったためROMが用いられた。歴史的背景から今でも不揮発メモリはROMと呼ばれている。現在ではROMはリードオンリーではなく書き込みできるものもある。
ROMに求められる機能として、CPUが一度に読み取るビット数ごとにデータの読み込みに対応することがある。
eg.) インテルのPentium4では64bitが一度に取得する単位。
図の引用元)
https://eetimes.itmedia.co.jp/ee/articles/2207/12/news038_2.html
メモリにさえ持ってこれればあとは一緒。
参考
コンピュータが立ち上がるまで
ここまではコンピュータをCPUというレベルでこの子が仕事をする流れを見てきたが、ここからはもう少し大きな括りで見てみる。
コンピュータをここではOSとして認識することにし、OSが立ち上がるまでを調べてみる。
OSとは?
そもそもOSとは何か。LinuxやらWindowsやらいろいろあるが、そもそもOS(Operating System)とは何か?
→ ハードウェア(H/W)の制御(=抽象化)やさまざまプログラムのインターフェースを提供しユーザがよりコンピュータを使いやすくなるよう環境を整えてくれているやつ。
これまででCPUがクロックに合わせて演算できることはわかったし、記憶した媒体からプログラムを読み解いてくれることはわかったけれど、そもそもプログラムをどうDRAMに保存したり、CPUの演算結果を出力するのか(=描画しかり他のデバイスへの書き込み)は分かってないし、多分CPUの責務じゃなさそう。
誰かがここを面倒見てくれないと手動でやらないといけなくなって、それはそれは面倒そう。
他にもLinuxがマルチタスク(=同時に複数のアプリケーションが動かせること)、マルチユーザ(=複数ユーザ(ターミナル)から同時に操作できること)をサポートするものとするなら、だれかがメモリから読み込んでる時にうっかり自分が書き込んでしまうとかのトラブルが発生しかねない。
ここを一括で面倒を見てくれる人がいた方がいいよね、ということでOSが役目を担ってくれており、H/Wの制御やかなり低レベルな処理を呼び出すIFを提供してくれている。
カーネルとは?
OSはより細かくはカーネル(コアとなるプログラム)+それをサポートするプログラムで構成されている。
上記の面倒を見てくれるお世話係がカーネルとするならその人に話しかけるための手段(コマンド実行)などを提供しているのが周辺のサポートするプログラム群になる。
BIOSを起動する。
BIOSとは?
Basic Input/Output Systemの略称。通電後、まず初めに動くのはBIOSプログラムになる。BIOSはマザーボード上のROMに保存されたファームウェア(※H/Wを制御するソフトウェア)であり、H/Wの起動からOS起動までの橋渡しをするものになる。
BIOSが起動後、まずデバイス(キーボードやマウスのほか、記憶装置なども含まれる)の初期化(一連の診断テスト)が行なわれる。これをPOST(Power On self Test)という。POSTでは最初にビデオカードが初期化されるのでこれが終了するとBIOSの初期化情報(POSTの診断結果)を表示する画面が出てくる。(よくラズパイとかで[ OK ]みたいに出てるやつはこれだったのか)
マザーボードには以下の4つのIFがあり、IDE接続(※コンピュータとHDDや光学ドライブを接続する規格)で物理ドライブ(FDDやHDD、SSDなど)を接続できる。それぞれ次のデバイスファイル紐づけられる。
- プライマリチャネルのマスタ -> hda
- プライマリチャネルのスレーブ -> hdb
- セカンダリチャネルのマスタ -> hdc
- セカンダリチャネルのスレーブ -> hdd
これらはfdisk
コマンドで確認ができるらしい。
BIOSはこの順に物理ドライブを認識し、どれを起動ドライブにするかが選択される順番になる。(最近ではBIOSの設定画面からのBoot Sequence
としてその読み込み順を変更できる。)
OSを起動する。
BIOSはディスクの先頭セクタ512BytesにあるMBR(マスターブートレコード)を読み込み、ディスクのパーティション情報やブートストラップ(一次ブートローダ)を認識、メモリ(RAM)上にロードする(=書き込む)。このブートローダを呼び出すことでOSが起動する(=RAM上に書き込まれたOSをCPUが実行できる)。ミドルウェアやアプリケーションの検出や設定次第ではそれらの起動が行なわれる。(=起動後すぐにWifi接続できるのとかはこの制御のおかげ)
なお、MBRは2TiB以上のパーティションを読み込めない。この問題を解決するためにGPT方式が作られ、UEFIはこの規格にも対応している。
一次ブートローダ
メモリに読み込まれたタイミングでパーティションテーブルから起動可能フラグのあるパーティションを探し、その先頭セクタ(ブートセクタ/PBR(パーティションブートレコード))を読み込み、二次ブートローダ(LILOやGRUB)を呼び出す。
二次ブートローダ
IPL(Internal Program Loader)と呼ばれるプログラムコードでOS起動の残りのブートローダの残りの機能を呼び出すもの。
同じく512Bytesしかなく、IPLはその中のさらに小さい領域にしか置けないので置き切れない分はPBRの後続セクタに配置され、二次ブートローダからそれを読み込むようになっている。
- LILO (Linux Loader) : レガシーなブートローダ。カーネルがどこに保存されているかの位置情報をHDDの物理的な位置情報で内部に保持している(=ブロックリスト参照型) <= ファイルシステムを認識しないため。
- GRUB (Grand Unified Bootloader) : 高性能なブートローダ。ファイルシステムを認識するためカーネル位置を相対的なファイルパスで指定できる。
参考
- http://www.cs.reitaku-u.ac.jp/infosci/os-kiso/os2003/text02-1.pdf
- http://nobusan.jp/computer/boot/boot.html
コンピュータが動くまで
ここではプロセスが立ち上がるまでを見てみる。
プロセスとはCPUやメモリなどのリソースを消費しOS上で動いているプログラムのこと。
1つのCPU(1コア)上で一度に動かせるのは1プロセスだけなので、複数のプロセスを動かすマルチプロセスを実現するにはCPUを使うプロセスを少しずつ交代させながら譲り合って使う必要がある。それぞれのプロセスがどれくらいの時間CPUを使ったら交代させるのか、どの順にプロセスを実行するのかを管理するのはOSであり、交代する時間をタイムスライス、交代をコンテキストスイッチと言っている。コンテキストスイッチの際にはレジスタの値など各プロセス固有の値をメモリ内の各プロセス固有の領域に退避させる。
デバイスの初期化
カーネル組み込み若しくはモジュールとしてロードしたデバイスドライバを用いてデバイスの初期化を行なう。特に通電後の動作が不定なデバイスにおいては正しく初期設定を割り当ててあげる必要がある。
initプロセス(PID1)の起動
全てのプロセスの親となるinitプロセス。
これは設定されたランレベルに応じてどのプログラムを実行するかが決まっており、/etc/inittab
ファイルで定義される。
[Ctrl]+[Alt]+[Delete]が押された場合の処理もここで定義されている。
# デフォルトランレベル(ランレベル3を指定)
id:3:initdefault:
# ブート時の処理(/etc/rc.d/rc.sysinitを実行)
si::sysinit:/etc/rc.d/rc.sysinit
# ランレベルごとの処理(各ランレベル用のrcスクリプトを実行し、その終了を待つ)
l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
l2:2:wait:/etc/rc.d/rc 2
l3:3:wait:/etc/rc.d/rc 3
l4:4:wait:/etc/rc.d/rc 4
l5:5:wait:/etc/rc.d/rc 5
l6:6:wait:/etc/rc.d/rc 6
# 1度だけ実行される処理(/sbin/updateを実行)
ud::once:/sbin/update
# [Ctrl]+[Alt]+[Delete]キーを押したときの処理
ca::ctrlaltdel:/sbin/shutdown -t3 -r now
# 電源オフ時の処理
pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
# 電源オン時の処理
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
# 端末制御(ランレベル2~5で/sbin/mingettyを実行。終了されると再実行)
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
# ランレベル5時のログイン処理(/etc/X11/prefdmを実行。終了されると再実行)
x:5:respawn:/etc/X11/prefdm -nodaemon
inittabの引用元)
https://atmarkit.itmedia.co.jp/ait/articles/0204/02/news002.html
その他ブート時の処理、rcスクリプト実行
上記のinittab
に定義されるように適切なrc
スクリプトが選択されて実行される。rcスクリプト自体は上記にあるように/etc/rc.d/
に置かれたスクリプトでしかない。