ディレクトリとは何か
はじめに
LinuxやMacOSで開発や作業をしていると、ディレクトリという概念は避けては通れません。
また、普段の会話の中でも「このディレクトリにログ入れている」「ディレクトリ構成は...」など、週に何回かはディレクトリという言葉を使うと思います。
アイコンでは入れ物のような形をしているディレクトリは、実際どのようなものなのでしょうか?
本記事では、ディレクトリとは何かを
- 仕様書や公式ドキュメントなどの定義
- Linuxカーネル(特にext4のドキュメント)
- 実際のディレクトリの観察
の3つの観点から確認します。
本記事のゴール
OS上で表示されるアイコンではフォルダに見えるディレクトリを、内部では「名前→inode番号」を並べた一種のファイルとして扱われている、ということがわかることが、この記事のゴールとなります。
ディレクトリとは(仕様書や公式ドキュメントから)
まずは、APIレベルで「ディレクトリ」がどのように定義されているかを確認します。
POSIX <dirent.h> によるディレクトリの定義
POSIXの<dirent.h>の説明ではDIR型を
ディレクトリストリームを表す型
として定義されています。
また、struct direntという構造体が定義されており、次のメンバを含みます。
- ファイルのシリアル番号(inode番号)
- エントリ名(ファイル名コンポーネント)
このヘッダファイルには、関数やマクロとして下記が定義されています。
また、ディレクトリの内部形式は指定されておらず、操作系の関数は定義されています。
つまり、POSIXが保証しているのは、readdir() で struct direntを順に取り出せることなどです。
POSIXではAPIの振る舞いを保証しており、中身のレイアウトまでは標準化していないことが読み取れます。
glibc マニュアルによるディレクトリエントリの説明
glibcのマニュアルでは、struct direntについて説明されています。
このページは、readdirが返すstruct dirent構造体の中身と役割を説明しています。
ファイル名のd_nameだけが、どの環境でも必ず使えるフィールドであり、d_inoやd_typeなど他のフィールドは実装ごとに扱いが異なるため、移植性を考慮する場合は慎重に検討する旨が記載されています。
path_resolution(7)による. / ..の扱い
Linuxのpath_resolution(7)では、「パス名をどのようにファイルへ解決するか」が解説されています。
このページでは、次のようなことが説明されています。
- すべてのディレクトリは
.と..を持ち、.は自分自身、..は親ディレクトリを指す。 - パスの解決は、それらが物理ファイルシステム上に存在するか否かに関わらず
./..がその意味を持つものとして扱う。 - ルートより上にはいけない。
ということがわかりました。
次は、Linuxが実際にはどのようにディレクトリを実装しているのかを見ていきます。
Linuxではディレクトリをどのように表現しているか
ここではext4ファイルシステムを例に、Linuxカーネルのドキュメントからディレクトリの実体を確認します。
ext4のドキュメントから読むディレクトリ
Linuxカーネル公式ドキュメントのext4の「Directory Entries」の章では、ディレクトリについて次のように説明されています
- ext4ファイルシステムにおいて、ディレクトリはほぼフラットファイルであり、 任意のバイト文字列(通常はASCII)をファイルシステム上のinode番号に対応付ける役割を担う。
- ファイルシステム全体で、同じinode番号を参照する複数のディレクトリエントリが存在し得る。
- ディレクトリの検索は、特定のディレクトリエントリに対応するデータブロックを読み取る。
ext4では、ディレクトリは、任意のバイト文字列(多くはファイル名)を、ファイルシステム上の inode番号に対応付けるための「ほぼフラットなファイル」として実装されています。
実験:ディレクトリの実体を観察する
以下のコマンドは、WSL/Linux VM/Dockerコンテナなどの環境で再現できます。
MacOSなどLinuxではない場合、下記のコマンドでDockerを立ち上げます。
docker run -it --rm \
-v "$PWD":/workspace \
-w /workspace \
ubuntu:24.04 bash
実験1:ディレクトリのinode番号を確認する
まず、ディレクトリ自身がinodeを持っていることを確認します。
mkdir sample
ls -lid sample .
結果は下記。
34863279 drwxr-xr-x 3 root root 4096 Dec 7 12:55 .
34863280 drwxr-xr-x 2 root root 4096 Dec 7 12:55 sample
先頭の数字が inode 番号です。
sampleにもカレントディレクトリ.にもinode 番号が振られていることが分かります。
statでも確認しておきます。
stat sample/
File: sample/
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 0,66 Inode: 34863280 Links: 2
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-12-07 12:55:50.432787000 +0000
Modify: 2025-12-07 12:55:50.432787000 +0000
Change: 2025-12-07 12:55:50.432787000 +0000
Birth: 2025-12-07 12:55:50.432787000 +0000
ここでファイル種別として directory と表示されるが、
- inode番号やリンク数など、通常ファイルと同様のメタデータを持っている。
- サイズが4096バイトなのは、ディレクトリエントリを格納するためのブロックが確保されているため(ext4の初期サイズ)
ということがわかります。
実験2:.と..が指しているものを確認する
次に . / ..を、inode番号ベースで確認します。
mkdir -p /tmp/dirlab
cd /tmp/dirlab
mkdir d1
cd d1
ls -lid . ..
結果は下記。
34863281 drwxr-xr-x 2 root root 4096 Dec 7 12:59 .
34863279 drwxr-xr-x 4 root root 4096 Dec 7 12:59 ..
ここから読み取れることは、
-
.のinode(例: 34863281)は自分自身のinode番号 -
..のinode(例: 34863279)は親ディレクトリ(/tmp/dirlab)のinode番号
pwdで見えるのは文字列としてのパスであり、inode番号を辿るときには、カーネルが ./..を利用して今いるディレクトリや、親ディレクトリを解決しています。
まとめ
本記事では、仕様書やカーネルドキュメント、実際の挙動の3つの観点から「ディレクトリとは何か」を見てきました。
- APIレベルでは
DIR*と struct dirent`が決められている。 - ext4ではディレクトリはinode番号を持ったファイルの一種である。
- 実験では、ディレクトリ自身もinodeを持ち、
./..を通じて親子関係が表現されていることを確認しました。
OS上で表示されるアイコンではフォルダに見えるディレクトリも、内部では「名前→inode番号」を並べた一種のファイルとして扱われている、ということがわかることが、この記事のゴールとなります。
参考
POSIXの<dirent.h>の説明
closedir
opendir
readdir
glibcのマニュアル
Linuxカーネル公式ドキュメント ext4 Directory Entries
Discussion