inode ってなんのためにあるの？

枯渇するとファイルが作れなくなる inode は、 どんな役割を果たしているんでしょうか。

ここでは inode がどのように使われるものなのかまとめました。 これを読めば「なぜinodeが枯渇するとファイルが追加できないのか」についても理解できると思います。

## はじめに

inode はファイル・ディレクトリに関連して作られるものです。 むしろ、ファイル・ディレクトリの実態といってもいいです。 そしてファイルの書き込み・読み込み時に参照・作成・更新されます。

inode を理解するために、パーティション内のレイアウトを見てみましょう。

## ファイルシステム と inode

ファイルシステムによって inode とそれにまつわるデータ領域も様々ですが、 ここでは <abbr title="very simple file system">vsfs</abbr> (very simple file system) という（説明上よく使われる）シンプルなファイルシステムを使って説明します。 vsfs は参考欄に載せたドキュメントでも使用されています。 vsfs を通して、 UNIX での基本的なファイル管理を確認します。

他のファイルシステムには XFS, ZFS, ext2, ext3, ext4, ReiserFS, FAT, NTFS などがあります。

ハードディスクは**パーティション**に分かれ、パーティションは**ブロック**という単位に分かれます。 そのブロックは、 vsfs では次の**5領域**に分類されます。 上から順に、パーティションの先頭から並んでいると考えてください。 また、パーティション内での各領域の（感覚的な）割合を数値で記載しています。 細かいことは書きませんが、 データ領域 が一番大きく、 inode 領域 が次に大きいです。 その他はすごく小さいです。

| 領域 | 説明 | 割合 |
|:--|:--|--:|
| スーパーブロック | パーティションの管理情報を格納。 | 2% |
| inode ビットマップ | inode 領域の使用・不使用をビットマップで管理。 | 2% |
| データ ビットマップ | データ領域の使用・不使用をビットマップで管理。 | 2% |
| inode 領域 | inode を保存する領域。 | 10% |
| データ 領域 | データを保存する領域。 | 84% |

### スーパーブロック

パーティションの最初のブロックをスーパーブロックに割り当てます。 スーパーブロックには、次のような**パーティション全体の管理情報**を格納しています。

* ファイルシステム識別子 (ここでは vsfs)
* 1ブロックのサイズ
* inode の数
* inode 領域 の開始位置

### inode ビットマップ

inode 領域をブロックに分けて、**使用されているかされていないかをビットで表す**ビットマップ領域です。

たとえば inode 領域が 5 つのブロックから構成されているとします。
1ブロック4KBとして、1セクタを256B、1つのinodeは1セクタに収まるものとすると、 inode の数は 5x4K/256=80 です。
すると 80ビットを inode ビットマップ として管理できればよいことになります。

inode領域の、 inode のために予約された 80個 それぞれの領域を、使用されているのかいないのか、 0, 1 を並べてわかるようにするのが inode ビットマップ です。
このビットマップを管理することで、 ビットマップ領域を確認するだけで、次に書き込める領域、空いている領域を見つけることができます。たとえば500GBのハードディスクだったら、500GBをすべてチェックしなくてもわかるわけですから、I/Oを格段に減らせます。

### データビットマップ

inode と同じように、 データ領域もビットマップで管理します。

### inode 領域

inode が格納されます。

inode には例として次の情報が保存されます。 いわゆる**ファイル・ディレクトリのメタデータ**です。 「画像ファイルで使われている色情報」のようなファイル・ディレクトリの中身は保存されません。

* ファイルサイズ
* ブロック数
* パーミッション
* 時刻情報
* データが格納されているブロック位置情報

inode に含まれる情報の一部は、 `stat` コマンド で見ることができます。

```
# Amazon Linux にて
[kenjiotsuka@sample]~% stat a.out 
  File: ‘a.out’
  Size: 9904            Blocks: 24         IO Block: 4096   regular file
Device: ca50h/51792d    Inode: 526920      Links: 1
Access: (0775/-rwxrwxr-x)  Uid: (  500/kenjiotsuka)   Gid: (  501/kenjiotsuka)
Access: 2021-07-07 09:29:59.116589605 +0900
Modify: 2021-07-07 09:29:55.092364079 +0900
Change: 2021-07-07 09:29:55.096364304 +0900
 Birth: -
```

