はじめに
この記事は、私がこの数年、自分が普段利用しているOSの系列であるUnixとはなんなのかを完全な興味本位で掘っていたものを他の人に図で共有していった経験から生まれました。
Unixという名称に関して
OSとしてのUNIXは、実に50年以上前にあたる1969年につくられました。しかし半世紀経過するなかで、現在のLinuxやAppleのOS(MacOS, iOS, iPadOS等),PlayStationシリーズの仕様のベースとなったOSです。このUNIXっぽいものはUNIX系(UNIX system-like)と呼ばれるもので、現在はiPhoneとAndroidOSスマートフォンの爆発的普及により、このUNIX系がコンピュータの市場で主流となりました。
私がお話しするのはすべてUNIX系を触ってきた経験に基づきます。ただ今回は、他の著書と表現を合わせ、UNIXやUNIX系全てのコミュニティの流れを網羅するフレーズとしてUnixを利用します。
図解と銘打っている理由
Unix関連のよい著書は数多くあり、私もその豊かな社会で育ってきました。ただ結局、何かサンプルのプログラムを実装しながら理解する必要がありました。私はいつものことだと普通に流して日々時間をみつけて試行錯誤をやってきていました。
ですが、最近会社であらゆることを説明したり教えたりする機会が大幅に増え、口伝+実際に触ってみてくれ、という自分がやってきたことをやってもらう、という教え方に限界を感じ始めました。実際にやりとりしてわかったのは、教わった方々は何かが開発機で起きた時、何が起きたのかイメージをつかめず、質問がしづらいという状況でした。なので、イメージを口伝以上のなにかで伝えなければなりませんでした。
適宜ホワイトボードに図を描きながら説明を繰り返し、パワポ資料も離散的に描いていき、試行錯誤の果て、ある程度自分が納得できるグラフィックデザインに落とし込めるようになりました。教わった方々からも質問もかなりいただきやすくなり、いままでよりずっと教わった側の人たちの顔は明るくなりました。
そこで、私の経験を可能な限り図で表現することで経験を補えるかもしれない、というわずかな可能性を感じるようになりました。とはいえ、経験に勝るものはありません。あくまで私ができる最大限は、「自分にもわかったかもしれない、もしかしたらできるかもしれない」と感じてもらい、一歩進んでもらうことです。
そして最も質問や詰んだという相談の多いLinux、抽象化してUnix関連の連続的な知識には、会社の外でもかなりニーズがあるんじゃないかと思い、Unixという観点からちゃんと記事にしようと思い至ったというわけです。
対象読者
経験則として、大学や高専などの情報系で修了または就学中の人たちが復習目的で聴いているときは伝わりやすい傾向にありましたので、そのあたりが主な対象読者になると思います。そうでない人も、コンピュータ関連の基礎知識がだいたい基本情報技術者の資格本の程度を難なく読み進められる程度あれば話としてはついてこれると思います。
UNIXから生まれたアイデア
1969年に生まれ育てられ始めたUNIXからは、現在のコンピュータに決して欠かすことのできない重大なアイデアが複数生まれました。今回は主に利用される以下の3つを説明します。
- アセンブリ言語より高い移植性で記述するための高水準プログラミング言語の原初C
- すべてのオブジェクトはファイルであるという発想からのプロセス間通信の発展
- ネットワーク通信であるTCP/IPのリファレンス実装的な影響と抽象化するソケット
1. アセンブリ言語より高い移植性で記述するための高水準プログラミング言語の原初C
C言語はいまではシステムプログラミングにおけるほぼ唯一に近い候補ですが、その理由はかつてUNIXがすべてアセンブリ言語で書かれていたのをC言語で記述し直し、移植をするためにつくられたことからきているようです。
C言語の仕様のほぼすべてはUnixのデザイン(あるいは仕様)に基づいており、標準入出力の操作、例えばHello Worldを出力したり、ネットワークプログラミングにおける元祖的なデザインであるソケットも、Unixから生まれました。これらを制御するシステムコールという特殊な処理も、ほとんどの場合Unixの時代のデザインから生まれた発想となっているようです。このためC言語における手法を理解したいと思った時は、Unixの哲学を理解すると非常に頭の中に入りやすいです。
2. すべてのオブジェクトはファイルであるという発想
Unixにおいてはファイルの読み書きをシステムコールによって実施することになりますが、その引数にはもちろんファイル名を書く場所があります。大抵の場合は拡張子がついているもので、txtやmd,pyなどが.のあとに続きます。しかしこれ以外にも、標準入力や標準出力を割り当てる方法を採用できるようになっています。この発想はパイプの発想に繋がることとなります。
3. ネットワーク通信であるTCP/IPのリファレンス実装的な影響と抽象化するソケット
いまやHTTPS通信なくして、ググることも、つぶやくことも、LINEすることもできません。このHTTPSを含むひと揃いの規約、プロトコルスイートとして、TCP/IPという位置付けがあります。
TCP/IPは国防高等研究計画局DARPAの前身ARPAのプロジェクトARPANETの次なるプロトコルとして開発されました。当時UNIXの別バージョンがカルフォルニア大学バーグレー校でBSDという名前でつくられており、これが当時ソースコードが制約されていないという理由から選定され、結果としてほかのリファレンス実装的に参照されるケースが増えたようです。この関連から、BSDよりソケットという概念が生まれたとみられます。
ソケットはその出自からバークレーソケットと呼ばれる場合もあるようです。これはTCP/IPにおける3ウェイハンドシェイクなどの面倒な処理の記述をすべて簡易的なシステムコールで抽象化(カプセル化)してOS側で実行してくれるというデザインがなされています。そして確立された通信上でバイトストリームをやりとりできるようにされているため、この上にHTTPSやssh, お好みで自己流RPCなどを実装させるなどの方法を確立させることができ、これによりTCP/IPはプロトコルスイートとしてますますの発展を遂げました。
Unixの中身を少しずつ理解する
UNIXから生まれたアイデアを見てきましたが、ほとんどがいま我々が当然のように享受しているものばかりでほとんど実感が湧かないと思います。そこで、実際に見えているウィンドウというGUIから話を掘り下げていこうと思います。
Unixで我々のみる画面の基礎はウィンドウシステムを中心とする
私たちがiPhoneやiMacを起動したりすると、常にロック画面やホーム画面が映ります。あまりにも当然すぎる話なのですが、この時点でコンピュータにとってはかなり特殊な挙動をしています。この画面をつくるために、かなりたくさんのプログラム、ここではプロセスと仮に呼びますがこれらの実行を避けて通ることができないのです。
これらの画面、Graphical User Interface(GUI)は、かつてのApple系のOSの場合はXQuartzと呼ばれるウィンドウシステムを中心にして動いていました。現在はXQuartzをより最適化した代替品が動作しているようですが、原理上は近いはずです。
このウィンドウシステムでXQuartz含むUnixで主に利用されているプロトコルの一種が、X Window Systemです。X Window Systemではクライアントと呼べるプロセスがあるのですが、ここがGUIの画面を作り出し、私たちにロック画面やホーム画面を見せています。このX Window SystemはかつてのApple系OSだけでなくLinuxなどのUnixで動作するものとなっており、共通化されています。
このウィンドウシステムにおいては、ただ文字の入出力しかできないターミナルの画面もまた、ウィンドウシステムで描画をサポートされるクライアントのひとつに数えられます。
ターミナルの起源はUNIX誕生の経緯にまで遡る
ターミナルは、ウィンドウシステムが提供しているマウス操作を除いて文字の入出力以外実行することができません。これはCommand Line Interface(CLI)と位置付けられます。このターミナルは複数起動させることができ、他のユーザーもsshなどで遠隔からログインして同時に操作可能という非常に優れたものとなっています(X Window Systemもこの流れを継いで複数人で利用可能)。
Unixプログラマーであれば毎度お世話になるこのターミナルですが、なぜターミナルと呼ぶのでしょうか?そして、なぜ同時に操作可能となっているのでしょうか?その起源は、UNIX誕生の経緯にまで遡ります。
非常に端的に表現すると、UNIXはCompatible Time Sharing System(CTSS)の影響を直に受けて生まれました。このCTSSはまさしく、複数人でひとつのコンピュータを利用できるようにした画期的なOSでした。今のWindows NT系ですら、複数のアプリは起動できてもほとんどひとりでしか操作できないのと比較してもらえればわかると思います。UNIXはCTSSの後継として、複数のユーザーで利用可能になりました。
しかし複数のユーザーで利用するためには、現代のように複数のコンピュータを用意することはコストの関係上できませんでした。そこで、単純にキーボードの入力とモニタ上の出力のみに特化させた、ほとんど何も記憶ができない端末が複数台利用されました。これが、ターミナルの原点にあたります。現在ウィンドウシステムでターミナルと表示されているものは、この物理的なターミナルを模倣(エミュレート)することから、ターミナルエミュレータという名称がより実態に則していることとなります。
プロセスは仮想的な計算機
ここまでごく普通にたくさんのプログラムがプロセスとして動いている、という話をしてきました。私たちはごく普通にたくさんのアプリを同時に立ち上げられることを経験しており、それはWindowsでも変わりはありません。ではそもそもプロセスとはなんなのでしょうか?
プロセスとは、OSというプログラムが作り出す仮想的な計算機を指します。それらは外からみていると、仮想的なCPUやメモリが割り当てられているように見えます。CPUについてはさておき、事実としてプロセスにはメモリというものが割り当てられています。
実際のUnixにおいては実メモリにちゃんと書き込まれていますが、番地が書き変わることによって他のプロセスやカーネルのメモリにアクセスが困難なようになっています。できたとしてもMemory Management Unit(MMU)により例外が発動し強制終了させられます。
プロセスは、ネットワーク通信したいときやファイルの読み書きをしたいといった、非常に危険な処理を関数のようなフォーマットでOSの中枢、Unixでいうところのカーネルに対して呼び出します。これはシステムコールと呼ばれ、カーネルはその情報を受け取った時のみ危険な処理を実行するようになっています。具体的にはUnixは通常のプロセスとカーネルのふたつのモードが厳密に分けられており、同時にメモリまで分けられています。これによりカーネルが意図しない危険なシステムコールを止める改修が実施しやすい設計になっています。
余談であり現在も進行しているかは不明ですが、Windows10含むWindows NT系の場合は、処理速度の向上のためだけにプロセスのメモリ空間とカーネルのメモリ空間を分けずに利用してしまう事例がWindows向けのウィンドウシステムやWebサーバ(WindowsNTカーネルの真の姿が見えない我々含むサードパーティにそんな芸当はできないはずなのでおそらくMS純正のIIS)にあったようです。つまりプロセスにカーネルで侵食させるコードを用意して実行させれば、場合によってはクラッシュや攻撃に繋げられる可能性すらあるという危険な代物です。
マイクロソフトは現在、これらの問題をセキュリティソフトであるWindows Defenderよるスキャンと、超高頻度のアップデートなどで地球規模で情報を収集して対策する仕組みを構築していますが、かなりの犠牲を払って実施されていることは想像に難くありません。
(2021/1/26追記)下記コメントでとてもうれしいフィードバックがありました。どうもWindowsのカーネル空間というものは私が想像するものとは異なるようです。
具体的にいうと私の想像するほうがモノリシックカーネルと呼ばれるLinuxのようなカーネルが巨大なプログラムだったのですが、Windowsはマイクロカーネルとされているそうです。つまりカーネルに最低限の実装を施し、カーネル空間で動かすプロセスも実質ユーザープロセスとしてしまおうという発想のカーネルです。なのでLinuxのカーネル空間の侵食というコードというよりは、X Window Systemへ侵食するコードと比較的近い状況と言えます。それでもWindowsがグラフィック関連でよくクラッシュしていたのはデバイスドライバの不具合によるもので、これがまずいことにカーネルをクラッシュさせてしまい……という話で、これはPC市場の大半を占めているからこそ起きていた話でどのOSでも起こりうる内容でございました。まさかUnixの話を書いていてWindowsの知見まで得られるとは思ってませんでした、ありがとうございました。
プロセス上のポインタを使ったメモリ管理は非常に難しい
ほぼすべてのプログラムはプロセス上で仮想メモリを管理され、それらはWindowsのようなことがない限りはOSの挙動をおかしくしたりすることはほぼありません。しかし、プロセス自身で誤った操作を実施してしまえば、運が良ければプロセス単体が強制終了することとなります。運が悪いとコンピュータがなぜか怪しい挙動をし続けるのを呆然と眺めるか、強制的に終了させるしかなくなります。それはポインタを使った厳密なメモリ管理をするときに踏みやすくなります。メモリのアクセスが重複してしまいやすく、この制御を人間の脳で行うことは非常に困難なのです。このため最適化が必要となるOSや組み込み系, DBやゲームサーバーなどのプログラミングを除いて最近はほぼ実施されません。少なくとも私のようなWebアプリ書く程度の人間では実装時にポインタなど考えることはほぼありません。いい時代になったものです。
マルチプロセスとマルチスレッドの関係
スレッドは、プロセスという仮想的な計算機の上でリソースを共有しあう概念で、マルチスレッドなどの文脈で利用されます。
この図からわかるとおり、うまく利用さえできれば最速の計算処理を実施できることになります。しかし実際はメモリのアクセスがシングルプロセス以上に重複してしまいやすく、この制御を人間の脳で行うことは非常に困難で、不具合を引き起こしやすく再現性が確保できません。このためマルチスレッドプログラミングは可能な限り避けたい内容となります。ただし、非同期処理をシングルスレッドで記述する場合は非常に奇妙な実装をしなければなりません。詳細はJavaScriptVMにおける非同期処理の方法をお調べいただくとよいのですが、あれを自分で実装するとなるとかなり気の滅入る作業となります。
マルチプロセスは、プロセス間通信を基盤に構築された手法です。マルチスレッドと比較して処理速度に劣りますが、互いに互いを壊し合う可能性が共有する手法に限定されるため、リスクを下げやすいというメリットがあります。
マルチプロセスを実現させるための広義のファイルの利用: プロセス間通信(InterProcess Communication)
マルチプロセスの場合、基本的に自分自身のメモリのみしか参照することができません。このことから何かしらの方法を使ってプロセス間でデータをやりとりしなければなりません。これらを、プロセス間通信(IPC: InterProcess Communication)と呼びます。
図で示した通り、プロセス間通信にはいくつかの種類がありますが、別のコンピュータ同士で実行できるのはソケットのみとなります。
ソケット上に構築されるRPC: Remote Procedure Call
ネットワーク通信をしている以上ソケットを利用したIPCが中心となりますが、その手法についてはすべてTCP/IPプロトコルスイート内のいずれかのプロトコルで動くアプリケーションを利用するか、別途プログラマーが実装する必要があります。たいてい前者で問題が解決し、ほとんどプログラマーは仕様にそったものを準備するだけでよいということになっています。私のようなWebアプリを作る場合にはほとんどソケットプログラミングをする必要がないのはこのためです。
ただ、ネットゲーム並みの処理速度を実現させる場合だと処理速度の最適化などの観点からRemote Procedure Callを利用する場合があります。JavaだとRMIと呼ばれるものです。さきほどのX Window SystemにおいてもRPCが利用されています。
ただこの手法を自分自身でゼロから開発するのは非常に難しく、たいていの会社においてRPC用の自社フレームワークを準備して利用されているようです。またサーバーとクライアントのネットワークプログラミングのどちらにも別途メンテナンスをしなければならず、多大なコストをかけてメンテナンスしているとみられます。
おわりに
Unixのことを調べて理解するのはなかなか時間がかかったものの、大切な経験になったと感じています。それはたくさんの人たちがUnixを実装し、利用し、共有してきてくれたからこそだと思います。その経験をこうして図解として世に出せることに、少しほっとしています。
この記事を、みなさんのたのしいUnixプログラマー生活の糧にしていただけたら幸いです。
参考文献
エリック・スティ−ブン・レイモンド「The Art of Unix Programming」
https://www.amazon.co.jp/dp/B07PYTR159/ref=cm_sw_r_tw_dp_J1ST4VQ7CNQJND80MP11
青柳 隆宏「はじめてのOSコードリーディング」
https://www.amazon.co.jp/dp/B0821XY1QJ/ref=cm_sw_r_tw_dp_CFM2JG589SGXY2JFPZ43
山森 丈範「Linuxシステムコール基本リファレンス」
https://www.amazon.co.jp/dp/4774195553/ref=cm_sw_r_tw_dp_QKW83MSPP8SCA7AB7AXF
武内 覚「試して理解 Linuxのしくみ」
https://www.amazon.co.jp/dp/B079YJS1J1/ref=cm_sw_r_tw_dp_BZ8BRHAHPXVDK88GVMNP
末安 泰三「Linuxカーネルの教科書」
https://www.amazon.co.jp/dp/B08HQKJK2S/ref=cm_sw_r_tw_dp_PD0A1WS7QXYQF8TDX5F2
藤田 昭人 「Unix考古学」
https://www.amazon.co.jp/dp/B01N6NML10/ref=cm_sw_r_tw_dp_TRZR4C5SP7Y2FV5X8DB5