「シェル」と「端末(ターミナル)」の違いと詳細

【超ざっくり解説シリーズ】シェル(Shell)とは?シェルについて初心者にもわかりやすく解説するよというnoteを読み、ちょっと内容がひどかったので、Twitterで言及したものの、ちゃんと説明したほうがよかろうと考えて急遽、この記事を書くことにした。

「シェル」「端末」「端末エミュレータ」「端末デバイス」については、Mimir YokohamaのLinux基礎初級「シェルとコマンドライン」の中でやっている。 この内容をやる時期としてはかなり難しい部類に入り、かつ重要であることから、最近は端末デバイスを直接叩くといったことを含めて2時間以上じっくり取り組むことが多い。

内容自体はそれほど難しくはないと思うのだが、理解に至るには

  • 入出力
  • デバイスと接続
  • カーネルとシステムコール
  • プロセス
  • ファイルディスクリプタ
  • デバイススペシャルファイル

に対する理解が前提となるため、ちゃんと認識できていないと難しいかもしれない。

なお、このあたりはOSによって非常に大きな差異があるところなので、ここではLinuxを前提に説明する。 ここに書かれている内容では理解に至るには足りないと感じられる方は、ぜひMimir Yokohamaの授業を受けてみてほしい。 じっくりと丁寧な説明とハンズオンによって理解できるまでしっかりと授業を行っている。

カーネル

カーネルはCPUの特権モードで動作するプログラムである。

実際に定義としてはそれしかなく、カーネルが実際にどのような仕事を担っているかというのも特に決まってはいない。

とはいえ、カーネルにやってほしい重要なこととして「リソース管理」というものがある。 最近のOSはタイムシェアリングシステムなので物理的な装置数以上のプロセスを起動することができるから、プロセスが好き勝手にリソースを使ってしまうと色々奪い合ってしまうのだ。 一番わかりやすいのは、「ハードディスクのアームをどっち方向にどれだけ動かすか」というモーター制御を奪い合ってしまうという状況ではないだろうか。

というわけでリソース管理をするためにカーネルとしてはプロセス管理をしなければならぬ、ということになる。

だが、カーネルというのは基本的にそういうことをする。 つまり、コンピュータを使う上で必要な環境を整える、コンピュータという舞台を作る、というのがカーネルの仕事だ。 ここが改善されるとそれを使う全てが改善されるため開発にも力が入るというものだ。

端末とコマンドライン

カーネルはプロセスを起動する仕組みを提供し、起動したプロセスを管理してくれるのだが、ここで問題になるのは「どうやってプロセスを起動するか」だ。

Linuxの場合、カーネルロード時にinitというプログラムを実行し、initが色々なプログラムを実行するようになっているのだが、 これでは任意のタイミングで必要なプログラムを実行するという方法がない。 また、プロセスやカーネルにユーザーが対話的にやりとりする方法がない。

いや、実際にそう時代もあったのだ。 コンピュータがパンチカードマシンだった時代、プログラムを実行するというのはパンチカードを差し込んで読み込ませることだった。

だが、タイムシェアリングシステム時代になると、ユーザーとコンピュータは「やりとり」をする必要が生まれた。 それも、できるだけ使いやすく。

ここで使われるようになったのが「テレタイプ」である。

テレタイプというのは通信機能(送受信機能)をもったタイプライタである。 言ってみればモールス信号とFAXの中間みたいな感じで、電話回線を通じて(手動交換機を通じて)ほかのテレタイプの紙に印字できる。

テレタイプ自体は1920年代には存在していたもので、1960年代にコンピュータに接続して使われるようになった。 当時の「最もポピュラーのコンピュータとユーザーが意思疎通するための機械」だったのだ。

ユーザーは、タイプライタ同様にテレタイプでタイピングをする、あるいは予め打ち込んで紙テープに出力したものを読み込ませることでコンピュータに対して意思を伝える。 コンピュータからの出力はテレタイプを通じて紙に印字される。

この仲介をするのがシェルなわけだけれども、この時点ではシェルと呼ぶようなものは必ずしも必須だったわけではなく、単純にテレタイプ・インタープリターのようなものであったし、 それ自体が「OS」と呼ばれることが多かった。

Linuxでも端末のことをテレタイプ(tty)と呼ぶが、実はUnixですらテレタイプは過去のものだった。 1970年代にはビデオディスプレイ端末が登場しており(Unixは1973年に登場)、Unixの端末というと紙テープではなく画面を使うもののイメージが強い。

紙テープから画面になることの違いは、「入出力環境が複数行になる」ということである。 例えばUnixのエディタコマンドとしてex(1)とvi(1)がある。 ex(古くはe, そしてee, ed)はラインエディタであり、一行で入出力ができる。つまり、紙テープに適している。 だが、ビデオディスプレイなら複数行を表示し、それを見ながら編集できる。それがvi(VIsual)なのである。

そして、もうひとつ大きな点として「表示上での削除が可能になる」ということがある。

だから、キーボードから打ったものをそのまま解釈してプロセスを起動する、という方法より「よりよい方法」が登場する。 それが「コマンドライン」である。

コマンドラインは、ユーザーはその意図を「行」に記述することができる。 行単位なので、改行したときがその意思の確定である。

