目录
一、VFS四大对象基础理解
1.1 super block
1.2 inode
1.3 dentry
1.4 struct file
二、从图片理解文件打开流程
三、open动作的实质
四、systemtap验证
4.1 open创建一个新文件
4.2 打开已存在文件
五、dentry cache
六、关于open文件描述符fd
七、参考资料
一、 VFS四大对象基础理解
VFS只存在于内存中,它在系统启动时被创建,系统关闭时注销。
VFS的作用就是屏蔽各类文件系统的差异,给用户、应用程序、甚至Linux其他管理模块提供统一的接口集合。
linux虚拟文件系统四大对象:
1)超级块(super block)
2)索引节点(inode)
3)目录项(dentry)
4)文件对象(file)
1.1 super block
超级块代表了整个文件系统,超级块是文件系统的控制块,有整个文件系统信息,一个文件系统所有的inode都要连接到超级块上,可以说,一个超级块就代表了一个文件系统。
在linux内核中vfs层superblock结构体为struct super_block,当然不同文件系统的超级块会有不同,在mount时,都会转换为一个vfs层的superblock,在内存中形成一个superblock的双链表。
superblock中成员指针变量struct dentry *s_root;使用当前超级块文件系统的根目录dentry结构体
1.2 inode
inode就是索引节点数据,也称为“元数据”(也就是对文件属性的描述)。例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取或修改的时间戳,链接数量,指向存储该内容的磁盘区块的指针,文件分类等等。
同时注意:inode有两种,一种是VFS的inode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode进行转换填充到内存形式的inode。
1.3 dentry
目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。
例如:open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。
注意:目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。
dentry属于VFS和单个文件系统,与设备驱动无关。每个dentry都有一个指向其父dentry的指针,以及一个子dentry的hash链表。
1.4 struct file
文件对象:注意文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode就是唯一的,目录项也是定的!
它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。
它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。
二、从图片理解文件打开流程
示例1:
在linux 中,一切皆文件,一切都是从 / 开始的,让文件系统记住 / 的inode号; / 的block中存储 / 下子文件的名字和其对应的inode号;然后依次指向下一级。
举例:假如现在要读取 /usr/bin中的内容,顺序是 / —> usr —>bin,顺序如下图所示:
示例2:
定位属于文件的数据块意味着首先在inode表中定位其inode。在打开操作时通常不知道所需文件的inode。我们所知道的是文件的路径。例如:
int fd = open(“/ home / ealtieri / hello.txt ”,O_RDONLY);
所需的文件是hello.txt,而它的路径是 /home/ealtieri/hello.txt。
要找出属于该文件的inode,我们首先需要从根目录开始遍历其路径,直到我们到达文件的父目录数据文件。此时,我们可以找到对应的条目hello.txt,然后找到它的inode编号。
这个过程起始是逐级找到hello.txt的inode号,然后到inode树上去找hello.txt文件的数据位置信息,然后读取hello.txt的数据。
注:
使用ext文件系统,根目录inode为2
三、open动作的实质
Open操作的实质就是获取一个该文件系统的inode,然后去填充vfs层的struct file结构体,这样就可以让上层使用了。
四、systemtap验证
以xfs示例:
写一个systemtap脚本,抓取以下几个函数:
probe module("xfs").function("xfs_file_open").call,
module("xfs").function("xfs_create").call,
module("xfs").function("xfs_dinode_to_disk").call,
module("xfs").function("xfs_btree_lookup").call
{
printf("start\n");
print_backtrace();
printf("end\n\n");
}
xfs_file_open:该函数用于将一个inode填充到struct file结构体中。
xfs_create:该函数用于向xfs申请一个inode
xfs_dinode_to_disk:将inode数据落盘
xfs_btree_lookup:在xfs b+tree上遍历,xfs使用B+tree来管理inode数据、free空间等。
4.1 open创建一个新文件
Open参数:O_RDWR | O_CREAT | O_APPEND | O_SYNC
结果:
#stap --all-modules xfs_create_open.stp
start
0xffffffffa06463e0 : xfs_create+0x0/0x710 [xfs]
0xffffffffa0642d8b : xfs_vn_mknod+0xbb/0x250 [xfs] (inexact)
0xffffffffa0642f53 : xfs_vn_create+0x13/0x20 [xfs] (inexact)
0xffffffff811eac7d : vfs_mknod+0xd/0x160 [kernel] (inexact)
0xffffffff811ec30f : do_last+0xc5f/0x1270 [kernel] (inexact)
0xffffffff811c11ce : sysfs_slab_alias+0x4e/0xa0 [kernel] (inexact)
0xffffffff811ee672 : path_openat+0x132/0x490 [kernel] (inexact)
0xffffffff811efe3b : do_file_open_root+0xb/0x120 [kernel] (inexact)
0xffffffff811fc9c7 : __alloc_fd+0x107/0x130 [kernel] (inexact)
0xffffffff811dd7e3 : do_sys_open+0x163/0x1f0 [kernel] (inexact)
0xffffffff811dd8fe : sys_close+0x2e/0x50 [kernel] (inexact)
0xffffffff81645909 : kexec_purgatory+0x3319/0x6790 [kernel] (inexact)
end
start
0xffffffffa06109c0 : xfs_btree_lookup+0x0/0x4e0 [xfs]
0xffffffffa0623c36 : xfs_dialloc_ag_inobt+0xf6/0x6e0 [xfs] (inexact)
0xffffffffa0661315 : _xfs_trans_bjoin+0x45/0x60 [xfs] (inexact)
0xffffffffa0661847 : xfs_trans_read_buf_map+0x247/0x400 [xfs] (inexact)
0xffffffffa062428f : xfs_dialloc_ag+0x6f/0x310 [xfs] (inexact)
0xffffffff812f8e9d : radix_tree_locate_item+0x6d/0x170 [kernel] (inexact)
0xffffffffa0629a5a : xfs_perag_get+0x2a/0xb0 [xfs] (inexact)
0xffffffffa062541d : xfs_dialloc+0x5d/0x280 [xfs] (inexact)
0xffffffff8101717f : dump_trace+0x17f/0x2d0 [kernel] (inexact)
0xffffffffa0645c41 : xfs_ialloc+0x71/0x540 [xfs] (inexact)
0xffffffffa0652127 : kmem_zone_alloc+0x77/0x100 [xfs] (inexact)
0xffffffffa0646186 : xfs_dir_ialloc+0x76/0x280 [xfs] (inexact)
0xffffffffa065621b : xfs_log_reserve+0x15b/0x1b0 [xfs] (inexact)
0xffffffff81639b42 : sys_call_table+0x742/0xa20 [kernel] (inexact)
0xffffffffa0646664 : xfs_create+0x284/0x710 [xfs] (inexact)
0xffffffffa0642d8b : xfs_vn_mknod+0xbb/0x250 [xfs] (inexact)
0xffffffffa0642f53 : xfs_vn_create+0x13/0x20 [xfs] (inexact)
0xffffffff811eac7d : vfs_mknod+0xd/0x160 [kernel] (inexact)
0xffffffff811ec30f : do_last+0xc5f/0x1270 [kernel] (inexact)
0xffffffff811c11ce : sysfs_slab_alias+0x4e/0xa0 [kernel] (inexact)
end
start
0xffffffffa0637d80 : xfs_file_open+0x0/0x50 [xfs]
0xffffffff811dc0e7 : do_dentry_open+0x217/0x2e0 [kernel] (inexact)
0xffffffffa0637d80 : xfs_file_open+0x0/0x50 [xfs] (inexact)
0xffffffff811dc319 : dentry_open+0x39/0xd0 [kernel] (inexact)
0xffffffff811eb90d : do_last+0x25d/0x1270 [kernel] (inexact)
0xffffffff811c11ce : sysfs_slab_alias+0x4e/0xa0 [kernel] (inexact)
0xffffffff811ee672 : path_openat+0x132/0x490 [kernel] (inexact)
0xffffffff811efe3b : do_file_open_root+0xb/0x120 [kernel] (inexact)
0xffffffff811fc9c7 : __alloc_fd+0x107/0x130 [kernel] (inexact)
0xffffffff811dd7e3 : do_sys_open+0x163/0x1f0 [kernel] (inexact)
0xffffffff811dd8fe : sys_close+0x2e/0x50 [kernel] (inexact)
0xffffffff81645909 : kexec_purgatory+0x3319/0x6790 [kernel] (inexact)
End
可以看出,创建一个新的不存在文件时,会先xfs_create申请一个xfs inode出来,xfs_create会调用xfs_btree_lookup到xfs inode B+tree上去遍历查找可用xfs inode。然后将xfs inode转换为vfs层的inode。xfs_file_open就可以用这个inode去填充一个struct file了。
其实只有create inode的动作需要实际读写盘操作,也就是这一操作因为机械盘的机械设计限制,注定耗时较高。填充struct file动作只需要在内存中完成。
例NFSv2和v3中,就是将open动作转换为一个create指令下发,客户端获取到inode数据后,在客户端完成填充struct file的动作。
4.2 打开已存在文件
Open参数:O_RDWR | O_CREAT | O_APPEND | O_SYNC
结果:
#stap --all-modules xfs_create_open.stp
start
0xffffffffa0637d80 : xfs_file_open+0x0/0x50 [xfs]
0xffffffff811dc0e7 : do_dentry_open+0x217/0x2e0 [kernel] (inexact)
0xffffffffa0637d80 : xfs_file_open+0x0/0x50 [xfs] (inexact)
0xffffffff811dc319 : dentry_open+0x39/0xd0 [kernel] (inexact)
0xffffffff811eb90d : do_last+0x25d/0x1270 [kernel] (inexact)
0xffffffff811c11ce : sysfs_slab_alias+0x4e/0xa0 [kernel] (inexact)
0xffffffff811ee672 : path_openat+0x132/0x490 [kernel] (inexact)
0xffffffff811efe3b : do_file_open_root+0xb/0x120 [kernel] (inexact)
0xffffffff811fc9c7 : __alloc_fd+0x107/0x130 [kernel] (inexact)
0xffffffff811dd7e3 : do_sys_open+0x163/0x1f0 [kernel] (inexact)
0xffffffff811dd8fe : sys_close+0x2e/0x50 [kernel] (inexact)
0xffffffff81645909 : kexec_purgatory+0x3319/0x6790 [kernel] (inexact)
end
重新打开我们刚刚创建的已存在文件,可以看到,没有create动作,也没看到lookup动作,只有一个xfs_file_open,难道不需要先获取inode了,没有inode,怎么填充struct file结构体?
这就涉及到了dentry cache。
五、dentry cache
如果所有的读写都需要像第一个一样,读写已存在文件也去实际读写磁盘,那会在某些场景下极大的影响效率。
Linux为了提高目录项对象的处理效率,设计与实现了目录项高速缓存(dentry cache,简称dcache),它主要由两个数据结构组成:
- 哈希链表dentry_hashtable:dcache中的所有dentry对象都通过d_hash指针域链到相应的dentry哈希链表中。
- 未使用的dentry对象链表dentry_unused:dcache中所有处于“unused”状态和“negative”状态的dentry对象都通过其d_lru指针域链入dentry_unused链表中。该链表也称为LRU链表。
dentry cache是一个存放在内存里的缩略版的磁盘文件系统目录树结构。我们知道文件系统内的文件可能非常庞大,目录树结构可能很深,该树状结构中,可能存在几千万,几亿的文件。
当然如果将文件系统所有文件名到VFS inode的关联都记录下来,但是这么做并不现实,首先并不是所有磁盘文件的inode都会记录在内存中,其次磁盘文件数字可能非常庞大,我们无法简单地建立这种关联,耗尽所有的内存也做不到将文件树结构照搬进内存。
六、关于open文件描述符fd
fd只是一个小整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp。
文件描述符的操作(如: open)返回的是一个文件描述符,内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用; 而流(如: fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的,FILE结构函数可以看作是对fd直接操作的系统调用的封装, 它的优点是带有I/O缓存。
每个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。
七、参考资料
Linux——文件系统中inode的工作:https://blog.csdn.net/tete2csdn/article/details/75674865
Linux 深入理解inode/block/superblock :https://blog.csdn.net/Ohmyberry/article/details/80427492
VFS四大对象:https://www.cnblogs.com/linhaostudy/