c++ new一个结构体_mount系统调用(do_new_mount->do_add_mount) [终]

996816788596194c1ec4ffebf0d60141.png

上面几篇文章简要的讲了mount过程如何通过系统调用进入内核,通过VFS层,进入到特定文件系统专有的mount处理函数,读取super block构建struct mount相关结构后返回的过程。现在我们得到了一个struct mount挂载实例,接下来要做的就是把这个新的装载实例加入到全局文件系统树中。

sys_mount - > do_mount -> do_new_mount(续)

上面在do_new_mount后沿着vfs_kern_mount -> mount_fs -> xfs_fs_mount -> mount_bdev -> xfs_fs_fill_super的顺序过了一遍,看到了mount的时候是如何利用文件系统提供的mount方法构造出一个mount结构的。现在回到do_new_mount中vfs_kern_mount函数返回的地方,如下:

static 

可以看到vfs_kern_mount返回了一个struct vfsmount结构体的指针,我们上面说了vfsmount是mount结构体中的一个成员(具体参见前面struct mount和struct vfsmount的定义)。

在说do_add_mount前我们先来说一下这里为什么要返回vfsmount这个mount结构体的成员,而不是直接返回mount结构体。对于了解gcc特性的人其实不难理解,Linux借助了gcc的一个特性,这个特性可以根据一个结构体的成员计算出这个结构体的首指针。具体可以参见内核中的container_of()函数的实现,或者直接查阅typeof这个gcc提供的关键字。来看do_add_mount的第一个参数real_mount(mnt),它的实现就是借助了container_of(),如下:

static 

它的意思就是根据struct mount中的mnt成员,计算出mnt这个变量所属的struct mount结构体的地址并返回struct mount的地址。换句话说它就是得到我们上面一直构建出的struct mount结构的首地址。

sys_mount - > do_mount -> do_new_mount -> do_add_mount

好了,现在来看do_add_mount函数:

