ハードウェア、ソフトウェア基礎
これからプログラミングの世界に足を踏み入れる方々にとって、ハードウェアとソフトウェアの関係を理解することは非常に重要だ。
ものごとには直観的にわかるものがある。その中には、自然に見られるものもあれば、車輪やピザのように人間が発明したものもある。
一方、直観的にわからず、ただ信じるしかないものもある。たとえば、テレビは空中でくねくねしながら飛んでくる見えないものをどのようにして音声と動画に変換するのだろうか。
コンピュータも、簡単に納得できないものの1つである。キーボードを叩いたらマシンが望みどおりのことをするのはどういう仕組みなのか。
一部の本では、コンピュータのメモリを図書館の書棚の本というたとえ話で説明する。このたとえ話通りであれば、図書館から本を取り出せば、本は一時的になくなるわけで、メモリから読み出しをすればその部分はメモリから消えてしまうのではないだろうか。しかし、実際に行うのは、本を持っていくのではなく、本のコピーを持っていくようなことだ。
この記事では、プログラミングを始めたばかりの人々のために、コンピュータのハードウェアとソフトウェアのことを簡単に説明したいと思う。さまざまなことが最終的には「当たり前」のことになるように説明したいと思うが、最初はひっかかるところがあるかもしれない。
ハードウェア
原始人のコンピュータ
原始時代、狩りから返ってきたオグとソグは、殺したマンモスの肉を積み上げた山に新たな肉の塊を加えることになるだろう。しかし、片方がもう片方よりも目立って大きいときに自慢できるという以外、その肉の山についてできることはない。
オグから何世代も経た子孫は、数を数え、数字を書き、そろばんを使えるかもしれない(ソグはある日肉の山を大きくしようとして、逆にマンモスに踏み潰されて子孫を残していない)。しかし、これらの道具を使っている状態を乗り越えてコンピュータの概念を生み出すためには、想像力とテクノロジの飛躍が必要だ。まず最初に必要なテクノロジは電気である。
電気
ベンジャミン・フランクリンは、電気とは何らかの見えない液体が、たくさんあるところ(プラス)からあまりないところ(マイナス)に流れていくことだと考えた。彼の考えは基本的に正しかったが、流れの向きは逆だった。電子は彼が言うマイナスからプラスに流れる。しかし、電子が発見されたのはずっとあとのことであり、あまりにも離れていたので用語を逆にすることができなかった。そのため、私たちは電子は電流とは逆の方向に流れると覚えなければならなくなった。
静電気や雷といった自然界の電気的な現象のことは古くからよく知られていたが、電線に電子を送り込む方法を覚えてからは、回路を作れるようになった。コンピュータを作るところに向かって一歩前進したのである。
以前の私は、軌道を回転するうちに電子が目を回して電流が生まれるのだと思っていたが、実際にはかなり違う。電子はある原子から別の原子へと跳び移っていくのである。チューブの中のボールベアリング(あるいはタピオカミルクティーのストローの中のタピオカ)にちょっと似ている。片方の端からボールを押し込むと、近所のボールが押されて前に動くことが連鎖的に起きて、反対側の端のボールが押し出される。平均的な電子はゆっくり動くが(電線内のドリフト速度は時速7.5cmほどである)、このほとんど同時に発生する衝突によって、電磁波が非常に高速に伝わるようになる。導体によって異なるが、その速度は光速の50%から99%になる。
発明
コンピュータを作るためにはまだ次のものが必要だ。
- ものを記憶する手段
- 覚えたもので何かをする手段
メモリの1つの考え方はスイッチである。オンかオフの状態になり、何かが反転させるまでその状態を保つものがあればよい。電気スイッチは、回路を開いたり閉じたりすることによって電流を流したり止めたりする。私たちは電灯などの電気機器をつけたり消したりするためにスイッチを使う。必要だったのは、スイッチ自身が電流を制御する手段である。
初期のコンピュータ(およびテレビ)は、この目的のために真空管を使っていたが、真空管は大きく、いずれ切れてしまう。現代のコンピュータを生み出すためにもっとも重要な発明は、ひとえにトランジスタだ。真空管よりも小さく、電気効率がよく、信頼性が高い。最後の重要なステップは、トランジスタを大幅に小型化し、集積回路の中でつなぎ合わせることだった。長年にわたって、コンピュータはどんどん小さくなり、それにともなって高速になり、大幅に安くなってきた。信号は、コンポーネント同士の距離が近くなればなるほど高速に伝わるようになる。
しかし、コンポーネントをどれだけ狭い場所に押し込めるかには限界がある。活発な電子は抵抗に遭い、そのために熱を発生させる。この限界には10年以上前に到達してしまい、今のメーカーは同じボードに複数の「チップ」を並べる競争をしている。すぐあとで説明する分散コンピューティングの需要が高くなったのはそのためだ。
こういった細かいことを無視すれば、ものごとを記憶し、その記憶を使って何かができるコンピュータという機械を作れるようになったのは、これらの発明のためである。
理想化されたコンピュータ
現実のコンピュータには複雑な要素が無数に含まれるが、本質的な部分だけを説明する。
サーキットボードには、CPU、メモリ、それらをつなぐ電線、外部装置を接続するためのプラグが含まれる。
CPU
CPU(中央処理装置)はチップとも呼ばれ、実際の「計算」を行う。
- 加算などの数学的な処理
- 値の比較
メモリとキャッシュ
RAM(ランダムアクセスメモリ)が「記憶」を行う。RAMは高速だが、揮発性であり、電源が失われるとデータも失われる。
発展の過程でCPUはメモリよりも高速になったので、コンピュータの設計者たちはCPUと主記憶(メインメモリ)の間に小さくて高速なキャッシュと呼ばれるメモリを追加した。CPUは、メモリから何らかのバイト情報を読み出すときに、まずもっとも近いキャッシュ(L1キャッシュと呼ばれる)にその情報がないかどうかを調べ、次にその先のキャッシュ(L2キャッシュ)を試し、最後に主記憶にアクセスする。
ストレージ
主記憶はデータを失ってしまうので、非揮発性のストレージも必要になる。一部のストレージデバイスはメモリよりも低コストでかなり多くのデータを保持できるが、転送速度もかなり遅くなる。
古くから使われるストレージ装置は、可動式の読み書きヘッドを持つ磁気ディスクである。昔の針を落とすビニールレコードと少し似ている。
その後、RAMのような半導体で作られていながら、磁気ディスクのように非揮発性のSSD(ソリッドステートドライブ)というハイブリッド技術が生まれた。SSDは価格、スピードとも、両者の間を取ったものになっている。
入力
人間が使う主要な手段はキーボード、マウス、タッチスクリーンになる。
出力
一般にディスプレイやプリンタを使ってコンピュータの出力を見る。
相対アクセス時間
これらのコンポーネントとの間でデータをやり取りするためにかかる時間には、大きなばらつきがある。実用上、このばらつきには大きな意味がある。
たとえば、ソフトウェアはメモリ内で実行され、メモリ内のデータにアクセスしなければならないが、ディスクなどの非揮発性デバイスに安全にデータを格納する必要もある。
問題は、ディスクがメモリの数千倍も遅ことだ。
プログラマは、スピードとコストの間でうまくバランスを取るための手法を常に考えている。
相対アクセス時間
場所 | 時間 | 割合 | 相対時間 | 相対距離 |
CPU | 0.4ナノ秒 | 1 | 1秒 | 1cm |
L1キャッシュ | 0.9ナノ秒 | 2 | 2秒 | 2cm |
L2キャッシュ | 2.8ナノ秒 | 7 | 7秒 | 7cm |
L3キャッシュ | 28ナノ秒 | 70 | 1分 | 70cm |
RAM | 100ナノ秒 | 250 | 4分 | 2.5m |
SSD | 100マイクロ秒 | 250,000 | 3日 | 2.5km |
磁気ディスク | 10ミリ秒 | 25,000,000 | 9か月 | 250km |
インターネット:
サンフランシスコ→ニューヨーク |
65ミリ秒 | 162,500,000 | 5年 | 16,250km |
CPUの命令の実行に1秒ではなく、1ナノ秒以下の時間しかかからないのはすばらしいことで、ディスクとネットワークは、CPUやRAMと比べて極端に遅いので、できる限り多くの仕事をメモリ内で済ませるとよい。そして、CPUはRAMよりもかなり高速なので、データを連続的にして、CPUに近い高速のキャッシュからデータを供給できるようにすることが望ましい。
ソフトウェア
このようなコンピュータのハードウェアをどのように制御すればよいのだろうか。まず、命令とデータがある。
ストアドプログラムコンピュータは、すべてのものをデータとして扱える分、設計が単純になる。しかし、命令とデータをどのように表現したらよいのだろうか。また、ある場所では保存でき、別の場所では処理できるものとは一体何なのだろうか。
最初はビット
2つの値の中のどちらかを覚えているスイッチという概念に戻ろう。スイッチはオンかオフ、高圧か低圧、正か負など、設定した状態になり、それを忘れず、あとで尋ねられたら自分の値を教えられる。集積回路は、小さなチップに数十億個の小さなスイッチを押し込み、接続できるようにした。
スイッチが2種類の値だけを持つことができるなら、それはビットつまり2進法の1桁の数字を表現できる。ビットは、整数の0と1、イエスとノー、真と偽といったものとして扱える。
しかし、ビットでは小さすぎて0と1を越えるものは表現できない。どうすればビットでもっと大きなものを表現できるのだろうか。
指を見れば答えはわかる。日常生活では10種類の数字(0から9)しか使わないが、位取り記数法を使って9よりもずっと大きな数を表現できる。数38に1を加えると、8が9になり、値は39になる。この数にさらに1を加えると、9は0になり、左の桁が1繰り上がって3が4になって、最終的に40になる。右端の数値は「1の位」、その左は「10の位」となる。同様に右の桁を10倍したものが左の桁になっていく。3桁の10進数は、10×10×10で000から999までの1,000種類の数を表現できる。
位取り記数法を使えば、ビットのコレクションで大きなものを表現できる。バイトは8ビットで、28(256)種類のビットの組み合わせを表現できる。バイトを使えば、たとえば0から255までの小さな整数を表現できる(位取り記数法では、0のためのスペースを残しておく必要がある)。
1バイトは、8個のビットを並べたもののようになる。各ビットは0(オフ、偽)か1(オン、真)の値を持つ。右端のビットは最下位ビット、左端のビットは最上位ビットである。
機械語
コンピュータの個々のCPUは、理解できるビットパターン(オペコードとも呼ばれる)を集めた命令セットを持つ。個々のオペコードは、ある位置の入力値を使って決まった機能を実行し、(別の)ある位置に値を出力する。CPUは、こういったオペコードやデータを格納するためのレジスタという特別な内部記憶領域を持つ。
バイトだけを操作し、A、B、C、Dという4個のバイトサイズのレジスタを持つ単純化されたコンピュータについて考えてみよう。
- コマンドのオペコードはレジスタAにセットされる。
- コマンドはバイトサイズの入力をレジスタBとCで受け取る。
- コマンドはバイトサイズの出力をレジスタDにセットする。
(2つのバイトを加算すると、1バイトでは収まりきらない結果になるというオーバーフローを起こすことがあるが、ここでは何がどこで行われるかを示すためにそこのところは無視する)
レジスタがたとえば次のようになっていたとする。
- レジスタAには2つの整数の加算のためのオペコードとして10進の1(2進の00000001)がセットされている。
- レジスタBには10進値5(2進の00000101)がセットされている。
- レジスタCには10進値3(2進の00000011)がセットされている。
CPUは、レジスタAに命令が届いたことに気づき、命令をデコード、実行する。その過程でレジスタBとCの値を読み出し、バイトの加算を実行できる内部のハードウェア回路に2つの値を渡す。処理が終わると、レジスタDに10進の8(2進の00001000)がセットされていることがわかる。
CPUは、このようにレジスタを使って加算その他の数学演算を実行する。オペコードをデコードして、CPU内の特定の回路に制御を渡す。CPUは、「Bにセットされている値はCにセットされている値よりも大きいか?」のような比較もできる。そして、CPUは、メモリからレジスタに値をフェッチする(読み出す)ことができ、レジスタからメモリに値をストアする(書き込む)ことができる。
コンピュータは、メモリ内にプログラム(機械語命令とデータ)を格納し、メモリとCPUの間で命令、データをやり取りする。
アセンブラ
機械語でプログラムを書くのは極めて難しい。すべてのビットを完璧に指定しなければならないが、それにはかなりの時間がかかる。そこで、機械語よりも少し読みやすいアセンブリ言語あるいは単純にアセンブラと呼ばれる低水準の言語が作られた。この種の言語は、CPUデザインごとに異なり、命令フローやデータを定義するために変数名のようなものを使えるようにしている。
高水準言語
アセンブラではまだしんどいので、人間にとってさらに使いやすい高水準言語が作られた。高水準言語で書かれたプログラムは、コンパイラと呼ばれるプログラムによってアセンブラに変換されるか、インタプリタと呼ばれるプログラムによって直接実行される。この種の言語の中でも特に古いのは、FORTRAN、LISP、Cなどである。これらは設計や用途が大きく異なるが、コンピュータのアーキテクチャの中での役割は同じである。
現実の仕事では、ソフトウェア「スタック」と呼ばれるものが形成されていることが多い。
メインフレーム
IBM、COBOL、FORTRAN、その他
Microsoft
Windows、ASP、C#、SQL Server
JVM
Java、Scala、Groovy
オープンソース
Linux、言語(Python、PHP、Perl、C、C++、Go)、データベース(MySQL、PostgreSQL)、ウェブ(Apache、NGINX)
プログラマは、これらの世界の中のどれかに留まり、その世界の中の言語やツールを使う。TCP/IPやウェブのように、これらのスタック相互の通信を実現するテクノロジもある。
オペレーティングシステム
個々のイノベーションは、それ以前にあったものを基礎として生まれており、一般に多くの人々は低水準の部分がどのような仕組みになっているのかを知らず、気にもしない。ツールはツールを生み出し、生み出されたツールがもっと多くのツールを作るが、私たちはそれを当然のことと受け止めている。
主要なオペレーティングシステムは、次の通りである。
Windows(Microsoft)
商用、多数のバージョン
macOS(Apple)
商用、Unix系
Linux
オープンソース、Unix系
Unix
オペレーティングシステムは、次の部品から構成されている。
カーネル
プログラムとI/Oをスケジューリング、制御する。
デバイスドライバ
ライブラリ
プログラマが使うソース、バイナリファイル。
アプリケーション
スタンドアローンのプログラム
同じコンピュータハードウェアが複数のオペレーティングシステムをサポートできるが、同時に使えるのは1つだけである。オペレーティングシステムの起動をブートと呼ぶ。そのため、リブートは再起動のことである。これらの用語は映画産業でも使われるようになってきている。製作会社は、うまくいかなかった試みを「リブート」すると言っている。複数のオペレーティングシステムを並べてインストールすれば、コンピュータをデュアルブートにできるが、同時に起動、実行できるのは1つだけである。
ベアメタルという用語は、1台のコンピュータでオペレーティングシステムを実行していることを表す。これからの節では、ベアメタルをグレードアップしていく。
仮想マシン
オペレーティングシステムは、大規模なプログラムの一種なので、ホストマシン上で仮想マシン(ゲストプログラム)として外部のオペレーティングシステムを実行する方法を突き止めた人が出てきた。そこで、第2のコンピュータを買ってきたり、デュアルブートしたりしなくても、PCでMicrosoft Windowsを実行しながら、同時にWindowsの上でLinux仮想マシンを起動できるようになった。
コンテナ
コンテナは仮想マシンよりも新しいアイデアで、同じカーネルを共有する限り、同時に複数のオペレーティングシステムを実行できる方法である。このアイデアは、Linuxカーネルのあまり知られていない機能を使って便利な管理機能を追加したDocker(https://www.docker.com)によって広まった。彼らが使った貨物輸送のコンテナ(貨物コンテナは運送の世界に革命を起こし、私たちすべての輸送費負担が軽減された)という比喩は明確で魅力的だった。Dockerはオープンソースでコードをリリースしたので、コンピュータ産業全体でコンテナは非常に短期間のうちに広く採用されるようになった。
Googleなどのクラウドプロバイダは、何年も前から外部に発表することなくLinuxにカーネルサポートを追加しており、それぞれのデータセンターでコンテナを使っていた。コンテナは仮想マシンよりも消費するリソースが少なく、1台のコンピュータに多くのプログラムを詰め込める。
分散コンピューティングとネットワーク
企業がパーソナルコンピュータを使い始めた頃、コンピュータ同士であるいはコンピュータとプリンタとの間で通信をするための方法が必要になった。最初のうちは、Novellなどのプロプライエタリな(企業の所有物でオープンソースではない)ネットワーキングソフトウェアが使われていたが、1990年代後半にインターネットが広く使われ出すようになって、TCP/IPに置き換えられていった。Microsoftは、BSDというUnix系のフリーのシステムからTCP/IPスタックを手に入れた。
インターネットブームには、サーバの需要が生まれる副産物があった。サーバとは、ウェブ、チャット、メールなどのサービスを実行するハードウェアとソフトウェアのことである。古いスタイルのシステム管理者(シスアド)は、すべてのハードウェアとソフトウェアを手作業でインストール、管理していたが、すぐにオートメーション化が必要であると誰の目にも明らかになった。Microsoftのビル・ベーカーが、サーバ管理についてのペットと畜牛のたとえを発表し、コンピュータ産業の隅々に浸透した(より一般的にペットと家畜と呼ばれることもある)。両者の違いをまとめると、表のようになる。
表 ペットと家畜
ペット | 家畜 |
1匹ずつ名前が与えられる | 自動的に番号がつけられる |
ケアがカスタマイズされる | ケアが標準化される |
壊れたときには手をかけて健全な状態に戻す | 壊れたときには交換する |
「シスアド」に代わるポストとしてDevOpsというものを耳にすることが多いだろう。これは開発(Development)と運用(Operation)を組み合わせた言葉で、サービスを落とすことなくスピーディに更新するためのテクニックである。クラウドサービスは極端に大規模で複雑であり、AmazonやGoogleのような大企業でもたびたびアウテージ(システムダウン)を引き起こす。
クラウド
何年も前からさまざまなテクノロジーを使ってコンピュータのクラスタが構築されてきた。初期の考え方は、同じコモディティコンピュータ(SunやHPのワークステーションではなく、Dellに代表される安価なPC)をLANでつないだベーオウルフクラスタだった。
クラウドコンピューティングという用語は、データセンターのバックエンドリソースを持つ会社だけでなく、CPU時間、ディスクストレージの使用量などに基づく料金を払うすべての会社、人が、そのデータセンターのコンピュータに計算を実行させデータを保存させるという意味である。もっとも有力なのはAmazonのAWS(Amazon Web Services)だが、Azure(Microsoft)やGoogle Cloudも大手の一角を占めている。
これらのクラウドサービスは、舞台裏ではベアメタル、仮想マシン、コンテナなどを使っている。どれもペットではなく家畜として扱われているものだ。
Kubernetes
Google、Amazon、Facebookといった多数のデータセンターで非常に大規模なクラスタを管理しなければならなかった企業は、その大規模システムを維持するために必要なソリューションを開発したり、他社から借りてきたりしてきた。
デプロイ
新しいコンピューティングハードウェアとソフトウェアをどのようにして利用できる状態にするか。そして、故障したときにマシンをどのように交換するか。
構成
これらのシステムをどのように実行するか。ほかのコンピュータの名前とアドレス、パスワード、セキュリティ設定などの情報が必要になる。
オーケストレーション
これらのコンピュータ、仮想マシン、コンテナをどのように管理するか。負荷の変化に合わせてスケールアップ、スケールダウンできるか。
サービスディレクトリ
誰が何をするか、それがどこにあるかをどのようにして明らかにするか。
Dockerなどが複数の競合するソリューションを作ったが、ここ数年の流れから考えると、この戦いはKubernetes(https://kubernetes.io)が勝利を収めたように見える。
Googleは、BorgとかOmegaというコードネームを持つ大規模な社内管理フレームワークを開発してきた。社員たちがこういった重要資産をオープンソース化したいと言ってきたとき、経営陣は少し悩んだが、思い切ってオープンソース化に踏み切った。Googleは2015年にKubernetes 1.0をリリースし、それ以来Kubernetesのエコシステムと影響力はぐんぐん増しています。
まとめ
この記事では、プログラミングを始める際に知っておくべきハードウェアとソフトウェアの基礎について解説しました。
かなり難しい部分が多く理解出来ない部分もあると思いますが、ゆっくり理解してください。
ハードウェアはコンピューターやデバイスの物理的な部分であり、CPUやメモリ、ストレージデバイスなどが含まれます。ハードウェアはプログラムを実行するための基盤となります。
一方、ソフトウェアはハードウェア上で動作するプログラムやアプリケーションであり、プログラマーによって開発されます。ソフトウェアはコードや命令の集まりであり、プログラムの実行に必要な役割を果たします。
プログラミングを学ぶ際には、ハードウェアとソフトウェアの関係を理解することが重要です。ハードウェアの性能や制約に合わせて、効率的なソフトウェアを開発することが求められます。
また、プログラミングを始めるためには、コンピューターやモバイルデバイスなどの適切なハードウェアを用意する必要があります。また、プログラミング言語や開発環境などのソフトウェアも必要です。
ハードウェアとソフトウェアの基礎を理解し、プログラミングの世界へのスタートを切りましょう。自分の目的や興味に合わせて、学びながらプログラミングスキルを磨いていくことで、より創造的なプログラミングの世界に飛び込むことができます。
プログラミングの道は無限の可能性を秘めています。積極的に学び、実践を重ねることで、プログラミングの世界で自分自身を表現し、新たなスキルや成果を得ることができるでしょう。是非、チャレンジしてみてください!