我借用一张图来表示首个文件系统挂载上去后它们之间的关系。
内核首先挂载的文件系统为 rootfs,它的重要数据结构之间的关系在上图中已经非常清晰了,它有自己的根目录,假设我样要在这个文件系统的根目录下创建一个 /dev 目录,那么之后,各数据结构之间的关系如下图:
因为新生成了一个目录,所以会有一个 new_inode,并且会有一个 new_dentry,新的目录项结构名字叫 "dev", 它的 d_parent 即父目录为 dentry, 即刚才的根目录 "/", 现在有了 /dev,我们要在这个目录上挂载一个实际的文件系统,ext4,那么挂载之后,它们之间的结构关系如下图:
因为是一个新的文件系统,所以它会有自己的 super block (e2_sb), 因为它是挂载到 rootfs 的 /dev 目录下,所以会产生一个挂载结构体 vfsmount (e2_mnt), 并且 e2_mnt->mnt_mountpoint 指向它所挂载的目录,new_dentry,然后设置了 new_dentry->d_mounted 为 1,它还产生了自己的根目录 e2_entry 以及该目录项的结点对象 e2_inode。
有了这样的结构后,假设要访问新文件系统中的一个文件 /dev/tmp/file 时,首先,经过路径遍历,会得到 rootfs 的 /dev 目录的目录项结构 new_dentry,结果发现它的 d_mounted 为 1,表明有其它文件系统挂载到该目录下面,即 ext4, 此时通过某种算法,得到挂载到该目录项下的 e2_mnt,然后得到 e2_mnt->mnt_root,即新文件系统的根目录,然后就可以接着继续查找,整个过程衔接的天衣无缝。
那么你可能要问了,那 rootfs 挂载到哪里呢?也就是根目录项是怎么创建的呢 ?
这确实是一个 egg first or chicken first 的问题,因为 rootfs 是一个内存文件系统,在内核加载时,通过 fs/namespace.c 中的 mnt_init 最后的 init_rootfs() 及 init_mount_tree() 来初始化。在 init_rootfs 中会注册 rootfs 文件系统,在 init_mount_tree 来挂载它。让源码来告诉我们真相。
在 init_mount_tree() 中会调用 do_kern_mount("rootfs", 0, "rootfs", NULL) 来挂载 rootfs,核心方法是 vfs_kern_mount, 它会创建 vfsmount 结构体来表示一个挂载点,然后会调用,注册 rootfs 时,file_system_type 中的 get_sb 来让 rootfs 文件系统驱动自己来产生一个 super block 来填充到 vfsmount 中,(这里可以看出 VFS 的灵活性,因为某些文件系统可能没有
super block ,所以当文件系统自己实现该功能,就可以虚拟一个超级块),奥秘就在这里。对于 rootfs 中的 get_sb 其实是由函数 rootfs_get_sb 来实现的,核心函数为 get_sb_nodev, 它会创建一个 super block,并且调用 fill_super 函数指针,在这里它是 ramfs_fill_super 来实现。代码如下:
static int ramfs_fill_super(struct super_block * sb, void * data, int silent) { struct inode * inode; struct dentry * root; sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_blocksize = PAGE_CACHE_SIZE; sb->s_blocksize_bits = PAGE_CACHE_SHIFT; sb->s_magic = RAMFS_MAGIC; sb->s_op = &ramfs_ops; sb->s_time_gran = 1; inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0); if (!inode) return -ENOMEM; root = d_alloc_root(inode); if (!root) { iput(inode); return -ENOMEM; } sb->s_root = root; return 0; }它会初始化刚才的 super block,并且创建一个 inode,和一个 dentry (root),并且该 sb->s_root = root; 即 rootfs 的根目录。那么这里还是没有告诉该文件系统挂载到哪里呀,但 rootfs 的重要的数据结构都已经明了了。接着看,回到 get_sb_nodev, 它会调用 simple_set_mnt。
int simple_set_mnt(struct vfsmount *mnt, struct super_block *sb) { mnt->mnt_sb = sb; mnt->mnt_root = dget(sb->s_root); return 0; }它会设置挂载点的根目录,那么路径遍历时,当遇到目录被挂载时,找到挂载点,就知道挂载的文件系统的根目录了。
原文地址:http://blog.csdn.net/henzox/article/details/42777471