inode に格納される情報も、 ファイルシステムによって変わってきます。 たとえば ext4 の inode は、 `ext4_inode` という構造体を使って表され(参考: [GitHub fs/ext4/ext4.h] (https://github.com/torvalds/linux/blob/2734d6c1b1a089fb593ef6a23d4b70903526fe0c/fs/ext4/ext4.h#L779))、 ext2 や ext3 とは異なります。
inode そのものについては [Linux マニュアル](https://man7.org/linux/man-pages/man7/inode.7.html) にも説明があります。

重要なのは、**データが格納されているブロック位置情報を持っている**ということです。

### データ領域

**ファイルに含まれる実際のデータ**が格納されます。ディレクトリの場合は、そのディレクトリに含まれるファイルの **ファイル名**とその**inode番号**を格納します。

## ファイルの読み書き と inode

ファイルの読み書きを知ると、 inode の必要性が理解できます。 vsfsでのファイルの読み込み・書き込みの例を書きました。 inode で効率化されていることがわかるはずです。

### 読み込み

vsfs 上のファイル `/a/b/c` を読み込む場合のステップは次のようになります。

1. まずルートディレクトリを参照します。 ルートディレクトリの inode番号は固定されています。 多くの場合は2だそうです。 ルートディレクトリの inode を参照して データのあるブロック番号を取得します。
1. データ領域からルートディレクトリの情報を取得します。 データの中から、 ディレクトリ `/a` の inode番号 を取得します。
1. ディレクトリ `/a` の inode を参照します。 ディレクトリ `/a` のデータのあるブロック番号を取得します。
1. データ領域からディレクトリ `/a` の情報を取得します。 データの中から、 ディレクトリ `/a/b` の inode番号 を取得します。
1. ディレクトリ `/a/b` の inode を参照します。 ディレクトリ `/a/b` のデータのあるブロック番号を取得します。
1. データ領域からディレクトリ `/a/b` の情報を取得します。 データの中から、 ファイル `/a/b/c` の inode番号 を取得します。
1. ファイル `/a/b/c` の inode を参照します。 ファイル `/a/b/c` のデータのあるブロック番号を取得します。
1. データ領域からファイル `/a/b/c` の情報を取得します。

読み込みだけでこれだけの作業が必要になります。 コスト高なので、多くのファイルシステムではキャッシュなどを活用して、I/Oを減らしています。

### 書き込み

vsfs 上の新規ファイル `/a/b/d` を書き込む場合のステップは次のようになります。

1. まずルートディレクトリを参照します。 ルートディレクトリの inode番号は固定されています。 多くの場合は2だそうです。 ルートディレクトリの inode を参照して データのあるブロック番号を取得します。
1. データ領域からルートディレクトリの情報を取得します。 データの中から、 ディレクトリ `/a` の inode番号 を取得します。
1. ディレクトリ `/a` の inode を参照します。 ディレクトリ `/a` のデータのあるブロック番号を取得します。
1. データ領域からディレクトリ `/a` の情報を取得します。 データの中から、 ディレクトリ `/a/b` の inode番号 を取得します。
1. ディレクトリ `/a/b` の inode を参照します。 ディレクトリ `/a/b` のデータのあるブロック番号を取得します。 パーミッションに問題がないかチェックします。
1. inode ビットマップを参照して、 ファイルが書き込めるのかをチェックします。
1. データビットマップを参照して、 ファイルはどこに書き込めるのかをチェックします。
1. チェックしたinode領域、データ領域にファイル `/a/b/d` とそのメタ情報を書き込みます。
1. `/a/b/d` について inode ビットマップを更新します。
1. `/a/b/d` について データビットマップを更新します。
1. `/a/b` のデータ領域を更新します。
1. `/a/b` のinode領域を更新します。

書き込みの回数は1回ではありません。 だいぶコストがかかります。

効率を上げるためにバッファリングを行って、まとめてディスクに書き込むこともあります。 また、電源切断などによりステップの途中で中断した時に、データが中途半端になってしまわないように、ジャーナリングという仕組みを導入しているファイルシステムもあります(ext3, ext4 など)。

## その他

### なぜinodeが枯渇するとファイルが追加できないのか

上のフローでわかる通り、ファイルを作るということは、inode を新たに追加するということです。 ですから、 inode に空きがなければファイルが作れません。

また、 inode の領域は、ファイルシステムのフォーマット時に分割され、 その後でデータが書き込めるようになります。 そのため inode の領域がいっぱいになった後で inode 領域を拡張するのは難しいです。
一応、ファイルシステムの中には inode 領域を拡張できるものもあります。

### inode の名前はいったいどこから

inode は **index node** という名前に由来します。 （これは想像ですが） 要するにパーティション内のファイルに目次をつけておかないと、ファイルを探して取得できないので index node という名前が生まれたのだと思います。

## まとめ

* inode は ...
    * パーティション内の **ファイル・ディレクトリのメタデータ** です。
    * ファイルの参照では、ファイルのデータ位置を知るために必要です。
    * ファイルの作成では、ファイルの管理情報として作成・更新されます。

<small>(inode に「ファイルの中身の保存場所も格納されています」と書きましたが、実際にはinodeに加えて多段インデックスを使うなど、ここで紹介したよりも複雑な管理になっています。)</small>


## 参考

* vsfs
    * [File System Implementation](http://csl.skku.edu/uploads/SSE3044S19/17-fsimpl.pdf)
    * https://student.cs.uwaterloo.ca/~cs350/F16/notes/filesys-2up.pdf
* inode
    * [Linux manual - inode(7)](https://man7.org/linux/man-pages/man7/inode.7.html)
    * [GitHub fs/ext4/ext4.h](https://github.com/torvalds/linux/blob/2734d6c1b1a089fb593ef6a23d4b70903526fe0c/fs/ext4/ext4.h#L779)