/*

do_add_mount函数主要做两件事:

  1. lock_mount确定本次挂载要挂载到哪个父挂载实例parent的哪个挂载点mp上。
  2. 把newmnt挂载到parent的mp下,完成newmnt到全局的安装。安装后的样子就像我们前文讲述的那样。

所以说有两个地方是重要的,一个是lock_mount,一个是graft_tree。

sys_mount - > do_mount -> do_new_mount -> do_add_mount -> lock_mount

lock_mount是第一个要说的地方,上面说了他的作用不是简单的lock那么简单,在lock之前它完成了一个很重要的工作,那就是确定mountpoint的最终位置。可能这样说很难理解,我们来举了例子:

比如我们本来要执行的命令是:

# mount -t xfs /dev/sdc1 /mnt

这样看mountpoint就是/mnt,对吗?不能说不对,但是不够准确。请参考上一篇文章中讲到的关于(图解)挂载关系的内容,当多个文件系统挂载到同一个路径名下时是一种什么样的情况?对,后挂载的文件系统会在挂载到前一次挂载的文件系统的根dentry上。lock_mount这个函数的一部分逻辑就保证了在多文件系统挂载同路径的时候,让每个新挂载的文件系统都顺序的挂载(覆盖)上一次挂载实例的根dentry。

如何做到这一点得从lock_mount的实现看起:

static 

lock_mount的大部分逻辑似乎都不是在做锁操作,是的,它的主要逻辑应该类似(仅仅是类似)是这样的:

while 

为了说明白它我们不得不需要进一步说明lookup_mnt函数的作用,然后我们需要再反回来看这个逻辑,所以在此我们先命名这个逻辑为FOLLOW_MNT逻辑(记住我们这留了一个坑:)。

lookup_mnt

首先我们要知道lookup_mnt是做什么的,它是内核中很重要的一个函数,经常在路径名查找时也被用到,它的作用就是根据一个父<mount, dentry>二元组找到挂载在其下面的子文件系统的mount实例,如果没找到就返回NULL。我们在上文中说过有一个全局的mount_hashtable的哈希数组,里面保存着除根文件系统以外所有的文件系统挂载实例,而每个其中元素的索引条件就是其父文件系统的挂载实例和其挂载点的dentry,换句话说每个mount实例在保存到mount_hashtable里时,是使用其挂载点dentry和挂载点所在的父挂载实例为依据计算hash值并存入的。而lookup_mnt就是以这个为依据计算hash值并查找相应挂载点实例的。来看一下代码:

/* 

我想lookup_mnt的注释写的有点让人不太能直接看懂,特别是说它第一次成功将返回/dev/sda1,第二次将返回/dev/sda2,第三次将返回/dev/sda3,最后它将返回NULL。我们都知道一个函数只能返回一次,那么这里一连说了四个返回是怎么回事呢?实际上它忽略了说传入的参数的变化。lookup_mnt的作用就是根据path中的dentry和mnt索引到要找的子mount。至于如何达到注释中说的意思,那就需要调用lookup_mnt的前后对参数path做一些调整。以上面的lock_mount()为例,它就是调用lookup_mnt()最好的例子,我们下面接着返回lock_mount函数进行说明:

FOLLOW_MNT逻辑

我们就以上述lookup_mnt的注释中的例子来解释说明一下,lookup_mnt是如何被使用的。假如我们就按照上面注释中的例子,执行了如下操作:

# mount /dev/sda1 /mnt
# mount /dev/sda2 /mnt
# mount /dev/sda3 /mnt

现在我要再执行:

# mount /dev/sdb1 /mnt

那么follow_mnt逻辑的执行顺序就是:

  • 1、path的初始状态为:
path->mnt = 根文件系统
path->dentry = 根文件系统下的/mnt的dentry
  • 2、第一次lookup_mnt(path)返回的mnt的状态是:
mnt为/dev/sda1这个挂载实例
mnt->mnt_root为/dev/sda1这个文件系统的根dentry
  • 3、lookup_mnt返回/dev/sda1的挂载实例,然后执行:
path->mnt = mnt; 将第一个挂载在/mnt上的/dev/sda1的挂载实例赋值给path->mnt
path->dentry = mnt->mnt_root; 将/dev/sda1挂载成功后的根dentry给path->dentry
  • 4、重返lookup_mnt(path),只是这次path中的mnt和dentry变成了/dev/sda1的。
  • 5、lookup_mnt发现/dev/sda1的根dentry上也挂载的文件系统/dev/sda2,于是返回了/dev/sda2的挂载实例。
  • 6、又将/dev/sda2的挂载实例和根dentry赋值给path,重新调用lookup_mnt。
  • 7、这回lookup_mnt返回/dev/sda3的挂载实例,然后将/dev/sda3的挂载实例和根dentry赋值给path,再次调用lookup_mnt。
  • 8、这回lookup_mnt发现/dev/sda3的根dentry上没有挂载文件系统,于是返回NULL。
  • 9、lookup_mnt返回了NULL,也就是找到了本次要被挂载的挂载点,用/dev/sda3的根dentry构建挂载点结构,并让lock_mount返回。新的 /dev/sdb1将在返回后的操作中被挂载到/dev/sda3的根dentry上。

sys_mount - > do_mount -> do_new_mount -> do_add_mount ->graft_tree

上面lock_mnt帮我们找到了mountpoint和父mount实例(父mount实例需要再借助一下real_mount(path->mnt);得到,real_mount的意思我们也讲过了)。现在mp, parent都有了,我们要做的就是把newmnt和parent,mp构建到一起,比如让newmnt的mnt_parent指向parent,让newmnt的mnt_mountpoint指向mp,用parent和mp计算hash值把newmnt加入到mount_hashtable中。当然还有很多inode, dentry, super_block等等结构之间关系的构建……

graft_tree主要就是做这些事,由于具体的实现过于繁杂,就像穿针引线一样,把很多东西缝补到一起,如果解释起来会涉及太多其它的内容,这里就不详细解释了。但是不代表我没有解释,我在上一篇文章(挂载关系图解)中已经用我认为最能理解的方式描述了,掌握这些逻辑关系就可以算掌握基础了,深入研究的话就是以后的话题了,这个系列的就先结在此处了。

结束语

mount的基本知识就说到这里,后面我会说On-disk的文件系统结构或者路径查找之类的。如果有更深入的mount相关的知识,我想还是等到有机会再更新吧。文章完成的较为仓卒,只能在工作之余写两笔,但是最近笔者的工作繁多越来越忙(说多了都是泪)。写的时候很多东西还要现翻看代码的具体逻辑,尽管如此可能还是有不准确的地方,如果谁能发现还望不吝赐教。

至此,VFS mount系统调用的第一个系列,完。


更多内容请参阅:

醉卧沙场:README - 专业性文章及回答总索引

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值