@@ -5,7 +5,7 @@ categories:
55 - 内核层
66abbrlink : f548d964
77date : 2024-12-24 16:05:00
8- updated : 2024-12-27 17:40 :00
8+ updated : 2024-12-30 16:05 :00
99---
1010
1111<meta name =" referrer " content =" no-referrer " />
@@ -48,23 +48,23 @@ Linux 存储栈的整体结构图如下。从上到下分别是:**VFS、通用
4848
4949# 常见的文件系统模型
5050
51- 任何实现的文件系统都需要包含这几种明确定义的类型:superblock 、inode、file 和 dentry。这些也是文件系统的元数据。
51+ 任何实现的文件系统都需要包含这几种明确定义的类型:super_block 、inode、file 和 dentry。这些也是文件系统的元数据。
5252
5353模型实体间通过某些 VFS 子系统或内核子系统进行交互:dentry cache(目录项缓存)、inode cache(索引节点缓存)和 buffer cache(缓冲区缓存)。每个实体都被视为对象,具有关联的数据结构和指向方法表的指针。通过替换关联的方法来为每个组件引入特定的行为(类似于 C++ 的多态)。
5454
55- ## superblock
55+ ## super_block
5656
57- ** superblock 超级块存储挂载文件系统需要的信息。** 具体如下:
57+ ** super_block 超级块存储挂载文件系统需要的信息。** 具体如下:
5858
59591 . inode 和块的位置。
60602 . 文件系统块大小。
61613 . 最大文件名长度。
62624 . 最大文件大小。
63635 . 根 inode 的位置。
6464
65- 磁盘上的 superblock 通常存储在磁盘的第一个块中,即文件系统控制块。
65+ 磁盘上的 super_block 通常存储在磁盘的第一个块中,即文件系统控制块。
6666
67- 在 VFS 中,superblock 实体都保留在类型为 ` struct super_block ` 的结构列表中,方法则保留在类型为 ` struct super_operations ` 的结构中。
67+ 在 VFS 中,super_block 实体都保留在类型为 ` struct super_block ` 的结构列表中,方法则保留在类型为 ` struct super_operations ` 的结构中。
6868
6969## inode
7070
@@ -161,7 +161,7 @@ struct file_system_type
161161
162162内核中的所有 file_system_type 都是通过一根单向链表组织起来的,register_filesystem() 函数负责将新的 file_system_type 加入到这个链表中。
163163
164- 每个文件系统类型下都挂载了多个文件系统,比如 sda、sdb 都是 ext4 文件系统,这些 superblock 以链表的形式连接到 ` file_system_type->fs_supers ` 字段中。系统中所有的 superblock 也是通过一根双向链表进行连接。
164+ 每个文件系统类型下都挂载了多个文件系统,比如 sda、sdb 都是 ext4 文件系统,这些 super_block 以链表的形式连接到 ` file_system_type->fs_supers ` 字段中。系统中所有的 super_block 也是通过一根双向链表进行连接。
165165
166166<img src =" https://img-blog.csdnimg.cn/direct/2bdd2062e61a41b182de277573ab32f4.png " alt =" image-20241227120407496 " style =" zoom :75% ;" />
167167
@@ -224,7 +224,7 @@ struct dentry *minix_mount(struct file_system_type *fs_type, int flags, const ch
224224}
225225```
226226
227- # VFS 中的超级块
227+ # super_block
228228
229229## struct super_block
230230
@@ -269,7 +269,7 @@ struct super_block
269269
270270另外,` void *s_fs_info ` 可用于存储文件系统的私有数据,具体实现时候可加入自己的数据。类似于 ` struct file ` 的 ` void *private_data ` 。
271271
272- ## 超级块操作
272+ ## super_block 操作
273273
274274超级块操作由 super_block 描述,定义如下:
275275
@@ -413,7 +413,7 @@ inode 用于引用磁盘上的文件。对于进程打开的文件,使用 `str
413413
414414inode 既存在于内存中的 VFS 结构,也存在于磁盘中(UNIX、HFS 以及 NTFS 等)。VFS 中的 inode 由 ` struct inode ` 结构表示。和 VFS 中的其他结构一样,` struct inode ` 是通用结构,涵盖了所有支持的文件类型的选项,甚至包括那些没有关联磁盘实体的文件类型(例如 FAT 文件系统)。
415415
416- ## inode 结构
416+ ## struct inode
417417
418418` struct inode ` 定义如下:
419419
@@ -539,7 +539,7 @@ struct inode
539539} __randomize_layout;
540540```
541541
542- 每个文件系统都缓存了一定的 inode 数量到内存中。同一个文件系统的 inode 以双向链表连接起来,挂在 ` superblock ->s_inodes` 字段中。同时,内核中所有的 inode 被组织在了一个哈希表 inode_hashtable 上。
542+ 每个文件系统都缓存了一定的 inode 数量到内存中。同一个文件系统的 inode 以双向链表连接起来,挂在 ` super_block ->s_inodes` 字段中。同时,内核中所有的 inode 被组织在了一个哈希表 inode_hashtable 上。
543543
544544<img src =" https://img-blog.csdnimg.cn/direct/c2c096ac10cd49878899428229ebfe98.png " alt =" image-20241227141716401 " style =" zoom :75% ;" />
545545
@@ -628,24 +628,94 @@ inode 索引节点的相关操作由 `struct inode_operations` 结构描述。
628628
629629**mount 代表了一个文件系统被挂载到了某个地方。**只有被挂载的文件系统,才能通过 VFS 的目录树进行访问。
630630
631- 一个文件系统可能被多次 mount 到不同的地方,这样一个 superblock 会对应多个不同的 mount 结构,这些 mount 以双向链表的形式组织起来,挂在 superblock ->s_mounts 字段。
631+ 一个文件系统可能被多次 mount 到不同的地方,这样一个 super_block 会对应多个不同的 mount 结构,这些 mount 以双向链表的形式组织起来,挂在 super_block ->s_mounts 字段。
632632
633633被 mount 的目录称为一个 mount 点。目录也是一个 dentry,mount 通过 mnt_mountpoint 字段指向该 dentry。
634634
635635<img src="https://img-blog.csdnimg.cn/direct/7b045be8277948d1a013fbc7538c9764.png" alt="image-20241227145405084" style="zoom:75%;" />
636636
637+ 挂载点用 mountpoint 结构体表示。所有的挂载点被放到一个哈希表 mountpoint_hashtable 中,以 dentry 为键(Key),mountpoint 为值(T)。
638+
639+ <img src="https://img-blog.csdnimg.cn/direct/3ad9f59e871b42a8b5a7209a9dfe07e9.png" alt="image-20241230094606623" style="zoom:75%;" />
640+
641+ > 注:这里的 mountpoint 是一个结构体。与上面的 mount->mnt_mountpoint 不一样,上面的是一个指针,指向 dentry。
642+
643+ ```c
644+ struct mount {
645+ struct hlist_node mnt_hash;
646+ struct mount *mnt_parent;
647+ struct dentry *mnt_mountpoint;
648+ struct vfsmount mnt;
649+ union {
650+ struct rcu_head mnt_rcu;
651+ struct llist_node mnt_llist;
652+ };
653+ #ifdef CONFIG_SMP
654+ struct mnt_pcp __percpu *mnt_pcp;
655+ #else
656+ int mnt_count;
657+ int mnt_writers;
658+ #endif
659+ struct list_head mnt_mounts; /* list of children, anchored here */
660+ struct list_head mnt_child; /* and going through their mnt_child */
661+ struct list_head mnt_instance; /* mount instance on sb->s_mounts */
662+ const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
663+ struct list_head mnt_list;
664+ struct list_head mnt_expire; /* link in fs-specific expiry list */
665+ struct list_head mnt_share; /* circular list of shared mounts */
666+ struct list_head mnt_slave_list;/* list of slave mounts */
667+ struct list_head mnt_slave; /* slave list entry */
668+ struct mount *mnt_master; /* slave is on master->mnt_slave_list */
669+ struct mnt_namespace *mnt_ns; /* containing namespace */
670+ struct mountpoint *mnt_mp; /* where is it mounted */
671+ union {
672+ struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
673+ struct hlist_node mnt_umount;
674+ };
675+ struct list_head mnt_umounting; /* list entry for umount propagation */
676+ #ifdef CONFIG_FSNOTIFY
677+ struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
678+ __u32 mnt_fsnotify_mask;
679+ #endif
680+ int mnt_id; /* mount identifier */
681+ int mnt_group_id; /* peer group identifier */
682+ int mnt_expiry_mark; /* true if marked for expiry */
683+ struct hlist_head mnt_pins;
684+ struct hlist_head mnt_stuck_children;
685+ } __randomize_layout;
686+
687+ struct mountpoint {
688+ struct hlist_node m_hash;
689+ struct dentry *m_dentry;
690+ struct hlist_head m_list;
691+ int m_count;
692+ };
693+ ```
694+
695+ 同一个目录可被多个文件系统 mount。这些文件系统会相互覆盖,通过 VFS 只能看到最近那个被 mount 的文件系统。
696+
697+ 为了处理这种情况,文件系统中所有的 mount 都被组织到同一个哈希表 mount_hashtable 中。哈希表以 ` <mount, dentry> ` 为键(Key),以新的 mount 作为值(Value),将其组织起来。
698+
699+ <img src =" https://img-blog.csdnimg.cn/direct/d1d769f8a5074874ba5ebe8dfb3724de.png " alt =" image-20241230101507978 " style =" zoom :70% ;" />
700+
701+ 将上述整理以后,能得到一个整体的架构图:
702+
703+ <img src =" https://img-blog.csdnimg.cn/direct/4f0432315c2b4ca8a40b4532ca8f1632.png " alt =" image-20241230102023524 " style =" zoom :70% ;" />
704+
637705# file
638706
639707** file 结构对应于由进程打开的文件,仅存在于内存中,并与 inode 索引节点关联。** 它是最接近用户空间的 VFS 实体。结构字段包含用户空间文件的熟悉信息(访问模式、文件位置等),与之相关的操作由已知的系统调用(read, write 等)执行。
640708
641709文件操作由 ` struct file_operations ` 结构描述。文件系统的文件操作使用 ` struct inode ` 结构中的 i_fop 字段进行初始化。在打开文件时,VFS 使用 inode->i_fop 的地址初始化 ` struct file ` 结构的 f_op 字段。后续的系统调用使用存储在 file->f_op 中的值。
642710
711+ file 与 inode 的区别在于,一个文件的 inode 在内核中是唯一的,但 file 可以有多个。
712+
713+ <img src =" https://img-blog.csdnimg.cn/direct/1329e49089bf44bdacd1fe4b41b1596a.png " alt =" image-20241230104309655 " style =" zoom :70% ;" />
714+
643715# 常规文件索引节点
644716
645717使用索引节点必须要填充 inode 结构的 i_op 和 i_fop 字段。索引节点的类型决定了他要实现的操作。
646718
647- ## 常规文件索引节点操作
648-
649719一个例子是 minix 文件系统的对象实例 minix_file_operations 和 minix_file_inode_operations。
650720
651721Linux 内核实现了 generic_file_llseek()、generic_file_read_iter()、generic_file_write_iter()、generic_file_mmap() 函数,定义了一些通用的 file 操作,具体做了哪些处理可参见源码。
@@ -719,7 +789,7 @@ static int minix_setattr(struct dentry *dentry, struct iattr *attr)
7197893. 更新索引节点。
7207904. 使用 block_truncate_page() 函数,将上一个块中未使用的空间填充为零。
721791
722- ## 地址空间操作
792+ # address_space
723793
724794**进程的地址空间与文件之间有着密切的联系:程序的执行几乎完全是通过将文件映射到进程的地址空间中进行的。**这种方法非常有效且相当通用,也可以用于常规的系统调用,如 read() 和 write()。
725795
@@ -804,6 +874,10 @@ struct address_space_operations {
804874};
805875```
806876
877+ address_space 是以基数树进行组织的文件 Cache。以页为单位,` page->index = 该页在文件中的逻辑偏移 / page_size ` 。
878+
879+ <img src =" https://img-blog.csdnimg.cn/direct/1121adafe2cf4ad8808244add141f736.png " alt =" image-20241230103801350 " style =" zoom :30% ;" />
880+
807881例如 minix 文件系统的 minix_aops 结构如下:
808882
809883``` c
@@ -881,7 +955,7 @@ int block_write_full_page(struct page *page, get_block_t *get_block, struct writ
881955typedef int (get_block_t)(struct inode * inode, sector_t iblock, struct buffer_head * bh_result, int create);
882956```
883957
884- # dentry 结构体
958+ # dentry
885959
886960## struct dentry
887961
@@ -911,6 +985,10 @@ struct dentry
911985};
912986```
913987
988+ dentry 也有一个全局哈希表进行组织,与它对应的 inode 互指。
989+
990+ <img src =" https://img-blog.csdnimg.cn/direct/05f35b213dce4f42b76e03d96f836f08.png " alt =" image-20241230102953331 " style =" zoom :75% ;" />
991+
914992## dentry 操作
915993
916994dentry 最常见的操作包括:
@@ -1103,6 +1181,107 @@ if (changed)
11031181 printk(KERN_ALERT "%zu 位已更改\n", idx);
11041182```
11051183
1184+ # 流程分析
1185+
1186+ ## 文件系统整体运行流程
1187+
1188+ 1 . 加载文件系统的内核模块,在 init 中,注册需要的 file_system_type。格式化过程在内核代码没有任何体现。
1189+
1190+ 2 . 调用 mount(),挂载文件系统,通过 file_system_type 的 mount() 回调加载对应的 super_block。
1191+
1192+ 3 . 通过 super_block 的 ` s_op->alloc_inode() ` 分配一个 inode。
1193+
1194+ 4 . 分配 root 的 dentry,调用 ` dentry->d_op ` 初始化 dentry。inode_operation 和 dentry_operation 都被记录在 super_block 中,inode 和 dentry 各自在初始化时拷贝了该指针。
1195+
1196+ 5 . 设置对应的挂载点,mount 过程完成。
1197+
1198+ 6 . 应用程序 open 文件,从指定路径一级一级向下读取对应的 dentry,直到找到需要的文件的 dentry。查找时优先从 dentry 的全局唯一哈希表上查。如果哈希表没有数据,则调用 ` inode->i_op->lookup() ` 查找。如果最后发现没有这样的文件,则可能调用 ` inode->i_op->atomic_open() ` 和 ` inode->i_op->create() ` 。在确保有文件的情况下,调用 ` file->f_op->open() ` 来打开文件。file 的 address_space 和 f_op 由 inode 赋予。
1199+
1200+ 7 . read 文件。如果文件加了范围锁,则需判断是否有冲突,然后调用 ` file->f_op->read() ` 或者 ` file->f_op->read_iter() ` 。
1201+
1202+ 8 . write 文件。如果文件加了范围锁,则需判断是否有冲突,然后调用 ` file->f_op->write() ` 或者 ` file->f_op->write_iter() ` 。
1203+
1204+ 9 . close 文件,先调用 ` file->f_op->flush() ` 刷数据,然后进行异步关闭操作。
1205+
1206+ > 如果当前进程不在中断上下文且不是 kthread 线程,将该文件的 close() 操作注册到 current->task_works 中。否则,将该文件的 close() 操作注册到全局的 delayed_fput_work 中。最终,两个异步线程会调到相同的回收代码中来。如果文件设置了 FASYNC 标志,调用 ` file->f_op->fasync() ` 函数,否则调用 ` file->f_op->release() ` 函数。
1207+
1208+ 10 . 调用 umount(),卸载文件系统,触发 ` super_block->s_op->kill_sb() ` 回调。
1209+
1210+ ## path lookup 过程
1211+
1212+ path lookup 是通过用户传递的一个绝对或相对路径,来找到对应文件的 inode 的过程。典型的应用如 open() 和 mount() 的查找。
1213+
1214+ ** path lookup 总共可分为 ref-walk 和 rcu-walk 两种模式。**
1215+
1216+ RCU 模式对锁的争用更少,并发更好,但不适合所有场景(因为 RCU 可能会导致进程睡眠)。ref 模式是传统的 path lookup 方式,不容易失败。
1217+
1218+ RCU 模式为了检测 dentry 的修改(rename)带来的查询失败,每次查询都会记录 dentry->d_seq,在查询结束后会检测当前 dentry 和父目录 dentry 的 d_seq 是否改变。针对 ref 模式,每次都会锁住当前 dentry 的 d_lock,在成功查询到需要的 dentry 后,会将其引用计数加一。
1219+
1220+ 当从当前目录跳转到下一层目录时,RCU 模式会丢弃掉原来的父目录的 d_seq 记录(因为不用关心祖父目录的引用计数),而 ref 模式则会丢弃对当前目录的引用。
1221+
1222+ ## mount 过程
1223+
1224+ mount 系统调用定义如下。更多细节参考博客 [ https://blog.csdn.net/bingyu880101/article/details/50481507 ] ( https://blog.csdn.net/bingyu880101/article/details/50481507 ) 。
1225+
1226+ ``` c
1227+ #include < sys/mount.h>
1228+
1229+ // source:要挂上的文件系统的名字,通常是一个设备名。
1230+ // target:文件系统要挂在的目标目录。
1231+ // filesystemtype:挂载的文件系统类型,如 "ext4"、"btrfs"、"msdos"、"proc"、"nfs"、"iso9660"、"vfat" 等。
1232+ // mountflags:指定文件系统的读写访问标志。
1233+ // data:文件系统持有的参数。
1234+ int mount (const char * source, const char * target, const char * filesystemtype, unsigned long mountflags, const void * _ Nullable data);
1235+ ```
1236+
1237+ mount 的过程具体如下:
1238+
1239+ 1. 根据 dir_name,进行 path lookup。
1240+
1241+ 2. 根据 type,查找对应的 file_system_type。
1242+
1243+ 3. 拿到 file_system_type,调 mount() 回调,将 dev_name 对应的块设备和 data 传递给它,mount() 回调将建立好对应的 root 的 dentry,super_block 和 root 的 inode。
1244+
1245+ 4. 新建 mount 结构体,将 dentry 与 mount 结构体绑定。
1246+
1247+ 5. 在 mount_hashtable 中不断查找。如果找到匹配的 mount 结构体,说明该挂载点已被使用,需要继续查找。直到找不到对应的 mount 结构体,说明当前挂载点尚未被占用,系统可以在此挂载新的文件系统。此时系统最后得到有效的 mount 结构体就作为当前 mount 结构体的父挂载点,得到的 dentry 作为当前 mount 结构体的 mountpoint。
1248+
1249+ 6. 建立父 mount 与当前 mount 的联系,建立 mountpoint 与当前 mount 的联系。
1250+
1251+ ## open 过程
1252+
1253+ open 主要的流程如下:
1254+
1255+ 1. 分析 open 传进来的 flags。
1256+
1257+ 2. 分配 fd。
1258+
1259+ 3. 对文件执行 open、create 等操作(视具体情况而定)。
1260+
1261+ 4. 通知监控文件打开的回调。
1262+
1263+ 5. 将打开得到的 file 结构体放到 fdtable 的 fd 数组中。
1264+
1265+ open 具体查找文件 inode 的过程,即是 path lookup 的过程。
1266+
1267+ `file->f_op` 有一个 atomic_open() 回调,允许文件系统以与原子的方式查找某个文件。如果该文件不存在,则文件系统尝试创建该文件(当设置了 O_CREAT 标志时)。故在尝试查找和创建文件时,VFS 优先使用 atomic_open(),当文件系统不支持该操作时,才回归到先 lookup,查找失败再 create 的模式。
1268+
1269+ ## 通用 read 流程
1270+
1271+ 这里不考虑文件的异步读写,也就是 aio 系列的 read 和 write。
1272+
1273+ 所谓通用,是指某些文件系统不单独写 read() 或 read_iter() 回调,而是调 VFS 实现的默认 read 函数 generic_file_read_iter()。
1274+
1275+ 读分为两种,一种是 DIRECT_IO,另一种是走 address_space。
1276+
1277+ ### DIRECT_IO
1278+
1279+ 如果这个 read 操作不能陷入等待(NO_WAIT),且要读取的文件范围内有缓存,则返回 -EAGAIN。否则,先将缓存的数据刷下去,再调用 `mapping->a_ops->direct_io` 读取数据。
1280+
1281+ ### address_space
1282+
1283+ TODO
1284+
11061285# 参考文章
11071286
110812871. [Linux Kernel Teaching — The Linux Kernel documentation](https://linux-kernel-labs.github.io/refs/heads/master/)
0 commit comments