对于文件系统的挂载,最重要的是理解vfsmount结构,它表示了文件系统的挂载信息。其结构如下所示。
需要特别注意的是mnt_mountpoint、mnt_root和mnt_parent。mnt_mountpoint表示文件系统的挂载点,也就是文件系统在挂在时指定的目录。mnt_root表示文件系统本身的根目录,也就是节点号为2的索引节点所对应的目录。好,现在我们来详细分析啥叫安装。
所谓安装,就是从一个存储设备上读入超级块,在内存中建立起一个super_block结构,再进而将此设备上的根目录与文件系统中已经存在的一个空白目录挂上钩。注意区别系统根目录和具体文件系统根目录。系统根目录的目录名是“/”,它只存在于内存中;而具体文件系统的根目录是没有目录名的,它存在于磁盘中,一般是节点号为2的索引节点对应的目录。系统在初始化时要将一个存储设备作为整个系统的“根设备”,它的根目录就成为整个文件系统的总根,也就是“/”。注意挂载都是将设备根目录挂在已存在的目录上。也就是说,根设备的根目录是挂载在系统总根“/”上的,其它设备根目录是挂载在其余已存在文件系统空目录上的。
举个例子。比如,我们将某个挂载硬盘分区mount -t vfat /dev/hda2 /mnt/d。实际上就是新建一个vfsmount结构作为连接件,vfsmount->mnt_sb = /dev/hda2的超级块结构;vfsmount->mntroot = /dev/hda2的"根"目录的dentry;vfsmount->mnt_mountpoint = /mnt/d的dentry; vfsmount->mnt_parent = /mnt/d所属的文件系统的vfsmount。并且把这个新建的vfsmount连入一个全局的hash表mount_hashtable中。
在路径查找中有个结构经常看到,那就是 struct nameidata nd;这个数据结构是临时性的,只用来返回搜索的结果。成功返回时,其中的指针dentry指向所找到的dentry结构,而在该dentry结构中则有指向相应的inode结构。指针mnt则指向一个vfsmount数据结构,记录着文件系统的安装点、文件系统的根目录等安装信息。
在linux文件系统路径查找中,有一个函数 __link_path_walk->follow_dotdot ,代码如下:
这个函数完成的功能是,对于路径中的当前分量,如果当前节点名是“..”,那就要往上跑到当前已经到达的节点nd->dentry的父目录中去。代码分三种情况进行讨论。
第一个if (nd->path.dentry == fs->root.dentry &&nd->path.mnt == fs->root.mnt) 表示,已到达节点nd->dentry就是本进程的根节点,这是不能再往上跑了,所以保持nd->不变。
第二个if (nd->path.dentry != nd->path.mnt->mnt_root) 表示,已到达节点nd->dentry并不是所在设备的根目录,也就意味着当前到达节点与其父目录节点在同一个设备上。因此直接把父目录节点d_parent作为当前dentry返回则可。
剩下第三种情况则是,已到达节点nd->dentry就是其所在设备上的根节点,往上跑一层就要跑到另一个设备上去了。而这里的if (parent == nd->path.mnt)是另外说明的特殊情况,即当前设备是系统根设备,则无法再往上跑到另一个设备中去了,因为已经到达“/”,所以直接返回。每个已安装的存储设备,包括根设备,都一个vfsmount结构,结构中有个指针mnt_parent指向其“父设备”,但是根设备的这个指针则指向自己,因为它再没有“父设备”了,而另一个指针mnt_mountpoint 则指向代表着安装点(一定是个目录)的dentry结构。若不是这种情况,那就要往上跑一层就要跑到另一个设备上。从文件系统的角度看,安装点与所安装设备的根目录是等价的,因为设备就是把它的根目录与安装点挂载挂钩而实现安装的。所以这种情况并不break,而要接着循环,就是要再往上一层跑到安装点的上一层目录中(而不是安装点本身)。
这两个函数我花了蛮久理解。后来发现其实很简单,就是说,如果当前找到的某个目录的dentry是被mount了的,那么就要一直顺着往下,找到mount在该目录的文件系统的,最底层一个,根目录的dentry结构。
在文件系统挂载后,代表此设备的vfsmount 变量(假设是mnt)会通过其中的mnt_hash连接到它的挂载目录所对应的dentry结构d_mounted指向的链表中。因此可以通过判断d_mounted是否为零来检测此dentry是否被mount。下面分析这两个函数。
它从 mount_hashtable表中去寻找相应的vfsmount结构。注意if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) 。其实mount在当前目录下的文件系统根目录,其mnt_mountpoint所对应的dentry所指向的inode是同一个,因为都是同一个挂载目录,inode只有一个。但是不同路径打开的dentry不一样所以要通过p->mnt_mountpoint == dentry来保证。而p->mnt_parent == mnt 很重要,它表示找的文件系统的根目录是直接mount在当前目录下的。所以在follow_mount函数中是一个循环,不断找到当前dentry直接mount下的根目录,直至最底层一个根目录。
因此follow_mount函数功能很明显,它把nd->dentry和nd->mnt更新为相应已安装文件系统的安装点和安装系统对象的地址;然后重复整个操作(几个文件系统可以安装在同一个安装点上)。