一時ファイルシステムと擬似ファイルシステム:データ、インターフェース、リソースの境界

このページの目次

パスはファイルのように見えるが、操作の意味は大きく異なる。tmpfs はアプリケーションデータを保存し、procfs は状態を生成し、sysfs への書き込みはデバイスを制御する可能性がある。これらのインターフェースを読む際は、まず責任範囲、寿命、リソースの境界を確認する。

説明すべき現象参照箇所
読み取ったテキストは整合性のあるスナップショットか?第 1~2 節:分類と seq_file
なぜ sysfs 属性をエディタで保存できないのか?第 3~4 節:制御とトレーシングインターフェース
一時ファイルがスワップに触れるのはなぜか?第 5 節:tmpfs と memfd
デバイスノード、初期ルート、実際のマウントの違いは?第 6~7 節:起動と位置特定

1. 責任範囲で分類する

すべてファイルインターフェースだが、コンテンツのソースとリソース制約は異なる ユーザーデータ保存 tmpfs / ramfs / memfd 動的観測と制御 procfs / sysfs / cgroupfs デバイスとデバッグインターフェース devtmpfs / debugfs / tracefs メモリ、スワップ、クォータ データは実際にリソースを占有する カーネル状態とコールバック 読み取りが必ずしもグローバルに整合性のあるスナップショットとは限らない ドライバ / トレース機能 アクセスが実際のデバイス操作をトリガーする可能性がある nodev はブロックデバイスをマウントソースとして必要としないことを示すが、ストレージがない、副作用がない、権限制限がないことを意味するものではない。
ファイルシステム一般的な責任重要な境界
procfsプロセスとシステム情報、一部制御インターフェースPID namespace、権限、マウントオプションの影響を受ける
sysfsデバイスモデルおよびカーネルオブジェクト属性属性インターフェースは ABI に準拠する必要があり、内部構造を無制限にエクスポートするものではない
debugfs開発・デバッグ用インターフェース安定したユーザー ABI を保証しない場合が多く、完全に無効化されている可能性もある
tracefsトレース設定とデータファイルによってスナップショット、ブロッキングストリーム、消費型レコードなど異なる
tmpfs一時ファイル、共有メモリ関連ストレージメモリを占有し、設定に応じてスワップ可能。容量と inode 制限がある
ramfs単純なメモリファイルストレージ通常のファイルのように書き戻して解放できず、tmpfs のようなサイズ上限もない
devtmpfsカーネルデバイスノードノードが存在しても、デバイスが現在利用可能である、またはアクセス権があることを保証しない

/proc/filesystems 内の nodev は「すべての擬似ファイルシステム」に対する正確なラベルではない。NFS などのブロックデバイスをマウントソースとしないファイルシステムも nodev としてリストされる場合がある。また、これはマウントオプションの nodev とは等価ではない。nodev オプションは、デバイス特殊ファイルをデバイスとして使用することを制限するものである。

2. procfs:テキスト出力の背後にはオブジェクトの寿命がある

/proc/<pid>/status、maps、fd はそれぞれタスクの状態、仮想メモリマップ、ファイルディスクリプタに関連する。これらのオブジェクトは読み取り中に変化したり、終了したりする可能性がある。PID は再利用されるため、同じ数値パスを複数回読み取っても、必ずしも同じ長寿命のオブジェクトを操作しているわけではない。安定したプロセス参照が必要な場合は、ディレクトリ名を恒久的な ID として扱うのではなく、pidfd などのインターフェースを理解する必要がある。

/proc/self はアクセスするユーザーに応じて解決される。コンテナが認識するプロセスの集合は、proc マウントに関連付けられた PID namespace に依存する。権限や hidepid なども可視性に影響を与える。すべての /proc システム統計が自動的にコンテナ固有の統計になるわけではない。/proc/<pid>/mem は厳格なアクセスチェックの対象であり、ptrace 権限を回避する代替チャネルとして一般化できるものではない。procfs ドキュメント を参照。

seq_file は出力の整理を管理するが、システムを凍結はしない

seq_file はセグメント化された出力、バッファリング、シーク位置の管理を行い、反復可能なオブジェクトからテキストを生成するのに適している。start/next/stop/show などのコールバックは、依然として対応するオブジェクトの同期と参照ルールに従う必要がある。バッファ不足や seek の場合、再走査が発生する可能性があるため、show には再現不可能なビジネスロジックの副作用を含めるべきではない。

単一の cat コマンドでも、複数の read 呼び出しで構成されることがある。出力の複数のフィールドが同じ整合性のあるスナップショットから来ているかどうかは、実装に依存し、「seq_file を使用している」という事実だけでグローバルな原子性の観測を推論することはできない。ユーザーランドでの解析では、キーによる識別を行い、新しいフィールドの追加を許容すべきであり、行番号が永遠に特定のものを指すと依存すべきではない。seq_file インターフェース を参照。

以下は、seq_file を再利用して定数のみを出力する独立したカーネルモジュールの例である。これは登録、失敗時の戻り値、および削除のペアリングを示しており、可変なドライバオブジェクトは読み取らない。

