Skip to content

Commit 3d84c1e

Browse files
authored
Linux内核虚拟文件系统
1 parent 32945b2 commit 3d84c1e

File tree

1 file changed

+398
-0
lines changed

1 file changed

+398
-0
lines changed

Diff for: 文章/Linux内核虚拟文件系统.md

+398
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
## **1、虚拟文件系统的作用**
2+
3+
虚拟文件系统(VFS)是linux内核和存储设备之间的抽象层,主要有以下好处。
4+
5+
\- 简化了应用程序的开发:应用通过统一的系统调用访问各种存储介质
6+
7+
\- 简化了新文件系统加入内核的过程:新文件系统只要实现VFS的各个接口即可,不需要修改内核部分
8+
9+
## **2、虚拟文件系统的4个主要对象**
10+
11+
虚拟文件中的4个主要对象,具体每个对象的含义参见如下的详细介绍。
12+
13+
### **2.1超级块**
14+
15+
超级块(super_block)主要存储文件系统相关的信息,这是个针对文件系统级别的概念。
16+
17+
它一般存储在磁盘的特定扇区中,但是对于那些基于内存的文件系统(比如proc,sysfs),超级块是在使用时创建在内存中的。
18+
19+
超级块的定义在*:<linux/fs.h>*
20+
21+
```text
22+
/*
23+
* 超级块结构中定义的字段非常多,
24+
* 这里只介绍一些重要的属性
25+
*/
26+
struct super_block {
27+
struct list_head s_list; /* 指向所有超级块的链表 */
28+
const struct super_operations *s_op; /* 超级块方法 */
29+
struct dentry *s_root; /* 目录挂载点 */
30+
struct mutex s_lock; /* 超级块信号量 */
31+
int s_count; /* 超级块引用计数 */
32+
33+
struct list_head s_inodes; /* inode链表 */
34+
struct mtd_info *s_mtd; /* 存储磁盘信息 */
35+
fmode_t s_mode; /* 安装权限 */
36+
};
37+
38+
/*
39+
* 其中的 s_op 中定义了超级块的操作方法
40+
* 这里只介绍一些相对重要的函数
41+
*/
42+
struct super_operations {
43+
struct inode *(*alloc_inode)(struct super_block *sb); /* 创建和初始化一个索引节点对象 */
44+
void (*destroy_inode)(struct inode *); /* 释放给定的索引节点 */
45+
46+
void (*dirty_inode) (struct inode *); /* VFS在索引节点被修改时会调用这个函数 */
47+
int (*write_inode) (struct inode *, int); /* 将索引节点写入磁盘,wait表示写操作是否需要同步 */
48+
void (*drop_inode) (struct inode *); /* 最后一个指向索引节点的引用被删除后,VFS会调用这个函数 */
49+
void (*delete_inode) (struct inode *); /* 从磁盘上删除指定的索引节点 */
50+
void (*put_super) (struct super_block *); /* 卸载文件系统时由VFS调用,用来释放超级块 */
51+
void (*write_super) (struct super_block *); /* 用给定的超级块更新磁盘上的超级块 */
52+
int (*sync_fs)(struct super_block *sb, int wait); /* 使文件系统中的数据与磁盘上的数据同步 */
53+
int (*statfs) (struct dentry *, struct kstatfs *); /* VFS调用该函数获取文件系统状态 */
54+
int (*remount_fs) (struct super_block *, int *, char *); /* 指定新的安装选项重新安装文件系统时,VFS会调用该函数 */
55+
void (*clear_inode) (struct inode *); /* VFS调用该函数释放索引节点,并清空包含相关数据的所有页面 */
56+
void (*umount_begin) (struct super_block *); /* VFS调用该函数中断安装操作 */
57+
};
58+
```
59+
60+
### **2.2索引节点**
61+
62+
索引节点是VFS中的核心概念,它包含内核在操作文件或目录时需要的全部信息。
63+
64+
一个索引节点代表文件系统中的一个文件(这里的文件不仅是指我们平时所认为的普通的文件,还包括目录,特殊设备文件等等)。
65+
66+
索引节点和超级块一样是实际存储在磁盘上的,当被应用程序访问到时才会在内存中创建。
67+
68+
索引节点定义在*:<linux/fs.h>*
69+
70+
```text
71+
/*
72+
* 索引节点结构中定义的字段非常多,
73+
* 这里只介绍一些重要的属性
74+
*/
75+
struct inode {
76+
struct hlist_node i_hash; /* 散列表,用于快速查找inode */
77+
struct list_head i_list; /* 索引节点链表 */
78+
struct list_head i_sb_list; /* 超级块链表超级块 */
79+
struct list_head i_dentry; /* 目录项链表 */
80+
unsigned long i_ino; /* 节点号 */
81+
atomic_t i_count; /* 引用计数 */
82+
unsigned int i_nlink; /* 硬链接数 */
83+
uid_t i_uid; /* 使用者id */
84+
gid_t i_gid; /* 使用组id */
85+
struct timespec i_atime; /* 最后访问时间 */
86+
struct timespec i_mtime; /* 最后修改时间 */
87+
struct timespec i_ctime; /* 最后改变时间 */
88+
const struct inode_operations *i_op; /* 索引节点操作函数 */
89+
const struct file_operations *i_fop; /* 缺省的索引节点操作 */
90+
struct super_block *i_sb; /* 相关的超级块 */
91+
struct address_space *i_mapping; /* 相关的地址映射 */
92+
struct address_space i_data; /* 设备地址映射 */
93+
unsigned int i_flags; /* 文件系统标志 */
94+
void *i_private; /* fs 私有指针 */
95+
};
96+
97+
/*
98+
* 其中的 i_op 中定义了索引节点的操作方法
99+
* 这里只介绍一些相对重要的函数
100+
*/
101+
struct inode_operations {
102+
/* 为dentry对象创造一个新的索引节点 */
103+
int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
104+
/* 在特定文件夹中寻找索引节点,该索引节点要对应于dentry中给出的文件名 */
105+
struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
106+
/* 创建硬链接 */
107+
int (*link) (struct dentry *,struct inode *,struct dentry *);
108+
/* 从一个符号链接查找它指向的索引节点 */
109+
void * (*follow_link) (struct dentry *, struct nameidata *);
110+
/* 在 follow_link调用之后,该函数由VFS调用进行清除工作 */
111+
void (*put_link) (struct dentry *, struct nameidata *, void *);
112+
/* 该函数由VFS调用,用于修改文件的大小 */
113+
void (*truncate) (struct inode *);
114+
};
115+
```
116+
117+
### **2.3目录项**
118+
119+
和超级块和索引节点不同,目录项并不是实际存在于磁盘上的。
120+
121+
在使用的时候在内存中创建目录项对象,其实通过索引节点已经可以定位到指定的文件,
122+
123+
但是索引节点对象的属性非常多,在查找,比较文件时,直接用索引节点效率不高,所以引入了目录项的概念。
124+
125+
路径中的每个部分都是一个目录项,比如路径: /mnt/cdrom/foo/bar 其中包含5个目录项,/ mnt cdrom foo bar
126+
127+
每个目录项对象都有3种状态:被使用,未使用和负状态
128+
129+
\- 被使用:对应一个有效的索引节点,并且该对象由一个或多个使用者
130+
131+
\- 未使用:对应一个有效的索引节点,但是VFS当前并没有使用这个目录项
132+
133+
\- 负状态:没有对应的有效索引节点(可能索引节点被删除或者路径不存在了)
134+
135+
目录项的目的就是提高文件查找,比较的效率,所以访问过的目录项都会缓存在slab中。
136+
137+
slab中缓存的名称一般就是 dentry,可以通过如下命令查看:
138+
139+
```text
140+
[wangyubin@localhost kernel]$ sudo cat /proc/slabinfo | grep dentry
141+
dentry 212545 212625 192 21 1 : tunables 0 0 0 : slabdata 10125 10125 0
142+
```
143+
144+
目录项定义在*:<linux/dcache.h>*
145+
146+
```text
147+
/* 目录项对象结构 */
148+
struct dentry {
149+
atomic_t d_count; /* 使用计数 */
150+
unsigned int d_flags; /* 目录项标识 */
151+
spinlock_t d_lock; /* 单目录项锁 */
152+
int d_mounted; /* 是否登录点的目录项 */
153+
struct inode *d_inode; /* 相关联的索引节点 */
154+
struct hlist_node d_hash; /* 散列表 */
155+
struct dentry *d_parent; /* 父目录的目录项对象 */
156+
struct qstr d_name; /* 目录项名称 */
157+
struct list_head d_lru; /* 未使用的链表 */
158+
/*
159+
* d_child and d_rcu can share memory
160+
*/
161+
union {
162+
struct list_head d_child; /* child of parent list */
163+
struct rcu_head d_rcu;
164+
} d_u;
165+
struct list_head d_subdirs; /* 子目录链表 */
166+
struct list_head d_alias; /* 索引节点别名链表 */
167+
unsigned long d_time; /* 重置时间 */
168+
const struct dentry_operations *d_op; /* 目录项操作相关函数 */
169+
struct super_block *d_sb; /* 文件的超级块 */
170+
void *d_fsdata; /* 文件系统特有数据 */
171+
172+
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* 短文件名 */
173+
};
174+
175+
/* 目录项相关操作函数 */
176+
struct dentry_operations {
177+
/* 该函数判断目录项对象是否有效。VFS准备从dcache中使用一个目录项时会调用这个函数 */
178+
int (*d_revalidate)(struct dentry *, struct nameidata *);
179+
/* 为目录项对象生成hash值 */
180+
int (*d_hash) (struct dentry *, struct qstr *);
181+
/* 比较 qstr 类型的2个文件名 */
182+
int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
183+
/* 当目录项对象的 d_count 为0时,VFS调用这个函数 */
184+
int (*d_delete)(struct dentry *);
185+
/* 当目录项对象将要被释放时,VFS调用该函数 */
186+
void (*d_release)(struct dentry *);
187+
/* 当目录项对象丢失其索引节点时(也就是磁盘索引节点被删除了),VFS会调用该函数 */
188+
void (*d_iput)(struct dentry *, struct inode *);
189+
char *(*d_dname)(struct dentry *, char *, int);
190+
};
191+
```
192+
193+
### **2.4文件对象**
194+
195+
文件对象表示进程已打开的文件,从用户角度来看,我们在代码中操作的就是一个文件对象。
196+
197+
文件对象反过来指向一个目录项对象(目录项反过来指向一个索引节点)
198+
199+
其实只有目录项对象才表示一个已打开的实际文件,虽然一个文件对应的文件对象不是唯一的,但其对应的索引节点和目录项对象却是唯一的。
200+
201+
文件对象的定义在*: <linux/fs.h>*
202+
203+
```text
204+
/*
205+
* 文件对象结构中定义的字段非常多,
206+
* 这里只介绍一些重要的属性
207+
*/
208+
struct file {
209+
union {
210+
struct list_head fu_list; /* 文件对象链表 */
211+
struct rcu_head fu_rcuhead; /* 释放之后的RCU链表 */
212+
} f_u;
213+
struct path f_path; /* 包含的目录项 */
214+
const struct file_operations *f_op; /* 文件操作函数 */
215+
atomic_long_t f_count; /* 文件对象引用计数 */
216+
};
217+
218+
/*
219+
* 其中的 f_op 中定义了文件对象的操作方法
220+
* 这里只介绍一些相对重要的函数
221+
*/
222+
struct file_operations {
223+
/* 用于更新偏移量指针,由系统调用lleek()调用它 */
224+
loff_t (*llseek) (struct file *, loff_t, int);
225+
/* 由系统调用read()调用它 */
226+
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
227+
/* 由系统调用write()调用它 */
228+
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
229+
/* 由系统调用 aio_read() 调用它 */
230+
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
231+
/* 由系统调用 aio_write() 调用它 */
232+
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
233+
/* 将给定文件映射到指定的地址空间上,由系统调用 mmap 调用它 */
234+
int (*mmap) (struct file *, struct vm_area_struct *);
235+
/* 创建一个新的文件对象,并将它和相应的索引节点对象关联起来 */
236+
int (*open) (struct inode *, struct file *);
237+
/* 当已打开文件的引用计数减少时,VFS调用该函数 */
238+
int (*flush) (struct file *, fl_owner_t id);
239+
};
240+
```
241+
242+
### **2.5四个对象之间关系图**
243+
244+
上面分别介绍了4种对象分别的属性和方法,下面用图来展示这4个对象的和VFS之间关系以及4个对象之间的关系。
245+
246+
(这个图是根据我自己的理解画出来的,如果由错误请帮忙指出,谢谢!)
247+
248+
![img](https://pic1.zhimg.com/80/v2-47c81e353571a8203518a88ee90f82b8_720w.webp)
249+
250+
![img](https://pic3.zhimg.com/80/v2-16eb8240205240f709b4a18b26d532ae_720w.webp)
251+
252+
## **3、文件系统相关的数据结构**
253+
254+
处理上面4个主要的对象之外,VFS中还有2个专门针对文件系统的2个对象,
255+
256+
\- struct file_system_type: 用来描述文件系统的类型(比如ext3,ntfs等等)
257+
258+
\- struct vfsmount : 描述一个安装文件系统的实例
259+
260+
file_system_type 结构体位于*:<linux/fs.h>*
261+
262+
```text
263+
struct file_system_type {
264+
const char *name; /* 文件系统名称 */
265+
int fs_flags; /* 文件系统类型标志 */
266+
/* 从磁盘中读取超级块,并且在文件系统被安装时,在内存中组装超级块对象 */
267+
int (*get_sb) (struct file_system_type *, int,
268+
const char *, void *, struct vfsmount *);
269+
/* 终止访问超级块 */
270+
void (*kill_sb) (struct super_block *);
271+
struct module *owner; /* 文件系统模块 */
272+
struct file_system_type * next; /* 链表中下一个文件系统类型 */
273+
struct list_head fs_supers; /* 超级块对象链表 */
274+
275+
/* 下面都是运行时的锁 */
276+
struct lock_class_key s_lock_key;
277+
struct lock_class_key s_umount_key;
278+
279+
struct lock_class_key i_lock_key;
280+
struct lock_class_key i_mutex_key;
281+
struct lock_class_key i_mutex_dir_key;
282+
struct lock_class_key i_alloc_sem_key;
283+
};
284+
```
285+
286+
每种文件系统,不管由多少个实例安装到系统中,还是根本没有安装到系统中,都只有一个 file_system_type 结构。
287+
288+
当文件系统被实际安装时,会在安装点创建一个 vfsmount 结构体。
289+
290+
结构体代表文件系统的实例,也就是文件系统被安装几次,就会创建几个 vfsmount
291+
292+
vfsmount 的定义参见*:<linux/mount.h>*
293+
294+
```text
295+
struct vfsmount {
296+
struct list_head mnt_hash; /* 散列表 */
297+
struct vfsmount *mnt_parent; /* 父文件系统,也就是要挂载到哪个文件系统 */
298+
struct dentry *mnt_mountpoint; /* 安装点的目录项 */
299+
struct dentry *mnt_root; /* 该文件系统的根目录项 */
300+
struct super_block *mnt_sb; /* 该文件系统的超级块 */
301+
struct list_head mnt_mounts; /* 子文件系统链表 */
302+
struct list_head mnt_child; /* 子文件系统链表 */
303+
int mnt_flags; /* 安装标志 */
304+
/* 4 bytes hole on 64bits arches */
305+
const char *mnt_devname; /* 设备文件名 e.g. /dev/dsk/hda1 */
306+
struct list_head mnt_list; /* 描述符链表 */
307+
struct list_head mnt_expire; /* 到期链表的入口 */
308+
struct list_head mnt_share; /* 共享安装链表的入口 */
309+
struct list_head mnt_slave_list;/* 从安装链表 */
310+
struct list_head mnt_slave; /* 从安装链表的入口 */
311+
struct vfsmount *mnt_master; /* 从安装链表的主人 */
312+
struct mnt_namespace *mnt_ns; /* 相关的命名空间 */
313+
int mnt_id; /* 安装标识符 */
314+
int mnt_group_id; /* 组标识符 */
315+
/*
316+
* We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
317+
* to let these frequently modified fields in a separate cache line
318+
* (so that reads of mnt_flags wont ping-pong on SMP machines)
319+
*/
320+
atomic_t mnt_count; /* 使用计数 */
321+
int mnt_expiry_mark; /* 如果标记为到期,则为 True */
322+
int mnt_pinned; /* "钉住"进程计数 */
323+
int mnt_ghosts; /* "镜像"引用计数 */
324+
#ifdef CONFIG_SMP
325+
int *mnt_writers; /* 写者引用计数 */
326+
#else
327+
int mnt_writers; /* 写者引用计数 */
328+
#endif
329+
};
330+
```
331+
332+
## **4、进程相关的数据结构**
333+
334+
以上介绍的都是在内核角度看到的 VFS 各个结构,所以结构体中包含的属性非常多。
335+
336+
而从进程的角度来看的话,大多数时候并不需要那么多的属性,所有VFS通过以下3个结构体和进程紧密联系在一起。
337+
338+
\- struct files_struct :由进程描述符中的 files 目录项指向,所有与单个进程相关的信息(比如打开的文件和文件描述符)都包含在其中。
339+
340+
\- struct fs_struct :由进程描述符中的 fs 域指向,包含文件系统和进程相关的信息。
341+
342+
\- struct mmt_namespace :由进程描述符中的 mmt_namespace 域指向。
343+
344+
struct files_struct 位于*:<linux/fdtable.h>*
345+
346+
```text
347+
struct files_struct {
348+
atomic_t count; /* 使用计数 */
349+
struct fdtable *fdt; /* 指向其他fd表的指针 */
350+
struct fdtable fdtab;/* 基 fd 表 */
351+
spinlock_t file_lock ____cacheline_aligned_in_smp; /* 单个文件的锁 */
352+
int next_fd; /* 缓存下一个可用的fd */
353+
struct embedded_fd_set close_on_exec_init; /* exec()时关闭的文件描述符链表 */
354+
struct embedded_fd_set open_fds_init; /* 打开的文件描述符链表 */
355+
struct file * fd_array[NR_OPEN_DEFAULT]; /* 缺省的文件对象数组 */
356+
};
357+
```
358+
359+
struct fs_struct 位于*:<linux/fs_struct.h>*
360+
361+
```text
362+
struct fs_struct {
363+
int users; /* 用户数目 */
364+
rwlock_t lock; /* 保护结构体的读写锁 */
365+
int umask; /* 掩码 */
366+
int in_exec; /* 当前正在执行的文件 */
367+
struct path root, pwd; /* 根目录路径和当前工作目录路径 */
368+
};
369+
```
370+
371+
struct mmt_namespace 位于*:<linux/mmt_namespace.h>*
372+
373+
但是在2.6内核之后似乎没有这个结构体了,而是用 struct nsproxy 来代替。
374+
375+
以下是 struct task_struct 结构体中关于文件系统的3个属性。
376+
377+
struct task_struct 的定义位于*:<linux/sched.h>*
378+
379+
```text
380+
/* filesystem information */
381+
struct fs_struct *fs;
382+
/* open file information */
383+
struct files_struct *files;
384+
/* namespaces */
385+
struct nsproxy *nsproxy;
386+
```
387+
388+
## **5、小结**
389+
390+
VFS 统一了文件系统的实现框架,使得在linux上实现新文件系统的工作变得简单。
391+
392+
目前linux内核中已经支持60多种文件系统,具体支持的文件系统可以查看 内核源码 fs 文件夹下的内容。
393+
394+
---
395+
396+
版权声明:本文为知乎博主「[极致Linux内核](https://www.zhihu.com/people/linuxwang-xian-sheng)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
397+
398+
原文链接:https://zhuanlan.zhihu.com/p/549601357

0 commit comments

Comments
 (0)