tty・i-node・ファイルディスクリプタによる入出力の流れを "echo World > hello.txt" で理解する
はじめに
ここ最近、 UNIX Linux における標準入出力・ファイル管理の仕組みをすっかり忘れてしまったなと感じることがありました。
そのため、これら入出力で使われる tty i-node ファイルディスクリプタなどを週末にお勉強しました。
知識の定着のために、以下のサンプル入出力がどのように行われるかを辿りながらまとめることで、できるだけ分かりやすくまとめようと思います。
echo World > hello.txt
対象読者
- 「プロセス」がわかる
- 「パイプ」がわかる
注意点
間違いが含まれている可能性が高いです。
ぜひ詳しいかたにコメント欄でお教えいただければと思います。
tty
われわれエンジニアはターミナルを利用してコマンドを入力し、出力されたコマンドの結果を文字として得ています。
このとき、各ターミナルには 擬似端末
が紐付けられています。
具体的には、 Linux ではターミナルに /dev/ttys004
のような擬似端末が紐付けられています。
この擬似端末 /dev/ttys004
はスペシャルファイル(別名デバイスファイル)です。
このスペシャルファイルに入出力することで、擬似端末に紐付けられているターミナルに文字を出力できます。
擬似端末も「ファイル」として表現されているため、通常のファイル(hello.txt など)と同様に扱えます。
具体例
ここからは具体例で tty の挙動を説明します。
zenn-ganyariya
ディレクトリを表示しているターミナルの擬似端末は /dev/ttys004
です。
擬似端末・スペシャルファイルのパスは tty
コマンドで確認できます。
また、同時に他のターミナルも開いてみます。
画像の例では、develop/competitive/go
を表示しているターミナルには /dev/ttys0013
が紐付けられています。
このとき、 /dev/ttys0013
のターミナルで echo "Hello, World" > /dev/ttys004
と打ち込みました。
すると、 "Hello, World" が /dev/ttys004
スペシャルファイルにリダイレクトされ、 /dev/ttys004
側に表示されていることが確認できます。
このように、擬似端末を入出力に指定することで、メッセージをやり取りできます。
上記の画像における下側のサンプルは、ファイルディスクリプタに触れたあとで説明します。
ボリューム
i-node
を説明するために、ボリュームにも触れたほうがよいため簡単にまとめます。
HDD SDD USB などを Linux で統一的に扱うために、これらの記憶媒体をボリュームとして管理します。
このボリュームは物理ボリュームと論理ボリュームに分けられます。
Linux では論理ボリュームとして、論理的に分割されたブロックに、データ領域や i-node リストなどを保存しています。
hello.txt の実体、つまりテキストバイナリの内容などはデータ領域に保存されます。
このデータ領域を指すために i-node が必要です。
i-node
echo World > hello.txt
上記を実行するためには、 hello.txt
の実体、そして管理情報が必要です。
実体はさきほどのボリュームのデータ領域にあります。
この実体を指すために i-node というデータ構造が必要です。
具体的には、 i-node (index-node) は以下のような情報を保持する C 言語の構造体です。
- i-node 番号
- ファイルサイズ
- ユーザ ID / グループ ID
- データ領域におけるブロック位置
- ...
i-node は論理ボリュームにおけるデータ領域ブロックデバイスのブロックの位置を保持しています。
これによって、データファイルの実体の位置を探すインデックス(index)として機能し、実体を見つけられます。
データファイルの実体と管理情報が分離されているため、なにかと便利そうですね。
上記の画像では、 i-node 番号 40 の i-node 構造体が hello.txt
の実体を指しています。
ls
で i オプションを利用することで、 ファイルに紐付いている i-node
番号を調べられます。
上記の例では、README.md
ファイルの i-node 番号は 22845133 です。
ちなみに、 i-node
ではファイルの名前を管理していません。
上記の README.md
の例では、22845133 の ID の i-node に README.md
という名前は管理されていません。
名前自体は、ファイルが所属するディレクトリエントリに紐付けられて保存されています。
というのも Linux ではディレクトリ自体もファイルとして表現されているためです。
zenn-ganyariya
ディレクトリエントリに、所属するファイルの i-node 番号の列とファイル名の列が保存されています。
ファイルディスクリプタ
i-node がファイルの実体を指す役割を持っていることがわかりました。
あとは、 i-node を用いて hello.txt
ファイルの位置を特定し、実際に開けばよさそうです。
echo World > hello.txt
プロセスがファイルを操作するためには、はじめにファイルをオープンする必要があります。
C 言語では、 open
関数を呼び出すと指定されたファイルをオープンできます。
このとき、オープンされたファイルを操作するための識別子として、ファイルディスクリプタが利用されます。
以下の疑似プログラムでは、hello.txt
を開くと、そのファイルを指すファイルディスクリプタ 3 が返されています。
// 操作イメージ
int main(void) {
int fd = open("hello.txt");
// fd = 3
printf("%d", fd);
}
ここで、ファイルディスクリプタはプロセスごとに独立しています。
プロセス ID のように OS 全体で共通した通し番号を使うわけではありません。
プロセスごとに 0 1 2 ... と、小さな値から利用されていきます。
ここで、 0 1 2 は特別なファイルディスクリプタであり、標準入力・標準出力・エラー出力です。(ファイルを追加で開くと 3 から振られていきます。)
具体例
ファイルディスクリプタの話が出たため、 tty の話で途中になっていたサンプルについて説明します。
zenn-ganyariya を指すターミナルに紐付いている shell プロセスの ID は 4028 です。
この 4028 プロセスが利用しているファイルの情報は、 Linux において /proc/4028/fd
で管理されています。
/proc/4028/fd
には、ファイルディスクリプタの整数を名前としてもつファイルが保存されています。
実際に ls proc/4028/fd
(fd はファイルディスクリプタの略) を実行すると、 0
, 1
, 2
というファイルがあります。
これらのファイルはシンボリックリンクであり、 /dev/ttys004
を指しています。
よって 4028 プロセスが標準出力しようとすると、ファイルディスクリプタ 1 が指している /dev/ttys004
に標準出力し、結果としてターミナルに文字が出力されます。
このように、プロセスがファイルを開いたときに、ファイルディスクリプタが返されます。
そして、このファイルディスクリプタを用いて、ファイルへ入出力しています。
ファイルを開く
この説は自分の推測が大きいため間違っている可能性が高いです。
ぜひご指摘いただければと思います。
C 言語で open を実行して得られたファイルディスクリプタで read write するのは非常に工数がかかります。
これは、プログラマ側で I/O 効率が悪くならないようにバッファを用意するなどの工夫が多く必要になるためです。
そこで、 C 言語でファイルを抽象化した FILE
構造体を利用すればよいです。
open
のかわりに fopen
することで、ファイル構造体へのポインタを得ることができます。
ファイル構造体を利用することで、内部的にバッファを利用でき、効率的な I/O が可能です。
ファイル構造体には以下のような情報が含まれています。
これによって、次のファイルの読み出し位置を理解しています。
- ファイルディスクリプタ番号
- アクセスモード
- 次にファイルを読み出す位置
- バッファ
- ...
プロセスごとにオープンしたファイルの情報を保存するテーブルが用意されます。
このテーブルにおいて、ファイルディスクリプタ番号と FILE 構造体のポインタが紐付けられます。
これによって、あるファイルディスクリプタに入出力しようとすると、 FILE 構造体にアクセスし、ファイル構造体が指す次に読むファイル実体の位置を取得できます。
自分の完全なる推測ですが open fopen するときに、内部的に i-node を利用してファイルを開いていると考えています。
まとめ
Linux における tty i-node ファイルディスクリプタの用語と流れを個人的にまとめました。
書いてみるとまだまだ理解が浅いなぁと感じました。
より理解を深めるには自作 OS を作ったり、カーネルのコードを読むのが良いのかなと感じました。
参考にさせていただいたもの
Discussion