mount流程(普通挂载文件系统、绑定目录和设置传播属性) - 549642238/linux-stable GitHub Wiki
用户态mount工具最终调用内核系统调用接口mount -> ksys_mount -> do_mount,根据传入的Operations不同,可以分为以下情况:
1. 挂载新(不是remount)文件系统到某个目录:mount -t ext4 dev dir
2. 绑定目录或绑定挂载实例树到某个目录,让目标目录和源目录的内容一样,主要用于共享:mount --(r)bind dir_s dir_d
3. 设置传播属性,主要用于(跨命名空间)mount/umount操作传播,可以指定在其他mount实例下受到mount/umount操作影响:mount --make-(r)shared|slave|private|unbindable mount_point
4. 拷贝mount namespace,主要用于跨命名空间的文件共享(例如docker):unshare -m bash
5. 不符合mount常理的例外情况
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
char __user *, type, unsigned long, flags, void __user *, data)
{
return ksys_mount(dev_name, dir_name, type, flags, data);
}
long do_mount(const char *dev_name, const char __user *dir_name,
const char *type_page, unsigned long flags, void *data_page)
{
retval = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path); // 找到挂载路径对应的path
if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
retval = do_reconfigure_mnt(&path, mnt_flags);
else if (flags & MS_REMOUNT)
retval = do_remount(&path, flags, sb_flags, mnt_flags,
data_page);
else if (flags & MS_BIND)
retval = do_loopback(&path, dev_name, flags & MS_REC); // 绑定目录
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
retval = do_change_type(&path, flags); // 更改挂载点传播属性
else if (flags & MS_MOVE)
retval = do_move_mount_old(&path, dev_name);
else
retval = do_new_mount(&path, type_page, sb_flags, mnt_flags,
dev_name, data_page); // 挂载文件系统
}
1. do_change_type:设置装载实例的传播属性Ref
传播属性规定了装载/卸载操作在文件系统间的传递和影响,有以下四种模式
A. private:在private属性的装载实例对应的文件系统上进行mount/umount操作不影响其他装载实例对应的文件系统
B. slave:主从模式,某个装载实例对应的文件系统上进行mount/umount操作,影响其slave装载实例对应的文件系统;不影响其master装载实例对应的文件系统
C. shared:共享模式,某个装载实例对应的文件系统上进行mount/umount操作影响所有和它处于同一peer group的装载实例对应的文件系统,具有相同mnt_group_id并且有shared标志位的mnt实例同属于一个peer group
D. unbindable:不可绑定。装载实例不能被bind到其他目录下
static int do_change_type(struct path *path, int ms_flags)
{
struct mount *mnt = real_mount(path->mnt);
int recurse = ms_flags & MS_REC; // 是否递归改变其child装载实例的传播属性
if (path->dentry != path->mnt->mnt_root) // 必须对装载实例改变传播属性,普通目录不可以
return -EINVAL;
if (type == MS_SHARED) { // mount --make-(r)shared走这里
err = invent_group_ids(mnt, recurse); // 申请mnt_group_id
}
for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL)) // 改变装载实例的传播属性
change_mnt_propagation(m, type);
}
void change_mnt_propagation(struct mount *mnt, int type)
{
if (type == MS_SHARED) {
set_mnt_shared(mnt); // 设置mnt的传播属性为shared,同时清除unbindable标志,所以不可能有mount实例同时为unbindable和shared
return;
}
do_make_slave(mnt); // 设置mnt的传播属性为slave,mnt的slave也要更换master。清掉mnt的shared标志位和mnt_group_id
if (type != MS_SLAVE) {
list_del_init(&mnt->mnt_slave);
mnt->mnt_master = NULL;
if (type == MS_UNBINDABLE)
mnt->mnt.mnt_flags |= MNT_UNBINDABLE; // 设置mnt的传播属性为unbindable
else
mnt->mnt.mnt_flags &= ~MNT_UNBINDABLE; // mnt的传播属性为private,既没有master也没有MNT_UNBINDABLE和MNT_SHARED
}
}
$ fallocate -l 100M sde(n)
$ mkfs.ext4 sde(n)
$ mkdir test(n)
$ mkdir -p test/var/lib # 针对每个新装载的文件系统
$ mount sde3 test1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/test1 rw,relatime shared:29 - ext4 /dev/loop0 rw
$ mount --make-slave test1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/test1 rw,relatime - ext4 /dev/loop0 rw
$ mount sde5 test2 # 防止test1重新分配到和原来一样的mnt_group_id,只为验证make shared后test1装载实例的确重新申请了mnt_group_id
$ mount --make-shared test1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/test1 rw,relatime shared:30 - ext4 /dev/loop0 rw
80 19 7:1 / /root/test2 rw,relatime shared:29 - ext4 /dev/loop1 rw
a. make slave后装载实例test1并没有master:mount文件系统(不带选项)生成的装载实例没有mnt_master并且mnt_share所在链表只有自己一个元素,make slave调用do_make_slave清除了装载实例的shared标志位和mnt_group_id,所以既没有mnt_master也没有shared标志位在show_mountinfo就不会显示任何信息,看起是一个private装载实例
b. 重新修改为shared属性后mnt_group_id和原来不一样:make shared调用invent_group_ids发现test1装载实例既没有mnt_group_id也没有shared标志位,就为其重新分配了mnt_group_id
$ mount sde3 test1
$ mount sde5 test1/var
$ mount --make-runbindable test1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/test1 rw,relatime unbindable - ext4 /dev/loop0 rw
80 78 7:1 / /root/test1/var rw,relatime unbindable - ext4 /dev/loop1 rw
$ mount --bind test1 test2
mount: /root/test2: wrong fs type, bad option, bad superblock on /root/test1, missing codepage or helper program, or other error.
$ mount --bind test1/var test2
mount: /root/test2: wrong fs type, bad option, bad superblock on /root/test1/var, missing codepage or helper program, or other error.
a. 设置runbindable后发现test1及其子装载实例都变成unbindable:show_mountinfo会检查每个mount实例的unbindable标志,如果有就会显示"unbindable"
b. 对unbindable的装载实例进行bind操作会报错:mount --bind操作调用do_loopback检查到装载实被设置为unbindable,直接返回错误
static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
int mnt_flags, const char *name, void *data)
{
type = get_fs_type(fstype); // 获取文件系统类型,根据用户态传入-t ext4参数从全局文件系统类型链表逐个查找
if (!err)
err = vfs_get_tree(fc); // 生成具体文件系统的根目录项(作为新挂载文件系统树的root)
if (!err)
err = do_new_mount_fc(fc, path, mnt_flags); // 申请装载实例并挂载到全局文件系统树
}
Step A. vfs_get_tree -> fc->ops->get_tree -> legacy_get_tree -> fs_type->mount:调用具体文件系统mount,绑定超级块和根dentry和inode,返回根dentry
略(可以参考ubifs_mount)
static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags) // 装载newmnt到全局文件系统树
{
mp = lock_mount(path); // 申请新的挂载点节点,挂载点节点对应的dentry是最后一个挂载在path->dentry文件系统对应的根dentry,同理path->mnt也被替换成最后一个挂载的文件系统对应的mount实例
parent = real_mount(path->mnt); // path(/mnt或已挂载其他文件系统的/)对应文件系统的挂载实例
if (path->mnt->mnt_sb == newmnt->mnt.mnt_sb && path->mnt->mnt_root == path->dentry) // 不能把同一个文件系统挂载到同一个挂载点
goto unlock;
if (d_is_symlink(newmnt->mnt.mnt_root)) // 新的挂载实例对应的根目录项不应该是一个符号链接,要求具体文件系统mount返回的dentry不能是符号链接
goto unlock;
newmnt->mnt.mnt_flags = mnt_flags; // 用户态传入的挂载标志位赋值
err = graft_tree(newmnt, parent, mp); // 将文件系统装载实例安装到全局文件系统树
}
static int attach_recursive_mnt(struct mount *source_mnt, struct mount *dest_mnt, struct mountpoint *dest_mp, struct path *parent_path)
{
if (!parent_path) { // mount文件系统会走这里
err = count_mounts(ns, source_mnt); // dest_mnt实例所在mnt_namespace下的挂载实例数量加上source_mnt实例不得超过sysctl_mount_max[100000]
}
if (IS_MNT_SHARED(dest_mnt)) { // dest_mnt实例的propagation type包括shared,对应的挂载操作应该传播给同一peer group中的其他mount实例
err = invent_group_ids(source_mnt, true); // 为source_mnt实例分配mnt_group_id,因为申请后没有为它设置mnt_group_id和shared标志位
err = propagate_mnt(dest_mnt, dest_mp, source_mnt, &tree_list); // 传播mount操作到dest_mnt的peer group和slave group
for (p = source_mnt; p; p = next_mnt(p, source_mnt)) // 所有要挂载到dest_mnt实例的mount实例都要设置propagation type为shared,因为dest_mnt实例的传播属性是shared
set_mnt_shared(p);
}
if (parent_path) {
} else { // mount会走这里
mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt); // 设置source_mnt挂载实例的parent和挂载点节点
commit_tree(source_mnt); // 提交文件系统树,更新mount namespace的mount数量
}
}
int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp, struct mount *source_mnt, struct hlist_head *tree_list)
{
for (n = next_peer(dest_mnt); n != dest_mnt; n = next_peer(n)) { // 处理peer group,对所有在mnt_share链表上的其他mount实例传播mount操
ret = propagate_one(n);
}
for (m = next_group(dest_mnt, dest_mnt); m;
m = next_group(m, dest_mnt)) { // 处理slave group,对所有在mnt_slave_list链表上的mount实例传播mount操作,包括每个slave的peer group和slave group
/* everything in that slave group */
n = m;
do {
ret = propagate_one(n);
n = next_peer(n);
} while (n != m);
}
}
static int propagate_one(struct mount *m)
{
if (IS_MNT_NEW(m)) // 传播挂载操作到m时检查m是否为clone的挂载实例(来自copy_tree,可能是propagate_one或mount --(r)bind克隆生成,也可能是propagate_one传播clone生成),对clone的挂载实例传播到clone挂载实例会无限递归下去(因为clone的挂载实例可能和old mount实例处于同一shared peer group)
return 0;
if (!is_subdir(mp->m_dentry, m->mnt.mnt_root)) // 如果挂载操作生成的实例对应的挂载目录不在m挂载实例的根目录下(虽然挂载操作应该告知m,但挂载操作影响的目录在m文件系统下看不到),忽略该挂载传播
return 0;
if (peers(m, last_dest)) { // 如果m和last_dest是同一个peer group,设置clone的传播标志位为CL_MAKE_SHARED
type = CL_MAKE_SHARED;
} else { // 发生了传播操作,不是shared就是slave,这里直接设置clone的传播标志位为CL_SLAVE,但如果m还包含了shared标志位,设置clone的传播标志位为CL_MAKE_SHARED
type = CL_SLAVE;
if (IS_MNT_SHARED(m))
type |= CL_MAKE_SHARED;
}
child = copy_tree(last_source, last_source->mnt.mnt_root, type); // 克隆整个以last source为根的mount文件系统树
mnt_set_mountpoint(m, mp, child); // 将child及其子文件系统树加入到m,m是child挂载实例的parent,完成挂载操作到m的传播
}
$ fallocate -l 100M sde(n)
$ mkfs.ext4 sde(n)
$ mkdir test(n)
$ mount sde3 test1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/test1 rw,relatime shared:29 - ext4 /dev/loop0 rw
a. 生成的装载实例是shared:因为dest_mnt(/)实例是shared,所以生成的装载实例在attach_recursive_mnt因为检测到dest_mnt是shared而被置为shared,并且申请了新的mnt_group_id
$ mount --make-private sde3 test1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/test1 rw,relatime - ext4 /dev/loop0 rw
a. mount --make-private sde3 test1实际上被拆分成两条系统调用,mount sde3 test1和mount --make-private test1
3. do_loopback:绑定目录或绑定挂载实例树到某个目录,这里把要绑定的目录当成了一个设备或文件系统Ref
static int do_loopback(struct path *path, const char *old_name, int recurse)
{
err = kern_path(old_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); // 找到源目录(要绑定的目录或挂载点)对应的path
mp = lock_mount(path); // 申请新的挂载点节点,挂载点节点对应的dentry是最后一个挂载在path->dentry文件系统对应的根dentry,同理path->mnt也被替换为最后一个挂载在path的mount实例
parent = real_mount(path->mnt); // 根据vfs mount在mount中的偏移找到mount实例
mnt = __do_loopback(&old_path, recurse);
err = graft_tree(mnt, parent, mp);
}
static struct mount *__do_loopback(struct path *old_path, int recurse)
{
struct mount *mnt = ERR_PTR(-EINVAL), *old = real_mount(old_path->mnt);
if (IS_MNT_UNBINDABLE(old)) // (被mount --make-unbindable)设置了unbindable标记的mount实例不能被mount --bind
return mnt;
if (recurse) // 对于mount --rbind
mnt = copy_tree(old, old_path->dentry, CL_COPY_MNT_NS_FILE); // 递归拷贝old及其所有child mount实例
else // 对于mount --bind
mnt = clone_mnt(old, old_path->dentry, 0); // 将文件系统装载实例安装到全局文件系统树
}
Step A. lock_mount:申请新的挂载点节点,挂载点节点对应的dentry是最后一个挂载在path->dentry文件系统对应的根dentry,同理path->mnt也被替换为最后一个挂载在path的mount实例
static struct mountpoint *lock_mount(struct path *path)
{
retry:
mnt = lookup_mnt(path); // 返回第一个在path所在mount实例中path->dentry处挂载的vfs mount实例,没有就返回NULL
if (likely(!mnt)) { // 如果path所在mount实例的path->dentry处没有挂载其它文件系统
struct mountpoint *mp = get_mountpoint(dentry); // 生成挂载点节点,绑定到dentry所在mount实例文件系统下对应的目录项
}
path->mnt = mnt; // 替换path->mnt为对应的挂载实例
dentry = path->dentry = dget(mnt->mnt_root);
goto retry;
}
struct mount *copy_tree(struct mount *mnt, struct dentry *dentry, int flag) // 复制mount namespace和make --rbind时会将mnt及其所有子挂载实例拷贝,拷贝形成的clone mount实例之间的父子关系和原来一致,clone的mount实例节点的propagation type和原来对应的mount实例节点相同
{
res = q = clone_mnt(mnt, dentry, flag); // 克隆mnt挂载实例
list_for_each_entry(r, &mnt->mnt_mounts, mnt_child) { // 递归克隆mnt下所有子文件系统的挂载实例,克隆后的mnt实例组成的文件系统树和mnt实例下文件系统树一样
for (s = r; s; s = next_mnt(s, r)) { // 处理r以及r下的所有子文件系统实例,r是mnt实例某一个child,s是r树中某个节点
q = clone_mnt(p, p->mnt.mnt_root, flag);
list_add_tail(&q->mnt_list, &res->mnt_list); // 文件系统树clone得到的每个节点的和res属于同一mount namespace,这里还未指定clone mount实例的mnt_ns,所以具体属于哪个mount namespace还不知道
attach_mnt(q, parent, p->mnt_mp); // 绑定clone挂载实例的父子关系和挂载点节点
}
}
}
static struct mount *clone_mnt(struct mount *old, struct dentry *root, int flag)
{
mnt = alloc_vfsmnt(old->mnt_devname); // 克隆的mount实例沿用old挂载实例的dev_name
if (flag & (CL_SLAVE | CL_PRIVATE | CL_SHARED_TO_SLAVE))
mnt->mnt_group_id = 0; /* not a peer of original */
else
mnt->mnt_group_id = old->mnt_group_id; // mount --(r)bind走这里,生成的挂载实例和old挂载实例的mnt_group_id一样
mnt->mnt.mnt_flags = old->mnt.mnt_flags; // clone的mount实例的propagation type和原来对应的mount实例相同
if ((flag & CL_SLAVE) || ((flag & CL_SHARED_TO_SLAVE) && IS_MNT_SHARED(old))) {
} else if (!(flag & CL_PRIVATE)) { // mount --r(bind)会走这里
if ((flag & CL_MAKE_SHARED) || IS_MNT_SHARED(old)) // 如果old挂载实例是shared,clone的mnt实例要和old处于同一shared list
list_add(&mnt->mnt_share, &old->mnt_share); // old装载实例和克隆的装载实例是同一个peer group
if (IS_MNT_SLAVE(old)) // 如果old装载实例有slave标志
list_add(&mnt->mnt_slave, &old->mnt_slave); // 克隆的装载实例和old装载实例同属一个slave group
mnt->mnt_master = old->mnt_master; // 克隆的装载实例和old装载实例同属一个slave group
}
}
static int graft_tree(struct mount *mnt, struct mount *p, struct mountpoint *mp)
{
return attach_recursive_mnt(mnt, p, mp, NULL);
}
static int attach_recursive_mnt(struct mount *source_mnt, struct mount *dest_mnt, struct mountpoint *dest_mp, struct path *parent_path)
{
if (!parent_path) { // mount --(r)bind、finish_automount和mount会走这里
err = count_mounts(ns, source_mnt); // dest_mnt实例所在mnt_namespace下的挂载实例数量加上source_mnt实例下所有child实例数量不得超过sysctl_mount_max[100000],mount --rbind新建的source_mnt实例可能有child mnt实例
if (err)
goto out;
}
if (IS_MNT_SHARED(dest_mnt)) { // dest_mnt实例的propagation type包括shared,对应的挂载操作应该传播给同一peer group中的其他mount实例
err = invent_group_ids(source_mnt, true); // source_mnt以及child mount实例如果propagation type不是shared并且没有mnt_group_id都要重新申请mnt_group_id,所以有些slave或private传播属性的mount实例被mount --rbind到shared的mount实例后,对应的mnt_group_id和old对应原来的mnt_group_id不一样
err = propagate_mnt(dest_mnt, dest_mp, source_mnt, &tree_list); // 传播mount操作到dest_mnt的peer group和slave group
for (p = source_mnt; p; p = next_mnt(p, source_mnt)) // 所有要挂载到dest_mnt实例的mount实例(以及它的child mount实例)都要设置propagation type为shared,因为dest_mnt实例的传播属性是shared
set_mnt_shared(p); // source_mnt以及child mount实例的propagation type都应该置上shared标志位
}
if (parent_path) {
} else { // mount --(r)bind会走这里
mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt); // 设置source_mnt挂载实例的parent和挂载点节点
commit_tree(source_mnt); // 提交文件系统树,更新mount namespace的mount数量
}
}
int count_mounts(struct mnt_namespace *ns, struct mount *mnt)
{
for (p = mnt; p; p = next_mnt(p, mnt)) // 递归遍历mnt实例以及mnt下所有child mount实例,这些实例都要算在mount namespace中的mount数量下。所以对于mount --rbind,针对某个mount实例copy_tree后,整颗clone的mount实例tree上所有mount实例都要遍历到。
mounts++;
old = ns->mounts;
pending = ns->pending_mounts;
sum = old + pending;
if ((old > sum) || (pending > sum) || (max < sum) || (mounts > (max - sum)))
return -ENOSPC; // 当前mount namespace存在的mount实例数量超过限制
ns->pending_mounts = pending + mounts; // 更新pending mount实例数量
}
static void commit_tree(struct mount *mnt) // 新的mnt加入mnt->mnt_parent,要更新mnt->mnt_parent所在的mount namespace信息,凡是要挂到parent挂载实例的mount实例(连同其递归child构成的文件系统树上每个mount实例)的mount namespace都要和parent的mount namespace相同,也即,同一文件系统树上所有mount实例处于同一mount namespace
{
struct mnt_namespace *n = parent->mnt_ns;
list_add_tail(&head, &mnt->mnt_list);
list_for_each_entry(m, &head, mnt_list) // mnt实例所在mount namespace的每个mount实例的mount namespace都变成mnt parent所在的mount namespace
m->mnt_ns = n;
list_splice(&head, n->list.prev); // 把mnt所在mount namespace的所有mount实例都加入到mount parent的mount namespace list链表。mount --rbind会递归拷贝以mnt实例开始的文件系统树,每个节点包括mnt都在同一mount namespace(mnt_list),所以所有节点都会和parent的mount namespace相同,处于同一个mnt_list链表。
n->mounts += n->pending_mounts; // mount namespace挂载数量更新,函数count_mounts会更新n->pending_mounts
n->pending_mounts = 0; // pending mount数量清0
__attach_mnt(mnt, parent); // mnt实例放入parent的child链表并加入全局哈希表
}
int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp, struct mount *source_mnt, struct hlist_head *tree_list) // 所有在dest_mnt实例目录下的挂载操作,都要传播到dest_mnt的peer group和slave group中的其他mount实例,slave group中的mount实例如果有peer或slave要继续递归传播
{
for (n = next_peer(dest_mnt); n != dest_mnt; n = next_peer(n)) { // 处理peer group,对所有在mnt_share链表上的其他mount实例传播mount操作
ret = propagate_one(n);
}
for (m = next_group(dest_mnt, dest_mnt); m; // 处理slave group,对所有在mnt_slave_list链表上的mount实例传播mount操作,包括每个slave的peer group和slave group
m = next_group(m, dest_mnt)) {
/* everything in that slave group */
n = m;
do {
ret = propagate_one(n);
n = next_peer(n);
} while (n != m);
}
}
$ fallocate -l 100M sde(n)
$ mkfs.ext4 sde(n)
$ mkdir -p root/var/lib # 针对每个新装载的文件系统
$ mkdir bind(n)
$ mount sde3 root
$ mount sde4 root/var
$ mount sde5 root/var/lib
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
82 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
$ mount --rbind root/ bind1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
82 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
84 19 7:0 / /root/bind1 rw,relatime shared:29 - ext4 /dev/loop0 rw
85 84 7:1 / /root/bind1/var rw,relatime shared:30 - ext4 /dev/loop1 rw
86 85 7:2 / /root/bind1/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
a. bind1下的文件系统mount tree和root一模一样:rbind调用copy_tree递归拷贝source_mnt及其child mount实例,构建的clone mount实例和原文件系统mount tree相同
b. bind后对应mount实例都在同一peer group下(例如/root/bind1/var和/root/root/var的shared的id一样,代表他们的mnt_group_id相同):rbind调用copy_tree -> clone_mnt将每个clone的mount实例的mnt_group_id都设置为old mount实例的mnt_group_id
c. bind1下所有mount实例均有shared传播属性(只有shared传播属性才会显示shared:id):rbind调用attach_recursive_mnt发现dest_mnt(/root/bind1所在的mount实例)的传播属性是shared,将所有克隆的mount实例传播属性设置为shared,但发现它们已经有mnt_group_id(沿用对应old mount实例的mnt_group_id)了,就不在重新分配mnt_group_id
$ mount sde3 root
$ mount sde5 root/var
$ mount --bind --make-slave bind1 bind2 # 简单的mount --make-slave sde(n) bind1并不能将装载实例bind1设置为slave,因为do_new_mount不会对生成的mount实例设置mnt_master,再调用mount --make-slave bind1时发现mnt->mnt_share和mnt->mnt_master都为空,会直接返回并且不设置mnt_master,导致bind1装载实例仍旧没有mnt_master而被show_mountinfo当做private。但如果mount --make-slave sde(n) bind1,然后mount --bind bind1 bind2(使得bind1装载实例的mnt_share链表不为空),最后mount --make-slave bind1,bind1装载实例就会有mnt_master。
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
82 19 253:0 /root/bind1 /root/bind2 rw,relatime master:1 - ext4 /dev/root rw
$ mount --rbind root/ bind2
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
82 19 253:0 /root/bind1 /root/bind2 rw,relatime master:1 - ext4 /dev/root rw
84 82 7:0 / /root/bind2 rw,relatime shared:29 - ext4 /dev/loop0 rw
85 84 7:1 / /root/bind2/var rw,relatime shared:30 - ext4 /dev/loop1 rw
a. rbind到dest_mnt传播属性为slave的文件系统,生成的装载实例slave_gen_mnt的传播属性和对应old mount实例相同:copy_tree将clone的装载实例的mnt_group_id和old装载实例的mnt_group_id对应起来,设置clone的mount实例传播属性和old一样,发现old装载实例的属性为shared所以把clone装载实例的mnt_share和old装载实例的mnt_share链入同一mnt_share_list
$ mount --bind --make-slave bind1 bind2
$ cat /proc/self/mountinfo
78 19 253:0 /root/bind1 /root/bind2 rw,relatime master:1 - ext4 /dev/root rw
$ mount --make-shared bind2
$ cat /proc/self/mountinfo
78 19 253:0 /root/bind1 /root/bind2 rw,relatime shared:29 master:1 - ext4 /dev/root rw
a. bind后master是1:bind调用clone_mnt发现old装载实例的属性为shared所以把clone装载实例(bind2)的mnt_share和old装载实例的mnt_share链入同一mnt_share_list,这样bind生成的装载实例的mnt_share所在链表肯定不为空。所以设置装载实例属性为slave时从mnt_share链表上找到的是old装载实例,然后将old装载设置为clone装载实例的mnt_mater,最后清除克隆装载实例的shared标志位和mnt_group_id、移除mnt_share链表。所以我们看到的mater:1是old装载实例的mnt_group_id
b. 装载实例bind2最终既有shared又有master:如果对以slave方式bind后的装载实例bind2设置shared属性,会调用invent_group_ids为bind2装载实例重新分配mnt_group_id,并设置shared标志位。所以show_mountinfo发现bind2装载实例既有master又有shared标志位
unshare -m bash会顺序执行两个系统调用,unshare(CLONE_NEWNS)和mount --make-rprivate,使得新的命名空间内的文件系统树和原来的一样,并且两个命名空间的装载实例不会互相影响
Step A. unshare -> ksys_unshare -> unshare_nsproxy_namespaces -> create_new_namespaces -> copy_mnt_ns:创建命名空间并拷贝原文件系统装载树到新的mount命名空间
struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
struct user_namespace *user_ns, struct fs_struct *new_fs)
{
new_ns = alloc_mnt_ns(user_ns, false); // 申请新的mount namespace
new = copy_tree(old, old->mnt.mnt_root, copy_flags); // 克隆以old mount实例(根文件系统的parent,即'/'的parent,系统start_kernel时创建,它的parent是自己)为根的文件系统树下所有mount实例
new_ns->root = new; // 新mount namespace的根文件系统对应的装载实例是拷贝过来的文件系统树根mount实例
list_add_tail(&new_ns->list, &new->mnt_list); // 加入新的mount实例及其cloned child mount实例到mount namespace链表,new->mnt_list已经在copy_tree中把所有new挂载实例的所有child mount实例链接进去
}
略
$ fallocate -l 100M sde(n)
$ mkfs.ext4 sde(n)
$ mkdir -p root/var/lib # 针对每个新装载的文件系统
$ mount sde3 root
$ mount sde5 root/var
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
$ unshare --propagation slave -m bash # 被拆分成unshare -m和mount --make-rslave两条系统调用
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime master:29 - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime master:30 - ext4 /dev/loop1 rw
(在原命名空间中运行)$ mount sde6 root/var/lib
(在原命名空间中运行)$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
114 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime master:29 - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime master:30 - ext4 /dev/loop1 rw
115 113 7:2 / /root/root/var/lib rw,relatime master:31 - ext4 /dev/loop2 rw
(在新的命名空间中运行)$ mount sde4 root/var/lib
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime master:29 - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime master:30 - ext4 /dev/loop1 rw
115 113 7:2 / /root/root/var/lib rw,relatime master:31 - ext4 /dev/loop2 rw
117 115 7:3 / /root/root/var/lib rw,relatime - ext4 /dev/loop3 rw
(在原命名空间中运行)$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
114 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
a. 新的命名空间和原命名空间的文件系统装载树一样,而且对应节点mount实例是master-slave关系:unshare用户态工具先调用unshare(系统调用) -> copy_mnt_ns -> copy_tree去克隆原文件系统装载树。对于每个克隆的装载实例,在clone_mnt时发现old mount实例是shared设置了其传播属性也为shared并且和old mount实例放在同一mnt_share链表。然后执行mount --make-slave时发现clone的装载实例的mnt_share链表下一项就是old mount实例(而且mnt_root也一致),于是就将old装载实例作为了对应克隆装载实例的mnt_master,所以看到unshare --propagation slave -m bash后新命名空间的root和root/var对应的master id是旧命名空间的root和root/var的shared id
b. 在旧命名空间装载文件系统到root/var/lib会影响新命名空间,反之则不会:在旧命名空间的mount操作调用attach_recursive_mnt发现dest_mount实例(root/var)是shared传播属性,装载操作会通过propagate_mnt -> propagate_one影响到它的slave group(新命名空间的root/var),所以新命名空间也在root/var/lib装载了文件系统。在新命名空间clone的装载实例其master是对应旧命名空间的装载实例,propagate_one被传入旧命名空间装载实例(root/var)的slave(新命名空间的root/var),然后copy_tree时传入的last_source是旧命名空间的装载实例(root/var/lib)、传入的type是CL_SLAVE,最终返回的cloned装载实例的mnt_master是旧命名空间的装载实例(root/var/lib);在新命名空间装载时,attach_recursive_mnt发现dest_mnt(新命名空间的root/var/lib)不是shared,直接放弃传播,所以旧命名空间不受影响
$ unshare --propagation shared|unchanged -m bash
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
(在原命名空间中运行)$ mount sde6 root/var/lib
(在原命名空间中运行)$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
114 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
115 113 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
(在新的命名空间中运行)$ mount sde4 root/var/lib
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
115 113 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
117 115 7:3 / /root/root/var/lib rw,relatime shared:32 - ext4 /dev/loop3 rw
(在原命名空间中运行)$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
114 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
118 114 7:3 / /root/root/var/lib rw,relatime shared:32 - ext4 /dev/loop3 rw
a. 新的命名空间和原命名空间的文件系统装载树一样,而且对应节点mount实例的shared id相同:unshare用户态工具先调用unshare(系统调用) -> copy_mnt_ns -> copy_tree去克隆原文件系统装载树。对于每个克隆的装载实例,在clone_mnt时先将old mount实例的mnt_group_id赋给clone mount实例并发现old mount实例是shared设置了其clone mount传播属性也为shared并且和old mount实例放在同一mnt_share链表。然后不管是--propagation unchanged还是--propagation shared都不会改变clone的装载实例的mnt_group_id和mnt_share链表,所以看到unshare --propagation unchanged|shared -m bash后新命名空间的root和root/var对应的shared id是旧命名空间的root和root/var的shared id
b. 在旧命名空间装载文件系统到root/var/lib会影响新命名空间,反之亦会影响:在旧命名空间的mount操作调用attach_recursive_mnt发现dest_mount实例(root/var)是shared传播属性,装载操作会通过propagate_mnt -> propagate_one影响到它的peer group(新命名空间的root/var),所以新命名空间也在root/var/lib装载了文件系统。在新命名空间clone的装载实例其shared id和对应旧命名空间的装载实例相同,propagate_one被传入旧命名空间装载实例(root/var)的peer mount(新命名空间的root/var),然后copy_tree时传入的last_source是旧命名空间的装载实例(root/var/lib)、传入的type是CL_MAKE_SHARED,最终返回的cloned装载实例的mnt_group_id就是旧命名空间对应的装载实例(root/var/lib)的mnt_group_id
$ unshare (--propagation private) -m bash # 有没有--propagation private都一样,unshare -m bash默认将拆分成unshare系统调用和mount --make-rprivate
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime - ext4 /dev/loop1 rw
(在原命名空间中运行)$ mount sde6 root/var/lib
(在原命名空间中运行)$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
114 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime - ext4 /dev/loop1 rw
(在新的命名空间中运行)$ mount sde6 root/var/lib
(在新的命名空间中运行)$ cat /proc/self/mountinfo
112 83 7:0 / /root/root rw,relatime - ext4 /dev/loop0 rw
113 112 7:1 / /root/root/var rw,relatime - ext4 /dev/loop1 rw
116 113 7:2 / /root/root/var/lib rw,relatime - ext4 /dev/loop2 rw
(在原命名空间中运行)$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
114 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
a. 新的命名空间和原命名空间的文件系统装载树一样,而且新命名空间所有mount实例没有任何传播标志:unshare用户态工具先调用unshare(系统调用) -> copy_mnt_ns -> copy_tree去克隆原文件系统装载树,克隆后的装载实例和old mount实例传播属性一致。然后新命名空间调用mount --make-rprivate调用change_mnt_propagation改变所有clone的装载实例传播属性,去掉shared标志、清除mnt_group_id和MNT_UNBINDABLE,所以看到新命名空间的mount实例没有显示任何传播属性
b. 在新旧命名空间装载文件系统到root/var/lib不会互相影响:新命名空间的装载实例为private,在下面mount/umount调用到attach_recursive_mnt检查其传播属性非shared,直接放弃传播;旧命名空间的传播属性为shared,但peer group下没有其他mount实例,所以在propagate_mnt中直接跳出传播循环,没有影响任何装载实例
对同一个目录mount多个文件系统会产生如下的结构:
/---------------- 根文件系统
|
|- mnt----------- ext4文件系统(a. mount -t ext4 /dev/sda /mnt)
|
|- tmp------- xfs文件系统(b. mount -t xfs /dev/sdb /mnt)
a. 第一次mount装载ext4文件系统到/mnt(dentry A)下,dentry A上有一个装载实例,ext4文件系统会把/mnt(dentry A)下的内容隐藏
b. 第二次mount装载xfs文件系统到/mnt下,用户态看上去以为xfs是装载在了/mnt(dentry A)下,但实际上对应的装载点是ext4文件系统的根目录/(dentry B),所以最终用户看到/mnt下的内容是最后一次装载实例对应文件系统xfs的内容,dentry A上只有一个装载实例
那么是否存在一个dentry同时被两个不同的装载实例都作为挂载点节点呢?装载后的目录内容对用户态的顶层显示是否还是最后一次装载实例对应的文件系统?
$ mkdir bind(n)
$ unshare -m --propagation slave
(在新命名空间中运行)$ mount --bind bind3 bind2
(在新命名空间中运行)$ cat /proc/self/mountinfo
108 79 253:0 /root/bind3 /root/bind2 rw,relatime master:1 - ext4 /dev/root rw
(在新命名空间中运行)$ touch bind2/before_propagate
(在原命名空间中运行)$ mount --bind bind1 bind2
(在新命名空间中运行)$ cat /proc/self/mountinfo
108 110 253:0 /root/bind3 /root/bind2 rw,relatime master:1 - ext4 /dev/root rw
110 79 253:0 /root/bind1 /root/bind2 rw,relatime master:1 - ext4 /dev/root rw
(在新命名空间中运行)$ ls bind2
before_propagate
(在新命名空间中运行)$ touch bind2/after_propagate
(在原命名空间中运行)$ umount bind2
(在新命名空间中运行)$ ls bind2
after_propagate before_propagate
(在新命名空间中运行)$ umount bind2
(在新命名空间中运行)$ ls bind3
after_propagate before_propagate
(在新命名空间中运行)$ ls bind2
# nothing
a. bind2(新旧命名空间对应bind2的dentry是同一个,创建命名空间并不复制dentry或inode)对应的dentry在某一时刻同时担任三个装载实例的挂载点节点,第一个是“mount --bind bind3 bind2”生成的装载实例mnt_entry_A,第二、三个是受传播属性影响,原命名空间“mount --bind bind1 bind2”操作在新命名空间克隆得到的装载实例mnt_entry_B以及在原命名空间生成的装载实例mnt_entry_C。最终bind2对应的dentry同时担任了两个装载实例的挂载点节点,mnt_entry_A的装载点节点被替换了,mnt_entry_A和mnt_entry_B都在新命名空间,并且mnt_entry_B是mnt_entry_A的parent。这个改变是在1064f874abc0d05eeed8993815f584d847b72486引入的,为了解决原来shadow list实现在某些场景下(同一个dentry先被传播克隆的装载实例entry_X作为挂载点节点,然后又被普通装载实例entry_Y作为挂载点节点,shadow list末尾是entry_Y,在传播umount时也是找shadow list末尾,找到的是entry_Y而不是entry_X)无法将umount传播到其他命名空间下的问题。Ref实现如下:
Step A. do_loopback -> graft_tree -> attach_recursive_mnt -> mnt_set_mountpoint:mount --bind过程生成的装载实例对应挂载点节点的赋值(前面do_loopback曾经提到)
/* mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt)
* dest_mnt: 新命名空间的根文件系统装载实例
* dest_mp: bind2对应的目录项
* source_mnt: mount --bind bind3 bind2在新命名空间生成的装载实例mnt_entry_A
*/
void mnt_set_mountpoint(struct mount *mnt, struct mountpoint *mp, struct mount *child_mnt)
{
mp->m_count++;
mnt_add_count(mnt, 1); /* essentially, that's mntget */
child_mnt->mnt_mountpoint = mp->m_dentry; // bind2的dentry是装载实例mnt_entry_A对应的挂载点节点
child_mnt->mnt_parent = mnt;
child_mnt->mnt_mp = mp; // 挂载点节点
hlist_add_head(&child_mnt->mnt_mp_list, &mp->m_list);
}
Step B. do_loopback -> graft_tree -> attach_recursive_mnt -> propagate_mnt:旧命名空间装载操作传播到新命名空间,同一个dentry作为两个不同命名空间的装载实例对应的挂载点节点,此步骤结束有三个装载实例的装载点节点对应的dentry是同一个
/* propagate_mnt(dest_mnt, dest_mp, source_mnt, &tree_list)
* dest_mnt: 新命名空间的根文件系统装载实例
* dest_mp: bind2对应的目录项
* source_mnt: mount --bind bind1 bind2在旧命名空间生成的装载实例mnt_entry_C
* tree_list: 因为传播而clone的装载实例都放在改链表下
*/
int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
struct mount *source_mnt, struct hlist_head *tree_list)
{
last_source = source_mnt;
mp = dest_mp;
list = tree_list;
/* all slave groups */
for (m = next_group(dest_mnt, dest_mnt); m;
m = next_group(m, dest_mnt)) {
...
ret = propagate_one(n); // 装载操作传播到新命名空间
...
}
}
static int propagate_one(struct mount *m)
{
child = copy_tree(last_source, last_source->mnt.mnt_root, type); // 因为传播需要克隆装载实例,得到mnt_entry_B
mnt_set_mountpoint(m, mp, child); // 设置克隆的装载实例对应的挂载点节点和parent
hlist_add_head(&child->mnt_hash, list); // 因传播而克隆的装载实例都放入链表
}
Step C. do_loopback -> graft_tree -> attach_recursive_mnt:替换mnt_entry_A的挂载点节点,并将mnt_entry_B设置为mnt_entry_A的parent。此步骤执行完之后,bind2只作为两个命名空间装载实例的挂载点节点目录项(mnt_entry_B和mnt_entry_C)。在umount mnt_entry_B时也会有对应操作将mnt_entry_A的parent和挂载点节点恢复
static int attach_recursive_mnt(struct mount *source_mnt, struct mount *dest_mnt, struct mountpoint *dest_mp, bool moving)
{
smp = get_mountpoint(source_mnt->mnt.mnt_root); // 申请临时挂载点节点,source_mnt->mnt.mnt_root是bind1
// propagation
hlist_for_each_entry_safe(child, n, &tree_list, mnt_hash) { // 对于所有因为传播而克隆的装载实例,这里只有mnt_entry_B
struct mount *q;
hlist_del_init(&child->mnt_hash);
q = __lookup_mnt(&child->mnt_parent->mnt,
child->mnt_mountpoint); // 如果可以找到已经存在于全局哈希表、具有相同parent并且挂载点节点dentry相同的装载实例,这里会找到mnt_entry_A
if (q)
mnt_change_mountpoint(child, smp, q); // 改变已经存在的装载实例[mnt_entry_A]的挂载点节点和parent
/* Notice when we are propagating across user namespaces */
if (child->mnt_parent->mnt_ns->user_ns != user_ns)
lock_mnt_tree(child);
child->mnt.mnt_flags &= ~MNT_LOCKED;
commit_tree(child);
}
}
void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
{
struct mountpoint *old_mp = mnt->mnt_mp;
struct mount *old_parent = mnt->mnt_parent;
list_del_init(&mnt->mnt_child); // 装载实例mnt不再是任何人的child
hlist_del_init(&mnt->mnt_mp_list); // 从挂载点节点链表删除装载实例
hlist_del_init_rcu(&mnt->mnt_hash); // 暂时先从哈希表移除,因为改变挂载点节点会改变哈希值
attach_mnt(mnt, parent, mp); // 修改mnt[mnt_entry_A]的mnt_parent为parent[mnt_entry_B],mnt_mountpoint为mp->m_dentry,mnt_mp为mp
put_mountpoint(old_mp); // 释放旧的挂载点节点
mnt_add_count(old_parent, -1); // 原mnt的parent少了一个引用计数
}
b. 即使mnt_entry_B在新命名空间下是第二次装载到bind2上的,用户态在新命名空间下对bind2的操作全部是对于bind3的操作。因为mnt_entry_A的parent被设置为mnt_entry_B,bind2对于用户态而言其顶层的装载实例是mnt_entry_A(对应设备是bind3)
例如:78 19 253:0 /root/bind1 /root/bind2 rw,relatime shared:29 master:1 - ext4 /dev/root rw
static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)
{
struct mount *r = real_mount(mnt);
struct super_block *sb = mnt->mnt_sb;
seq_printf(m, "%i %i %u:%u ", r->mnt_id, r->mnt_parent->mnt_id,
MAJOR(sb->s_dev), MINOR(sb->s_dev)); // 装载实例id,parent id,装载超级块主设备号和次设备号[78 19 253:0]
if (sb->s_op->show_path) { // 设备path,如果是设备则显示为'/',目录则会显示[/root/bind1]
err = sb->s_op->show_path(m, mnt->mnt_root);
} else {
seq_dentry(m, mnt->mnt_root, " \t\n\\");
}
seq_putc(m, ' ');
err = seq_path_root(m, &mnt_path, &p->root, " \t\n\\"); // 挂载点节点对应dentry的名字[/root/bind2]
seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw"); // 装载选项是否有只读[rw]
show_mnt_opts(m, mnt); // 装载选项,以逗号分隔(,realtime,nosuid,nodev)[,relatime]
if (IS_MNT_SHARED(r)) // 如果装载实例的传播属性有shared
seq_printf(m, " shared:%i", r->mnt_group_id); // 装载实例的mnt_group_id[shared:29]
if (IS_MNT_SLAVE(r)) { // 如果装载实例有设置mnt_master
int master = r->mnt_master->mnt_group_id; // 打印装载实例master的mnt_group_id
seq_printf(m, " master:%i", master); // [master:1]
}
if (IS_MNT_UNBINDABLE(r)) // 如果装载实例的传播属性有unbindable[本例暂无]
seq_puts(m, " unbindable");
seq_puts(m, " - "); // 之后是具体文件系统相关数据[-]
show_type(m, sb); // 文件系统类型(ext4,tmpfs)[ext4]
seq_putc(m, ' ');
if (sb->s_op->show_devname) { // 设备名字
err = sb->s_op->show_devname(m, mnt->mnt_root); // [/dev/root]
} else {
mangle(m, r->mnt_devname ? r->mnt_devname : "none");
}
seq_puts(m, sb_rdonly(sb) ? " ro" : " rw"); // 超级块是否只读[rw]
err = show_sb_opts(m, sb); // 超级块选项(sync,dirsync)[本例暂无]
seq_putc(m, '\n');
}