本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
前回を読んでいない方はこちらからどうぞ.
Linuxカーネルの記事一覧はこちらからどうぞ.
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
今回のテーマは仮想ファイルシステム(VFS:Virtual File System)です.
仮想ファイルシステムを理解すると,OSがファイル操作を簡単にしていることがわかります.
目次
仮想ファイルシステム
仮想ファイルシステムは,Linuxがサポートするすべてのファイルシステムモデルを抽象化します.
C++の抽象ベースクラスと同様の概念になります.
ファイルシステムを抽象化すると以下ができます.
- 異なるファイルシステムの共存:例えばFAT32でフォーマットされたUSBドライブとext4でフォーマットされたHDD/SSDのrootfsを同時にマウント
- 協調:例えば,FAT32とext4ファイルシステム間でファイルをシームレスにコピー
上図に仮想ファイルシステムの概要を示します.
仮想ファイルシステムは,ユーザ空間のアプリケーション(例:cp)からシステムコールを呼び出して,カーネル空間のファイルシステムを操作する場合にアクセスします.
仮想ファイルシステムは,ファイルシステム(例:ext4)を操作するために,ハードウェアのストレージ(例:HDD/SSD)にアクセスします.
共通の(Top/Bottom)仮想ファイルシステムのインタフェース
共通の(Top/Bottom)仮想ファイルシステムのインタフェースを紹介します.
仮想ファイルシステムは,ユーザ空間が共通のインタフェースで,保存されている具体的なファイルシステムに依存せずにファイルにアクセスすることを可能にします.
標準的なシステムコールとして,open,read,write,lseek等があります.
また,ユーザ空間とのTop仮想ファイルシステムのインタフェースにより,ファイルシステム間で透過的に動作します(下図).
ファイルシステム抽象化レイヤを紹介します.
仮想ファイルシステムはユーザ空間の要求を対応する物理ファイルシステム(例:ext4,FAT32,JFFS2)にリダイレクトします.
つまり,ファイルシステム抽象化レイヤは,ファイルシステムを利用したBottom仮想ファイルシステムのインタフェースです.
Linuxカーネルで新しいファイルシステムを開発することは,Bottom仮想ファイルシステムのインタフェースに準拠することを意味します.
※Top仮想ファイルシステムのインタフェースは共通になります.
例えば,HDD/SDDでext4に書き込む場合は以下の手順になります.
- システムコール:writeシステムコール
- Top仮想ファイルシステムのインタフェース:sys_write関数
- Bottom仮想ファイルシステムのインタフェース:ext4_write_begin関数とext4_write_end関数
- HDD/SSDに書き込み:ext4_write_begin関数とext4_write_end関数の間の処理
Unixファイルシステム
Unixファイルシステムの「ファイルシステム」という用語は,ファイルシステムの種類やパーティションを指すことがあります.
下図のように,ディレクトリに整理されたファイルの階層的なツリーのことです.
ファイルとは,ファイルアドレス0からアドレス(ファイルサイズ-1)までのバイト列を並べたものです(上図).
ファイルのメタデータとして名前,アクセス権,修正日等を持ちます.
また,以下の情報を管理します.
ディレクトリは,ファイルや他のディレクトリ(サブディレクトリ)を含むフォルダのことです.
サブディレクトリを入れ子にして,パスを作成することができます.
例えば,「/home/chishiro/Desktop/file」となります(下図).
仮想ファイルシステムのデータ構造
仮想ファイルシステムのデータ構造は以下になります.
- dentry:ファイル/ディレクトリ名と,ファイルシステムのディレクトリツリーを定義する階層的なリンク
- inode:ファイル/ディレクトリのメタデータ
- file:プロセスによって開かれたファイルに関する情報
- superblock:ファイルシステムの論理パーティションに関する情報
- file_system_type:ファイルシステムタイプ(例:ext4)に関する情報
- 関連する操作(bottom仮想ファイルシステムのインタフェース):super_operations,inode_operations,dentry_operations,file_operations
superblock
superblockは,ファイルシステム(パーティション)に関するグローバルな情報を含みます(上図).
ファイルシステムで作成され,マウント時に仮想ファイルシステムに渡されます.
ディスクベースのファイルシステムはsuperblockを特別な場所に保存します.
他のファイルシステムにはマウント時に生成する方法があります.
linux/include/linux/fs.hでsuperblockを管理するsuper_block構造体があります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
struct super_block { struct list_head s_list; /* Keep this first */ dev_t s_dev; /* search index; _not_ kdev_t */ unsigned char s_blocksize_bits; unsigned long s_blocksize; loff_t s_maxbytes; /* Max file size */ struct file_system_type *s_type; const struct super_operations *s_op; const struct dquot_operations *dq_op; const struct quotactl_ops *s_qcop; const struct export_operations *s_export_op; unsigned long s_flags; unsigned long s_iflags; /* internal SB_I_* flags */ unsigned long s_magic; struct dentry *s_root; struct rw_semaphore s_umount; int s_count; atomic_t s_active; #ifdef CONFIG_SECURITY void *s_security; #endif const struct xattr_handler **s_xattr; #ifdef CONFIG_FS_ENCRYPTION const struct fscrypt_operations *s_cop; struct key *s_master_keys; /* master crypto keys in use */ #endif #ifdef CONFIG_FS_VERITY const struct fsverity_operations *s_vop; #endif #ifdef CONFIG_UNICODE struct unicode_map *s_encoding; __u16 s_encoding_flags; #endif struct hlist_bl_head s_roots; /* alternate root dentries for NFS */ struct list_head s_mounts; /* list of mounts; _not_ for fs use */ struct block_device *s_bdev; struct backing_dev_info *s_bdi; struct mtd_info *s_mtd; struct hlist_node s_instances; unsigned int s_quota_types; /* Bitmask of supported quota types */ struct quota_info s_dquot; /* Diskquota specific options */ struct sb_writers s_writers; /* * Keep s_fs_info, s_time_gran, s_fsnotify_mask, and * s_fsnotify_marks together for cache efficiency. They are frequently * accessed and rarely modified. */ void *s_fs_info; /* Filesystem private info */ /* Granularity of c/m/atime in ns (cannot be worse than a second) */ u32 s_time_gran; /* Time limits for c/m/atime in seconds */ time64_t s_time_min; time64_t s_time_max; #ifdef CONFIG_FSNOTIFY __u32 s_fsnotify_mask; struct fsnotify_mark_connector __rcu *s_fsnotify_marks; #endif char s_id[32]; /* Informational name */ uuid_t s_uuid; /* UUID */ unsigned int s_max_links; fmode_t s_mode; /* * The next field is for VFS *only*. No filesystems have any business * even looking at it. You had been warned. */ struct mutex s_vfs_rename_mutex; /* Kludge */ /* * Filesystem subtype. If non-empty the filesystem type field * in /proc/mounts will be "type.subtype" */ const char *s_subtype; const struct dentry_operations *s_d_op; /* default d_op for dentries */ /* * Saved pool identifier for cleancache (-1 means none) */ int cleancache_poolid; struct shrinker s_shrink; /* per-sb shrinker handle */ /* Number of inodes with nlink == 0 but still referenced */ atomic_long_t s_remove_count; /* * Number of inode/mount/sb objects that are being watched, note that * inodes objects are currently double-accounted. */ atomic_long_t s_fsnotify_connectors; /* Being remounted read-only */ int s_readonly_remount; /* per-sb errseq_t for reporting writeback errors via syncfs */ errseq_t s_wb_err; /* AIO completions deferred from interrupt context */ struct workqueue_struct *s_dio_done_wq; struct hlist_head s_pins; /* * Owning user namespace and default context in which to * interpret filesystem uids, gids, quotas, device nodes, * xattrs and security labels. */ struct user_namespace *s_user_ns; /* * The list_lru structure is essentially just a pointer to a table * of per-node lru lists, each of which has its own spinlock. * There is no need to put them into separate cachelines. */ struct list_lru s_dentry_lru; struct list_lru s_inode_lru; struct rcu_head rcu; struct work_struct destroy_work; struct mutex s_sync_lock; /* sync serialisation lock */ /* * Indicates how deep in a filesystem stack this SB is */ int s_stack_depth; /* s_inode_list_lock protects s_inodes */ spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp; struct list_head s_inodes; /* all inodes */ spinlock_t s_inode_wblist_lock; struct list_head s_inodes_wb; /* writeback inodes */ } __randomize_layout; |
super_operations構造体でsuperblockを操作します.
各メンバ変数は,super_block構造体を操作する関数ポインタで,主な操作関数は以下になります.
- alloc_inode関数ポインタ:alloc_inode関数から呼び出され,inode構造体のメモリを確保し,初期化します.
- destroy_inode関数ポインタ:destroy_inode関数から呼び出され,inode構造体に割り当てられているリソースを解放します.
- dirty_inode関数ポインタ:inodeがdirtyとマークされたときに仮想ファイルシステムによって呼び出されます.
- write_inode関数ポインタ:仮想ファイルシステムがディスクにinodeを書き込む必要があるときに呼び出されます.
- drop_inode関数ポインタ:inode->i_lockスピンロックを保持したまま,そのinodeへの最後のアクセスが遮断されたときに呼び出されます.
- evict_inode関数ポインタ:inodeに関するあらゆる情報をディスクとメモリから削除します(ディスク上のinodeと関連するデータブロックの両方).
- put_super関数ポインタ:仮想ファイルシステムがsuperblockを解放(アンマウント)する際に呼び出されます(superblockのロックを保持した状態).
- sync_fs関数ポインタ:仮想ファイルシステムがsuperblockに関連するすべてのdirtyデータを書き出すときに呼び出されます.
- freeze_fs関数ポインタ:仮想ファイルシステムがファイルシステムをロックし,強制的に整合性のある状態にするときに呼び出されます.この方法は現在,LVM(Logical Volume Manager)で使用されています.
- unfreeze_fs関数ポインタ:仮想ファイルシステムがファイルシステムのロックを解除し,再び書き込み可能にするときに呼び出されます.
- statfs関数ポインタ:仮想ファイルシステムがファイルシステムの統計情報を取得する必要があるときに呼び出されます.
- remount_fs関数ポインタ:ファイルシステムが再マウントされたときに呼び出されます(カーネルロックがかかった状態).
- umount_begin関数ポインタ:仮想ファイルシステムがファイルシステムをアンマウントする際に呼び出されます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
struct super_operations { struct inode *(*alloc_inode)(struct super_block *sb); void (*destroy_inode)(struct inode *); void (*free_inode)(struct inode *); void (*dirty_inode) (struct inode *, int flags); int (*write_inode) (struct inode *, struct writeback_control *wbc); int (*drop_inode) (struct inode *); void (*evict_inode) (struct inode *); void (*put_super) (struct super_block *); int (*sync_fs)(struct super_block *sb, int wait); int (*freeze_super) (struct super_block *); int (*freeze_fs) (struct super_block *); int (*thaw_super) (struct super_block *); int (*unfreeze_fs) (struct super_block *); int (*statfs) (struct dentry *, struct kstatfs *); int (*remount_fs) (struct super_block *, int *, char *); void (*umount_begin) (struct super_block *); int (*show_options)(struct seq_file *, struct dentry *); int (*show_devname)(struct seq_file *, struct dentry *); int (*show_path)(struct seq_file *, struct dentry *); int (*show_stats)(struct seq_file *, struct dentry *); #ifdef CONFIG_QUOTA ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t); ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t); struct dquot **(*get_dquots)(struct inode *); #endif long (*nr_cached_objects)(struct super_block *, struct shrink_control *); long (*free_cached_objects)(struct super_block *, struct shrink_control *); }; |
また,ファイルシステムをマウントする関数は,linux/fs/super.cのmount_bdev関数です.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
struct dentry *mount_bdev(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, int (*fill_super)(struct super_block *, void *, int)) { struct block_device *bdev; struct super_block *s; fmode_t mode = FMODE_READ | FMODE_EXCL; int error = 0; if (!(flags & SB_RDONLY)) mode |= FMODE_WRITE; bdev = blkdev_get_by_path(dev_name, mode, fs_type); if (IS_ERR(bdev)) return ERR_CAST(bdev); /* * once the super is inserted into the list by sget, s_umount * will protect the lockfs code from trying to start a snapshot * while we are mounting */ mutex_lock(&bdev->bd_fsfreeze_mutex); if (bdev->bd_fsfreeze_count > 0) { mutex_unlock(&bdev->bd_fsfreeze_mutex); error = -EBUSY; goto error_bdev; } s = sget(fs_type, test_bdev_super, set_bdev_super, flags | SB_NOSEC, bdev); mutex_unlock(&bdev->bd_fsfreeze_mutex); if (IS_ERR(s)) goto error_s; if (s->s_root) { if ((flags ^ s->s_flags) & SB_RDONLY) { deactivate_locked_super(s); error = -EBUSY; goto error_bdev; } /* * s_umount nests inside open_mutex during * __invalidate_device(). blkdev_put() acquires * open_mutex and can't be called under s_umount. Drop * s_umount temporarily. This is safe as we're * holding an active reference. */ up_write(&s->s_umount); blkdev_put(bdev, mode); down_write(&s->s_umount); } else { s->s_mode = mode; snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev); sb_set_blocksize(s, block_size(bdev)); error = fill_super(s, data, flags & SB_SILENT ? 1 : 0); if (error) { deactivate_locked_super(s); goto error; } s->s_flags |= SB_ACTIVE; bdev->bd_super = s; } return dget(s->s_root); error_s: error = PTR_ERR(s); error_bdev: blkdev_put(bdev, mode); error: return ERR_PTR(error); } EXPORT_SYMBOL(mount_bdev); |
inode
inodeは,ファイルやディレクトリに関するもので,メタデータとファイル/ディレクトリの操作方法に関する情報が含まれています(上図).
メタデータとして,ファイルサイズ,所有者ID/グループ等があります.
ファイル/ディレクトリがアクセスされたときに,ファイルシステムによってオンデマンドで生成されなければなりません.
Unix系ファイルシステムではディスクから読み込みます.
他のファイルシステムでは,ディスク上の情報から再構築されます.
linux/include/linux/fs.hのinode構造体でinodeを管理します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
/* * Keep mostly read-only and often accessed (especially for * the RCU path lookup and 'stat' data) fields at the beginning * of the 'struct inode' */ struct inode { umode_t i_mode; unsigned short i_opflags; kuid_t i_uid; kgid_t i_gid; unsigned int i_flags; #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *i_acl; struct posix_acl *i_default_acl; #endif const struct inode_operations *i_op; struct super_block *i_sb; struct address_space *i_mapping; #ifdef CONFIG_SECURITY void *i_security; #endif /* Stat data, not accessed from path walking */ unsigned long i_ino; /* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */ union { const unsigned int i_nlink; unsigned int __i_nlink; }; dev_t i_rdev; loff_t i_size; struct timespec64 i_atime; struct timespec64 i_mtime; struct timespec64 i_ctime; spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ unsigned short i_bytes; u8 i_blkbits; u8 i_write_hint; blkcnt_t i_blocks; #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif /* Misc */ unsigned long i_state; struct rw_semaphore i_rwsem; unsigned long dirtied_when; /* jiffies of first dirtying */ unsigned long dirtied_time_when; struct hlist_node i_hash; struct list_head i_io_list; /* backing dev IO list */ #ifdef CONFIG_CGROUP_WRITEBACK struct bdi_writeback *i_wb; /* the associated cgroup wb */ /* foreign inode detection, see wbc_detach_inode() */ int i_wb_frn_winner; u16 i_wb_frn_avg_time; u16 i_wb_frn_history; #endif struct list_head i_lru; /* inode LRU list */ struct list_head i_sb_list; struct list_head i_wb_list; /* backing dev writeback list */ union { struct hlist_head i_dentry; struct rcu_head i_rcu; }; atomic64_t i_version; atomic64_t i_sequence; /* see futex */ atomic_t i_count; atomic_t i_dio_count; atomic_t i_writecount; #if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING) atomic_t i_readcount; /* struct files open RO */ #endif union { const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ void (*free_inode)(struct inode *); }; struct file_lock_context *i_flctx; struct address_space i_data; struct list_head i_devices; union { struct pipe_inode_info *i_pipe; struct cdev *i_cdev; char *i_link; unsigned i_dir_seq; }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct fsnotify_mark_connector __rcu *i_fsnotify_marks; #endif #ifdef CONFIG_FS_ENCRYPTION struct fscrypt_info *i_crypt_info; #endif #ifdef CONFIG_FS_VERITY struct fsverity_info *i_verity_info; #endif void *i_private; /* fs or device private pointer */ } __randomize_layout; |
inode_operations構造体でinodeを操作します.
主な操作関数は以下になります.
- create関数ポインタ:open/creatシステムコールで呼び出され,通常のファイルをサポートしたい場合にのみ必要です.
- lookup関数ポインタ:仮想ファイルシステムが親ディレクトリのinodeを検索する必要があるときに呼び出されます.探すべき名前は,dentryで見つかります.
- link関数ポインタ:linkシステムコールで呼び出されます.ハードリンクをサポートしたい場合のみ必要です.
- unlink関数ポインタ:unlinkシステムコールによって呼び出されるます.inodeの削除をサポートしたい場合にのみ必要です.
- symlink関数ポインタ:symlinkシステムコールによって呼び出されます.symlinkをサポートしたい場合のみ必要です.
- mkdir関数ポインタ:mkdirシステムコールで呼び出されます.サブディレクトリの作成をサポートしたい場合にのみ必要です.
- rmdir関数ポインタ:rmdirシステムコールによって呼び出されます.サブディレクトリの削除をサポートしたい場合にのみ必要です.
- mknod関数ポインタ:デバイス(char,block)inode,名前付きパイプ(FIFO)またはソケットを作成するためにmknodシステムコールによって呼び出されます.これらのタイプのinodeの作成をサポートしたい場合にのみ必要です.
- rename関数ポインタ:renameシステムコールによって呼び出され,最初(第2引数と第3引数)のinodeとdentryから2番目(第4引数と第5引数)のinodeとdentryによって与えられる親と名前を持つようにオブジェクトの名前を変更します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
struct inode_operations { struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *); int (*permission) (struct user_namespace *, struct inode *, int); struct posix_acl * (*get_acl)(struct inode *, int, bool); int (*readlink) (struct dentry *, char __user *,int); int (*create) (struct user_namespace *, struct inode *,struct dentry *, umode_t, bool); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct user_namespace *, struct inode *,struct dentry *, const char *); int (*mkdir) (struct user_namespace *, struct inode *,struct dentry *, umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct user_namespace *, struct inode *,struct dentry *, umode_t,dev_t); int (*rename) (struct user_namespace *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*setattr) (struct user_namespace *, struct dentry *, struct iattr *); int (*getattr) (struct user_namespace *, const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len); int (*update_time)(struct inode *, struct timespec64 *, int); int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode); int (*tmpfile) (struct user_namespace *, struct inode *, struct dentry *, umode_t); int (*set_acl)(struct user_namespace *, struct inode *, struct posix_acl *, int); int (*fileattr_set)(struct user_namespace *mnt_userns, struct dentry *dentry, struct fileattr *fa); int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa); } ____cacheline_aligned; |
dentry(directory entry)
dentry(directory entry)はファイルまたはディレクトリを以下に関連付けるものです.
- ファイル名/ディレクトリ名を格納する
- ディレクトリ内の位置を保存する
- ディレクトリ固有の操作(例:パス名検索)を実行する
例えば,「/home/lkp/test.txt」の場合,1つのdentryは'/', 'home', 'chishiro', 'test.txt' のそれぞれに関連します.
また,ディスク表現のキャッシュは,ファイルやディレクトリのアクセスに応じてオンザフライで(処理中に)構築されます.
dentryには,used,unusedまたはnegativeの値があります.
- used:有効なinode(d_inodeが指す)に対応し,1人以上のユーザー(d_count)が存在する.メモリを解放するために破棄することはできない.
- unused:有効なinodeであるが,現在のユーザがいない.キャッシュのためにメモリに保存される.破棄可能.
- negative:有効なinodeを指していない.例えば,存在しないファイルをopenする.キャッシュのために保持される.破棄可能.
dentryは必要に応じて構築され,将来の迅速なパス名検索のためのdentry cache(dcache)と呼ばれるメモリに保存されます.
dcache(dentry cache)は,使用したdentryのinodeのi_dentryメンバ変数でリンクされたリストです.
1つのinodeは複数のリンクを持つことができ,したがって複数のdentryを持つことができます.
dcacheは,LRU(Least Recently Used)でソートされたunusedかつnegativeのdentryのリンクリストで,LRUリストの末尾から素早く再利用します.
ハッシュテーブル+ハッシュ関数で,dcacheに存在する対応するdentryに素早くパスを解決します.
dcacheのハッシュテーブルは,dentry_hashtableの配列です.
各要素は,同じ値にハッシュされたdentryのリストへのポインタです.
ハッシングは,d_hash関数で行います.
ファイルシステムは独自のハッシュ関数を提供することができます.
dcache 内のdentryを検索するのはd_lookup関数で行います.
成功すればdentryを返し,失敗すればNULLを返します.
inodeも同様にメモリ内のinodeキャッシュにキャッシュされます.
dcacheのdentryはinodeキャッシュのinodeをピン留めしています.
linux/include/linux/dcache.hのdentry_operations構造体でdentryを操作します.
dentryの操作する関数ポインタは以下になります.
- d_revalidate関数ポインタ:仮想ファイルシステムがdentryを再検証する必要があるときに呼び出されます.名前の検索でdcache内のdentryが見つかるたびに呼び出されます.
- d_weak_revalidate関数ポインタ:仮想ファイルシステムがジャンプした(選択した)dentryを再検証する必要があるときに呼び出されます.親ディレクトリのルックアップによって取得されなかったdentryでパスウォークが終了したときに呼び出されます.
- d_hash関数ポインタ:仮想ファイルシステムがハッシュテーブルにdentryを追加するときに呼び出されます.d_hash関数に渡される最初のdentryは,名前がハッシュ化される親ディレクトリです.
- d_compare関数ポインタ:あるdentryと与えられた名前を比較するために呼び出されます.
- d_delete関数ポインタ:dentryへの最後の参照が削除され,dcacheがそれをキャッシュするかどうか決定しているときに呼び出されます.
- d_init関数ポインタ:dentry割り当て時に呼び出されます.
- d_release関数ポインタ:dentryが本当に解放されたときに呼び出されます.
- d_iput関数ポインタ:dentryがinodeを失ったとき(割り当てが解放される直前)に呼び出されます.
- d_dname関数ポインタ:dentryのパス名を生成すべきときに呼び出されます.
- d_automount関数ポインタ:オートマウントデントリがトラバースする(横断する/アクセスするという意味)ときに呼び出されます(オプション).
- d_manage関数ポインタ:ファイルシステムがデントリからの移行を管理できるようにします(オプション).
- d_real関数ポインタ:overlay/union型ファイルシステムはこのメソッドを実装し,overlayによって隠された基礎となるdentryの1つを返します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct dentry_operations { int (*d_revalidate)(struct dentry *, unsigned int); int (*d_weak_revalidate)(struct dentry *, unsigned int); int (*d_hash)(const struct dentry *, struct qstr *); int (*d_compare)(const struct dentry *, unsigned int, const char *, const struct qstr *); int (*d_delete)(const struct dentry *); int (*d_init)(struct dentry *); void (*d_release)(struct dentry *); void (*d_prune)(struct dentry *); void (*d_iput)(struct dentry *, struct inode *); char *(*d_dname)(struct dentry *, char *, int); struct vfsmount *(*d_automount)(struct path *); int (*d_manage)(const struct path *, bool); struct dentry *(*d_real)(struct dentry *, const struct inode *); } ____cacheline_aligned; |
ファイルシステムのデータ構造
ファイルシステムのデータ構造を紹介します.
fileオブジェクトは,プロセスによって開かれたファイルを表します(上図).
openで生成され,closeで破棄されます.
2つのプロセスが同じファイルをオープンする場合,2つのfileオブジェクトが同じ一意なdentryを指し,そのdentry自体が一意なinodeを指します.
ディスク上のデータ構造には対応しません.
linux/include/linux/fs.hのfile構造体でファイルを管理します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u; struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; /* * Protects f_ep, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; enum rw_hint f_write_hint; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; struct mutex f_pos_lock; loff_t f_pos; struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct hlist_head *f_ep; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; errseq_t f_wb_err; errseq_t f_sb_err; /* for syncfs */ } __randomize_layout __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */ |
file_operations構造体でファイルを操作します.
ファイルを操作する主な関数ポインタは以下になります.
- llseek関数ポインタ:仮想ファイルシステムがファイル位置のインデックスを移動する必要があるときに呼び出されます.
- read関数ポインタ:readおよび関連するシステムコールによって呼び出されます.
- write関数ポインタ:writeおよび関連するシステムコールによって呼び出されます.
- read_iter関数ポインタ:iov_iterを宛先とする非同期読み出しの可能性があります.
- write_iter関数ポインタ:iov_iterをソースとする非同期書き込みの可能性があります.
- poll関数ポインタ:プロセスがこのファイルにアクティビティがあるかどうかをチェックし,(オプションで)アクティビティがあるまでスリープ状態にしたいときに仮想ファイルシステムによって呼び出されます.select/pollシステムコールによって呼び出されます.
- mmap関数ポインタ:mmapシステムコールで呼び出されます.
- open関数ポインタ:仮想ファイルシステムがinodeをオープンする際に呼び出されます.仮想ファイルシステムはファイルをオープンするとき,新しいfile構造体を作成します.そして,新たに確保されたfile構造体に対して呼び出されます.
- flush関数ポインタ:ファイルをフラッシュするためにcloseシステムコールによって呼び出されます.
- release関数ポインタ:開いているファイルへの最後の参照が閉じられたときに呼び出されます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iopoll)(struct kiocb *kiocb, bool spin); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); unsigned long mmap_supported_flags; int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int); loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int remap_flags); int (*fadvise)(struct file *, loff_t, loff_t, int); } __randomize_layout; |
linux/include/linux/fs.hのfile_system_type構造体で特定の具体的なファイルシステムタイプに関する情報を管理します.
マウントされているファイルシステムとは無関係に,サポートされているファイルシステムごとに1つです(コンパイル時に選択).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
struct file_system_type { const char *name; int fs_flags; #define FS_REQUIRES_DEV 1 #define FS_BINARY_MOUNTDATA 2 #define FS_HAS_SUBTYPE 4 #define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */ #define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */ #define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */ #define FS_THP_SUPPORT 8192 /* Remove once all fs converted */ #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ int (*init_fs_context)(struct fs_context *); const struct fs_parameter_spec *parameters; struct dentry *(*mount) (struct file_system_type *, int, const char *, void *); void (*kill_sb) (struct super_block *); struct module *owner; struct file_system_type * next; struct hlist_head fs_supers; struct lock_class_key s_lock_key; struct lock_class_key s_umount_key; struct lock_class_key s_vfs_rename_key; struct lock_class_key s_writers_key[SB_FREEZE_LEVELS]; struct lock_class_key i_lock_key; struct lock_class_key i_mutex_key; struct lock_class_key invalidate_lock_key; struct lock_class_key i_mutex_dir_key; }; |
linux/include/linux/mount.hのvfsmount構造体は,ファイルシステムがマウントされると作成されます.
vfsmount構造体は,ファイルシステムの特定のインスタンス「マウントポイント」を表します.
1 2 3 4 5 6 |
struct vfsmount { struct dentry *mnt_root; /* root of the mounted tree */ struct super_block *mnt_sb; /* pointer to superblock */ int mnt_flags; struct user_namespace *mnt_userns; } __randomize_layout; |
プロセスのデータ構造
プロセスのデータ構造を紹介します(上図).
linux/include/linux/fdtable.hのfiles_struct構造体は,オープンしたファイルやファイルディスクリプタに関するプロセス単位の情報を管理します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* * Open file table structure */ struct files_struct { /* * read mostly part */ atomic_t count; bool resize_in_progress; wait_queue_head_t resize_wait; struct fdtable __rcu *fdt; struct fdtable fdtab; /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; unsigned int next_fd; unsigned long close_on_exec_init[1]; unsigned long open_fds_init[1]; unsigned long full_fds_bits_init[1]; struct file __rcu * fd_array[NR_OPEN_DEFAULT]; }; |
linux/include/linux/fs_struct.hのfs_struct構造体は,プロセスに関連するファイルシステム情報を管理します.
1 2 3 4 5 6 7 8 |
struct fs_struct { int users; spinlock_t lock; seqcount_spinlock_t seq; int umask; int in_exec; struct path root, pwd; } __randomize_layout; |
linux/fs/mount.hのmnt_namespace構造体は,マウントされたファイルシステムのユニークなビューをプロセスに提供します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct mnt_namespace { struct ns_common ns; struct mount * root; /* * Traversal and modification of .list is protected by either * - taking namespace_sem for write, OR * - taking namespace_sem for read AND taking .ns_lock. */ struct list_head list; spinlock_t ns_lock; struct user_namespace *user_ns; struct ucounts *ucounts; u64 seq; /* Sequence number to prevent loops */ wait_queue_head_t poll; u64 event; unsigned int mounts; /* # of mounts in the namespace */ unsigned int pending_mounts; } __randomize_layout; |
まとめ
今回は仮想ファイルシステムを紹介しました.
主要なデータ構造はまとめると以下になります.
- file_system_type構造体:ファイルシステム(例:ext4)
- super_block構造体:マウントされたファイルシステムのインスタンス(つまりパーティション)
- dentry構造体:パス名
- inode構造体:ファイルのメタデータ
- file構造体:オープンファイル記述子
- address_space構造体:ノード単位のページキャッシュ
3つの鍵となるキャッシュは以下になります.
- dentryキャッシュ:dentry_hashtable,dentry->d_hash,dentry->d_hash
- inodeキャッシュ:inode_hashtable,inode->i_hash
- ページキャッシュ:inode->i_mapping
仮想ファイルシステムを深掘りしたいあなたは,以下を読みましょう!
- Overview of the Linux Virtual File System
- SFS: Random Write Considered Harmful in Solid State Drives,Slides
- NOVA: A Log-structured File System for Hybrid Volatile/Non-volatile Main Memories
- Performance and Protection in the ZoFS User-space NVM File System
- CrossFS: A Cross-layered Direct-Access File System
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
次回はこちらからどうぞ.