#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static struct proc_dir_entry *entry;

static int kb_show(struct seq_file *seq, void *unused)
{
    seq_puts(seq, "format_version 1\n");
    seq_puts(seq, "example_value 42\n");
    return 0;
}

static int __init kb_init(void)
{
    entry = proc_create_single("kb_seq_example", 0444, NULL, kb_show);
    return entry ? 0 : -ENOMEM;
}

static void __exit kb_exit(void)
{
    proc_remove(entry);
}

module_init(kb_init);
module_exit(kb_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Constant seq_file teaching example");

対応する Kbuild の動作は obj-m += kb_seq_example.o である。教学用のチェックでは、オブジェクトファイルのみをコンパイルすればよく、モジュールのロードは必要ない。定数を動的オブジェクトに変更した場合でも、削除時の並行コールバック、バックグラウンドタスク、およびプライベートデータの寿命を依然として処理しなければならない。このフレームワークをコピーしただけでは、それらの参照関係が自動的に解決されるわけではない。

3. sysfs と kernfs:オブジェクト属性は通常の編集可能なファイルではない

sysfs は、カーネルオブジェクトをディレクトリ、属性、リンクとして提示する。/sys/devices はデバイスの階層を表し、bus/class などのビューはリンクによって整理されることが多い。1つの kobject は通常ディレクトリに対応し、属性がファイルに対応する。各 kobject が直接1つの属性ファイルに等しいとは言えない。

操作属性インターフェースの実際の動作
show現在の値を生成する
store入力を検証し、対応する変更を実行する
成功を返すインターフェースの処理結果を報告するが、任意のデバイス操作が完全に終了したことを必ずしも意味しない

テキスト属性は通常、簡潔で明確な値を使用する。バイナリ属性には独自の契約がある。サイズ、シーク、および繰り返し読み取りも、インターフェースに従って解釈する必要がある。sysfs の設計 を参照。

kernfs は、sysfs、cgroupfs などにツリーとファイル操作の基盤を提供し、6.x よりも以前から存在する。ノードの削除と実行中の操作の調整も行わなければならないため、「RCU 付きのディレクトリツリー」としてのみ理解してはならない。そのリンク機能は主にシンボリックリンクであり、通常のディスクファイルシステムのような任意のハードリンクセマンティクスを持つと一般化してはならない。フレームワークが提供する同期は、ドライバのプライベート状態の保護に代わるものではない。

ユーザーランドで制御属性に書き込む際は、ドキュメントで規定された入力形式全体を使用し、エディタの一時ファイルと rename を使った保存モードを用いてはならない。属性の安定性と単位を理解するには、ファイル名から推測するのではなく、Documentation/ABI/ 内の対応するエントリを優先して参照すべきである。

4. debugfs と tracefs:観測にもオーバーヘッドと状態変化がある

debugfs は開発者がデータをエクスポートするのに便利だが、インターフェースはバージョンや設定に応じて変化する可能性がある。本番環境でのトラブルシューティングでは、権限と手順に従って使用できる。「不安定な ABI」だからといって、本番環境で絶対に読み取れないわけではない。debugfs ノードを作成するドライバは、ユーザーがオープンした参照を保持している場合のデバイス削除やデータ解放も考慮する必要がある。

tracefs はトレース機能のために独立したマウントを提供する。trace と trace_pipe の読み取り方法は異なる。後者は新しいイベントを待機してデータを消費できるため、cat がすぐに終了しない場合がある。イベントスイッチ、バッファサイズ、または tracing_on を変更すると、システムのトレース状態が変更される。特定のノードを公開するかどうかは、設定、マウント、および権限によって制限される。debugfs および ftrace ドキュメント を参照。

5. tmpfs:一時とは、スペースを占有しないこと、またはディスクに触れないことを意味しない

tmpfs は shmem メカニズムを使用してユーザーデータを保存する。通常のファイルシステムのデータブロックバックエンドはないが、スワップが許可された設定ではコンテンツをスワップアウトできる。したがって、「データは RAM のみにあり、ディスクに現れない」というのは一般的な保証ではない。アンマウントまたは再起動後、スワップに書き込まれていたり fsync が呼び出されたりしていたとしても、永続ファイルシステムのように回復することを期待してはならない。

4つの数量:クォータ、論理長、残留およびスワップアウト

数量意味
size=、nr_inodes=容量と inode 数の上限。すべてのメモリを予約するものではない
ファイルの論理サイズスパース拡張後、非常に大きくなる可能性があるが、すべてのコンテンツページを割り当てるわけではない
割り当て済みページと残留量どのくらいの内容が割り当てられ、現在 RAM にあるか
スワップアウト量コンテンツの一部はスワップバックアップによって引き受ける可能性がある

マウントクォータ、cgroup、およびシステム負荷が複合的に作用するため、マウントにまだ余裕があっても、メモリ割り当てが必ずしも成功するわけではない。tmpfs ドキュメント を参照。

/dev/shm には POSIX 共有メモリオブジェクトを保持するために tmpfs がマウントされることが多い。/run には実行状態が保存されることが多く、/tmp は tmpfs である場合もあればディスクディレクトリである場合もあり、実際の mountinfo を確認する必要がある。System V 共有メモリ、匿名共有マップなども関連する shmem メカニズムを使用するが、すべてが /dev/shm 内の可視な名前に対応するわけではない。

以下では、まずファイルの長さを変更し、その後数ページのみを書き込む。ページをスワップアウトした後、論理長、割り当て量、RAM、およびスワップを照合する。RAM を解放しても、ファイルの割り当て済みコンテンツの帳簿は同時に消去されない。

図を準備しています
ファイルは8ページあるのに、なぜ8ページのRAMを占有しないのか?

スパース長、ファイルシステム割り当てクォータ、RAM残留、スワップバックアップは、4つの異なる帳簿である。スワップアウトは残留を減らすものの、tmpfs のコンテンツ割り当てクォータを解放しない。

memfd:fd によって管理されるメモリファイル

通常の memfd_create() は、匿名のメモリファイルを提供し、mmap や fd の渡しが可能であり、seals を使用して後続の変更を制限できる。MFD_HUGETLB は異なるバックアップモードに属しており、通常の shmem パスと混同してはならない。seals はオブジェクトレベルの制約であり、単に現在の fd を読み取り専用に書き換えるものではない。既に書き込み可能な共有マップが存在する場合などは、特定の seal の追加に影響を与える可能性がある。

import errno
import fcntl
import os

fd = os.memfd_create("kb-memfd", os.MFD_CLOEXEC | os.MFD_ALLOW_SEALING)
try:
    assert os.write(fd, b"hello") == 5
    seals = (fcntl.F_SEAL_WRITE | fcntl.F_SEAL_GROW |
             fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_SEAL)
    fcntl.fcntl(fd, fcntl.F_ADD_SEALS, seals)
    assert fcntl.fcntl(fd, fcntl.F_GET_SEALS) & seals == seals
    assert os.pread(fd, 5, 0) == b"hello"
    try:
        os.pwrite(fd, b"X", 0)
    except OSError as error:
        assert error.errno == errno.EPERM
    else:
        raise AssertionError("sealed file unexpectedly writable")
    print("memfd コンテンツは読み取り可能、seal により書き換えが阻止される:通過")
finally:
    os.close(fd)

この例ではファイルシステムをマウントせず、恒久的なパスも作成しない。また、永続化や秘密データがスワップアウトされないという保証も示していない。インターフェースの詳細は memfd_create(2) を参照。

6. ramfs、rootfs、initramfs、および devtmpfs

名称起動またはストレージにおける役割
ramfsメモリファイルストレージ。通常の書き戻しバックエンドおよび tmpfs 式の容量制限がない
rootfs起動に必要な特殊なルートマウント。設定に応じて ramfs または tmpfs を使用する
initramfs初期ルートに解凍されるアーカイブコンテンツおよび起動環境

ramfs への継続的な書き込みはシステムメモリを消費する可能性がある。initramfs は、ramfs/tmpfs と並列のディスクファイルシステムフォーマットではない。rootfs と initramfs を参照。

デバイスノード:ノードの作成とハードウェアの起動は別々に考える

devtmpfs は、カーネルデバイスモデルによって基本的なデバイスノードが管理される。udev などのユーザーランド機能は、イベントに基づいて権限、名前付けリンク、およびその他のルールを適用する。ノードの基本的な作成は udev のみに帰属させることはできず、すべての起動パスでカーネルが /dev を自動的にマウントするとも限らない。カーネル設定と initramfs のユーザーランドがマウントプロセスに影響を与える。

デバイスノードは、タイプやデバイス番号などのメタデータであり、オープンすると対応するドライバに入る。ノードを削除してもハードウェアが停止するわけではなく、ノードを再作成してもドライバが存在することが保証されるわけではない。nodev マウント制限、権限、およびデバイスアクセス制御は、認可判断に参加し続ける。

7. 観測を必要な範囲に限定する

問題優先して確認すべき箇所
/tmp が実際にメモリ上にあるかどうかfindmnt -T /tmp および実際のマウントオプション
tmpfs が一杯だがシステムにまだメモリがあるマウント容量、inode クォータ、論理/実際の使用量
/proc/<pid> で予期したタスクが表示されないPID namespace、プロセスの寿命、権限、および hidepid
sysfs 属性が存在しないデバイスインスタンス、ドライババインディング、カーネル設定、および ABI ドキュメント
tracefs の読み取りが終了しないブロッキング/消費型ストリームを読み取っていないか、イベントがまだ生成されていないか

findmnt、df -t tmpfs、および定点での属性読み取りは、通常、最初の事実構築に十分である。/sys/devices の全体を再帰的に走査するのは高価であり、混乱を招く。動的インターフェースに対しては、拡張子が通常のファイルに似ているからといって、バッチ読み書きしてはならない。ソースコードは fs/proc/、fs/sysfs/、fs/kernfs/、mm/shmem.c、および drivers/base/devtmpfs.c を沿って段階的に特定できる。