このコマンドラインを快適なインターフェイスとして利用できるようにしたのが「シェル」である。

シェル

コマンドラインの基本的な考え方は前述の通りだが、Unix原初のシェルであるshは

  • 行を編集する機能
  • コマンドラインで複数のプロセスを起動する操作
  • ファイルディスクリプタをファイルに対して再オープンする操作
  • 出力ファイルディスクリプタを別のプロセスの入力ファイルディスクリプタへのパイプにする操作

など様々な機能を搭載した「超高機能インターフェイス」であった。 ユーザーは1行の「コマンドライン」の中で実に様々な操作を行うことができる。

今にすればshなんて貧弱極まりないのだが、当時の感覚からすれば「キーボードから打ったものがそのままコンピュータの動作になる」などというのと比べ信じられないほどいろんなことができ、様々に応用できるスグレモノだったのだ。

これがスグレモノである点として、「プログラムを記述する必要性が下がる」というのがある。 インターフェイスが単にキーボードから打った内容に基づきプロセスを起動するだけであるならば、全ての操作は事前にプログラムを用意しておかなければ行えない。 shは実に様々な操作をsh自身によって提供し、なにより応用が効くようになっていたので、圧倒的にプログラムを書くのではなくコマンドラインを打つことで目的を達成できるようになったのだ。

基本的には、bashであれ、zshであれ、それらの「機能が豊富で便利」という点を強化したにすぎず、基本的には変わらない。 シェル自身もプログラムであり、別に特別なものではない。ただ、「インターフェイスが何もなければ対話的にコンピュータを使うことはできないが、シェルはただ対話的に使えるだけでなく、対話的に 便利に 使えるインターフェイスだ」というだけの話である。

カーネルに対して特別にくっついているわけではないし、Linuxシェルはユーザースペースで動作し、別にシステムコールを直に叩くわけではなく汎用性のあるライブラリ(例えばglibc)をロードし、実行する「対話的プログラム」でしかない。

ただ重要な点がある。シェルが登場したとき、コンピュータとやりとりするための機械はテレタイプ、あるいはビデオディスプレイ端末であった、ということだ。

Unixはそれがどちらであっても、あるいはどこのメーカーのテレタイプであっても抽象的に扱えるように「デバイスファイル」という仕組みによって扱うようになっている。 そのため、「端末デバイス」という抽象化したレベルで取り扱うことができ、この端末デバイスは、テレタイプのような一行のものなのか、あるいはビデオディスプレイ端末のような複数行のものなのか、という情報と合わせて取り扱うことができる。

現在は端末デバイスではなく、ビデオデバイスを通じて出力し、キーボードを通じて入力するのが普通だ。 これは一般的には「X Server→ビデオデバイス(カーネルインターフェイス)→ビデオドライバ(カーネル)→ビデオカード(ハードウェア)」の構成であるが、シェルは依然として端末デバイスを必要とする。 普通、シェルは直接にX Server、あるいはビデオデバイスを扱う機能はもたされておらず、あくまでも端末デバイスを使って入出力を行うようになっている。

端末、仮想端末

だが、現在本当に端末に繋がっているコンピュータなんてまずないので、物理的に端末を意味する端末デバイスファイルが用意できない。

そこで、実際には端末はなくても、端末デバイスとして扱える仮想端末デバイスと、コマンドラインの入出力に適した仮想端末ソフトウェアが使用される。

「テキストモード」においては、「仮想コンソール」と呼ばれることが多いプログラムを使用する。 このプログラムは、ビデオデバイスによってテキストビデオモードを設定すると共に、(テキストビデオモードの)ビデオデバイスを通じた画面出力と、キーボード入力の処理機能、 そして仮想端末デバイスの提供(Linuxでは/dev/tty*)を行う。 その上でシェルを起動すれば、シェルは/dev/tty?を端末として扱うことができるわけだ。

ターミナルエミュレータは単なるX client(あるいはWaylandクライアント)である。 つまり、グラフィカルアプリなのだが、これはこれで仮想端末を提供する。Linuxでは/dev/pts/*である。 やはり、この上でシェルを起動すれば、シェルは/dev/pts/?を端末として扱うことができる。

だから、例えば

% ls -l /proc/$$/fd/0
lrwx------ 1 haruka haruka 64  6月  1 00:19 /proc/2835/fd/0 -> /dev/pts/0

ということである。

なお、端末エミュレータはキーボードを処理しない。X serverから受け取ったキー入力を仮想端末デバイスを通じて入力するようになっている。 だから、「シェルの標準入力はデフォルトでキーボードに繋がっている」というのは嘘で、対話的シェルは入出力ともに端末デバイスに繋がっている。 仮想コンソールの場合、シェル→端末デバイス→仮想コンソール→キーボードと間接的につながっているのだが、ターミナルエミュレータの場合は全く繋がっていない。

ちなみに、「端末」といえば、DECのVT-100である。 1978年にリリースされたビデオディスプレイ端末で、まぁとにかく売れに売れて、端末といえばVT-100というレベルだった。

だから、VT-100の仕様が一般的で、VT-100と互換仕様の端末も出たし、仮想端末もだいたいVT-100と互換になっていたりする(そして、その設定がある)。

そして、もうひとつ。

xtermは、それを端末として扱うための色々なものを、自前でもっていたりする。