讨论linuxVFS是个很沉重的话题, 个人觉得,从源码上分析确实不太明智,第一,看完分析完就忘,第二,太浪费时间,懂了后也无法应用在实际场合中,所以,理清脉络个人觉得对内核的学习是最重要的,理清实现的思路,之后在以后真的要应用时详细的分析代码细节,所以本文讨论VFS主要围绕实现机制,围绕以下几点来说明
1.什么是VFS
2.inode, dentry
3.文件系统的注册,挂载
4.如何实现不同文件系统之间的COPY
下面还是逐条分析
■什么是VFS
VFS是软件, 一个什么样的软件呢? 是一个用来管理多个实际文件系统的软件
比如linux系统下有两个实际的文件系统, 一个fat32类型磁盘A , 一个ext3类型磁盘B, 如果我要将A上的一个文件1.txt拷贝到B中去,我只需要在中端敲击命令 cp path1/1.txt path2
就可以了, 而底层会如何做的?
1.根据path1找到对应的1.txt文件标识(inode 1)
2.调用inode 1对应的拷贝函数(此拷贝函数对应的是fat32类型的),将磁盘中的1.txt内容读入高速缓存中
3.根据path2找到对应目的地的inode 2
4.调用inode 2对应的拷贝函数(此拷贝函数对应的是ext3类型的),将高速缓存中1.txt内容拷贝到path2指定的目的地
这1-4的过程就是VFS其中的一部分
■inode, dentry
关于inode,dentry,网上的解释也是一堆一堆的,说下我自己的理解
在实际文件系统中,即磁盘中,有inode存储区域, 这个存储区域中每一个inode代表着一个文件,这里具体以minix3文件系统来举例
以linux3.2.0, ext4文件系统为例,当执行挂载命令时
调用关系如下
SYSCALL_DEFINE5
|
|----do_mount
|
|----do_new_mount
|----do_kern_mount
| |
| |----vfs_kern_mount
| |
| |----mount_fs
| |
| |----type->mount(ext4_mount)
|----do_add_mount
接下来分析一下这些函数都是做什么用的
1.do_mount
当执行mount -t ext4..挂载命令时, 会执行此函数
因为mount命令有很多的形式,比如remount的等等,因此,do_mount会对这些情况作区分,当然如果紧紧是挂载,那么会执行do_new_mount的分支
2.do_new_mount
定义一个 vfsmount结构的变量mnt,
调用vfs_kern_mount返回挂载后的mnt: mnt = do_kern_mount
do_add_mount将mnt链到内核维护的vfsmount结构中,这个地方多说一嘴,可能不太对,因为源码看的比较糙
do_add_mount(mnt, path, mnt_flags);
这个函数参数mnt为vfsmount结构, 其中mnt与挂载的根dentry: root已经建立的联系,path是挂载的路径名称
这个函数是将挂载的路径名称与mnt建立了联系
举个例子:假设 /home/abc/1.txt 这里abc目录是一个挂载点,在打开1.txt的过程中获取到了abc目录的inode,通过inode发现是个挂载点
那么内核会在维护vfsmount的hash里去找,会根据路径"/home/abc"去找对应的vfsmnount结构mnt,找到后,获取vfsmount中的dentry,即mnt->mnt_root
即挂载点的entry,之后找到对应的inode,即dentry->d_inode,之后找到对应的1.txt文件,如果大家这个地方不太懂可以往下看,回头再来看这段描述
3.do_kern_mount
首先通过struct file_system_type *type = get_fs_type(fstype);
获取type类型,这个操作是根据什么来的呢?
①.在super.c文件中的最后
module_init(ext4_init_fs)
module_exit(ext4_exit_fs)
所以在系统刚启动时,会调用ext4_init_fs
②.在ext4_init_fs中会调用函数register_filesystem(&ext4_fs_type);
对ext4_fs_type进行注册,会将ext4_fs_type中的.name="ext4"注册到内核的全局变量file_systems
中
③.ext4_fs_type
ext4_fs_type是提前定义好的
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
.mount = ext4_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
所以,get_fs_type(fstype);函数中 fstype传入的是"ext4",函数会在file_systems
中寻找"ext4"对应的fs_type即 ext4_fs_type,之后调用vfs_kern_mount
4.vfs_kern_mount
①.首先定义两个变量
struct vfsmount *mnt; //完成挂载后的mnt
struct dentry *root; //完成挂载后的root dentry
②.malloc出一个mnt,根据名字来分配
mnt = alloc_vfsmnt(name); 其中name是设备名,比如在/dev/sdaxx的mem_cache中分配一个地方
③.获取根dentry, root = mount_fs(type, flags, name, data);
④.建立 mnt与root的关系
mnt->mnt_root = root;
mnt->mnt_sb = root->d_sb;
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt; //在do_add_mount会对mnt_parent进行更改
5.mount_fs没什么好说的,调用mount_fs
6.mount_fs
调用type->mount,其中type是do_kern_mount传下来的,即ext4_fs_type
最后调用ext4_mount也就是我们要重点讨论的对象
对于挂载来说, 我们要研究一下ext4_mount这个函数,也就是VFS比较重要的地方都在这里了,当然我们也是本着理清脉络的方式去研究
下图给出调用关系
ext4_mount
|
|----mount_bdev
|
|----ext4_fill_super
|
|----ext4_iget
|
|----d_alloc_root
|
|----__d_alloc
|----d_instantiate
对于ext4_mount来讲,我认为比较重要的地方如上
这里说明一下ext4_fill_super函数
1.ext4_fill_super
首先获取了超级快结构sb
接着调用root = ext4_iget(sb, EXT4_ROOT_INO);获取根"/"的inode
2.ext4_iget
从磁盘获取root的inode后(因为"/"是目录,即对inode的i_op进行赋值)
inode->i_op = &ext4_dir_inode_operations;
inode->i_fop = &ext4_dir_operations; 这里便是VFS重点所在
举个例子: 当我们把ext4类型的磁盘设备挂载到根目录"/"下,由于挂载时将inode赋值成ext4类型
在"/"目录下创建文件1.txt
那么"/"所对饮inode,即调用inode->i_op->ext4_create去创建文件
ext4_create函数会先创建1.txt对应的inode
inode = ext4_new_inode(handle, dir, mode, &dentry->d_name, 0, NULL);
然后对inode的i_op赋值成ext4类型
inode->i_op = &ext4_file_inode_operations;
inode->i_fop = &ext4_file_operations;
3.d_alloc_root
创建一个dentry名字为"/"
将inode与entry建立联系
1.首先要明确/ 与/abc是两个挂载点
ext4磁盘挂载到了/下 ,所以挂载后/的inode操作对应的是ext4的操作
fat32磁盘挂载到了 /abc下 ,我们知道abc文件夹的生成时通过"/"的ext4_create创建的
挂载了fat32后, 在abc inode下的s_mount会被置1, 等目录搜索到/abc时会找到vfsmountB
从而得到dentry->i_node,即找到在/abc在挂载的"/"的inode,于是换成此"/"目录的inode操作函数,即fat32_create等去操作文件
总结:通过以上脉络分析,我们总结如下
1. 一个文件对应一个inode与entry结构,
2. 因为文件与文件的不同对应的inode->i_op不同 ==>这就是VFS的精髓
比如打开一个文件A,调用这个文件的inode-i_fop->open
那么对应的这个open函数完全是根据A的类型来,如果A是ext4类型,则调用ext4_open
如果是个字符设备,则调用字符设备open函数,即驱动中实现的open函数,关于字符设备的open函数机制也是VFS的一部分,在这里不作讨论
1.什么是VFS
2.inode, dentry
3.文件系统的注册,挂载
4.如何实现不同文件系统之间的COPY
下面还是逐条分析
■什么是VFS
VFS是软件, 一个什么样的软件呢? 是一个用来管理多个实际文件系统的软件
比如linux系统下有两个实际的文件系统, 一个fat32类型磁盘A , 一个ext3类型磁盘B, 如果我要将A上的一个文件1.txt拷贝到B中去,我只需要在中端敲击命令 cp path1/1.txt path2
就可以了, 而底层会如何做的?
1.根据path1找到对应的1.txt文件标识(inode 1)
2.调用inode 1对应的拷贝函数(此拷贝函数对应的是fat32类型的),将磁盘中的1.txt内容读入高速缓存中
3.根据path2找到对应目的地的inode 2
4.调用inode 2对应的拷贝函数(此拷贝函数对应的是ext3类型的),将高速缓存中1.txt内容拷贝到path2指定的目的地
这1-4的过程就是VFS其中的一部分
■inode, dentry
关于inode,dentry,网上的解释也是一堆一堆的,说下我自己的理解
在实际文件系统中,即磁盘中,有inode存储区域, 这个存储区域中每一个inode代表着一个文件,这里具体以minix3文件系统来举例
每个文件都有一个inode与dentry,而dentry是用来搜索文件用
dentry在VFS中,它的存在是为了路径的搜索, 比如/mnt/1.txt
/ mnt/ 1.txt
对应的map是
dentry=/ dentry=mnt/ dentry=1.txt
路径结构关系: dentry("/")<--(parent)--dentry("mnt/")<--(parent)--dentry("1.txt")
也就是说,open文件时,根据dentry关系去寻找,最后找到1.txt对应的dentry
对于1.txt来说
dentry->d_inode == 1.txt的inode
下图给出 inode与dentry的关系
以linux3.2.0, ext4文件系统为例,当执行挂载命令时
调用关系如下
SYSCALL_DEFINE5
|
|----do_mount
|
|----do_new_mount
|----do_kern_mount
| |
| |----vfs_kern_mount
| |
| |----mount_fs
| |
| |----type->mount(ext4_mount)
|----do_add_mount
接下来分析一下这些函数都是做什么用的
1.do_mount
当执行mount -t ext4..挂载命令时, 会执行此函数
因为mount命令有很多的形式,比如remount的等等,因此,do_mount会对这些情况作区分,当然如果紧紧是挂载,那么会执行do_new_mount的分支
2.do_new_mount
定义一个 vfsmount结构的变量mnt,
调用vfs_kern_mount返回挂载后的mnt: mnt = do_kern_mount
do_add_mount将mnt链到内核维护的vfsmount结构中,这个地方多说一嘴,可能不太对,因为源码看的比较糙
do_add_mount(mnt, path, mnt_flags);
这个函数参数mnt为vfsmount结构, 其中mnt与挂载的根dentry: root已经建立的联系,path是挂载的路径名称
这个函数是将挂载的路径名称与mnt建立了联系
举个例子:假设 /home/abc/1.txt 这里abc目录是一个挂载点,在打开1.txt的过程中获取到了abc目录的inode,通过inode发现是个挂载点
那么内核会在维护vfsmount的hash里去找,会根据路径"/home/abc"去找对应的vfsmnount结构mnt,找到后,获取vfsmount中的dentry,即mnt->mnt_root
即挂载点的entry,之后找到对应的inode,即dentry->d_inode,之后找到对应的1.txt文件,如果大家这个地方不太懂可以往下看,回头再来看这段描述
3.do_kern_mount
首先通过struct file_system_type *type = get_fs_type(fstype);
获取type类型,这个操作是根据什么来的呢?
①.在super.c文件中的最后
module_init(ext4_init_fs)
module_exit(ext4_exit_fs)
所以在系统刚启动时,会调用ext4_init_fs
②.在ext4_init_fs中会调用函数register_filesystem(&ext4_fs_type);
对ext4_fs_type进行注册,会将ext4_fs_type中的.name="ext4"注册到内核的全局变量file_systems
中
③.ext4_fs_type
ext4_fs_type是提前定义好的
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
.mount = ext4_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
所以,get_fs_type(fstype);函数中 fstype传入的是"ext4",函数会在file_systems
中寻找"ext4"对应的fs_type即 ext4_fs_type,之后调用vfs_kern_mount
4.vfs_kern_mount
①.首先定义两个变量
struct vfsmount *mnt; //完成挂载后的mnt
struct dentry *root; //完成挂载后的root dentry
②.malloc出一个mnt,根据名字来分配
mnt = alloc_vfsmnt(name); 其中name是设备名,比如在/dev/sdaxx的mem_cache中分配一个地方
③.获取根dentry, root = mount_fs(type, flags, name, data);
④.建立 mnt与root的关系
mnt->mnt_root = root;
mnt->mnt_sb = root->d_sb;
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt; //在do_add_mount会对mnt_parent进行更改
5.mount_fs没什么好说的,调用mount_fs
6.mount_fs
调用type->mount,其中type是do_kern_mount传下来的,即ext4_fs_type
最后调用ext4_mount也就是我们要重点讨论的对象
对于挂载来说, 我们要研究一下ext4_mount这个函数,也就是VFS比较重要的地方都在这里了,当然我们也是本着理清脉络的方式去研究
下图给出调用关系
ext4_mount
|
|----mount_bdev
|
|----ext4_fill_super
|
|----ext4_iget
|
|----d_alloc_root
|
|----__d_alloc
|----d_instantiate
对于ext4_mount来讲,我认为比较重要的地方如上
这里说明一下ext4_fill_super函数
1.ext4_fill_super
首先获取了超级快结构sb
接着调用root = ext4_iget(sb, EXT4_ROOT_INO);获取根"/"的inode
2.ext4_iget
从磁盘获取root的inode后(因为"/"是目录,即对inode的i_op进行赋值)
inode->i_op = &ext4_dir_inode_operations;
inode->i_fop = &ext4_dir_operations; 这里便是VFS重点所在
举个例子: 当我们把ext4类型的磁盘设备挂载到根目录"/"下,由于挂载时将inode赋值成ext4类型
在"/"目录下创建文件1.txt
那么"/"所对饮inode,即调用inode->i_op->ext4_create去创建文件
ext4_create函数会先创建1.txt对应的inode
inode = ext4_new_inode(handle, dir, mode, &dentry->d_name, 0, NULL);
然后对inode的i_op赋值成ext4类型
inode->i_op = &ext4_file_inode_operations;
inode->i_fop = &ext4_file_operations;
3.d_alloc_root
创建一个dentry名字为"/"
将inode与entry建立联系
dentry->d_inode = inode;
■如何实现不同文件系统之间的COPY
1.首先要明确/ 与/abc是两个挂载点
ext4磁盘挂载到了/下 ,所以挂载后/的inode操作对应的是ext4的操作
fat32磁盘挂载到了 /abc下 ,我们知道abc文件夹的生成时通过"/"的ext4_create创建的
挂载了fat32后, 在abc inode下的s_mount会被置1, 等目录搜索到/abc时会找到vfsmountB
从而得到dentry->i_node,即找到在/abc在挂载的"/"的inode,于是换成此"/"目录的inode操作函数,即fat32_create等去操作文件
总结:通过以上脉络分析,我们总结如下
1. 一个文件对应一个inode与entry结构,
2. 因为文件与文件的不同对应的inode->i_op不同 ==>这就是VFS的精髓
比如打开一个文件A,调用这个文件的inode-i_fop->open
那么对应的这个open函数完全是根据A的类型来,如果A是ext4类型,则调用ext4_open
如果是个字符设备,则调用字符设备open函数,即驱动中实现的open函数,关于字符设备的open函数机制也是VFS的一部分,在这里不作讨论