讨论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文件系统来举例

Linux vfs调用原理_文件系统

每个文件都有一个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的关系

Linux vfs调用原理_linux_02

■文件系统的注册,挂载

以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

 给出一个关系图:

Linux vfs调用原理_内核_03

我们要把 /dnw/1.txt 拷贝到 /abc下

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的一部分,在这里不作讨论