mnt namespace隔离文件原理

mnt namespace的核心数据结构

图一mnt 数据结构
如图一. 是mnt_namespace隔离文件的核心数据结构(内核3.10.0)。通过对open代码研究,除了对chroot的current->fs->root进行的处理。并没有其他对文件目录的其他修改。那么此处产生的问题在于如何在不同mnt中通过相同的文件名访问位于磁盘不同位置的文件。

mount 的思考

这要从mount格式化挂载目录开始说起。每个文件系统对应一个super_block。格式化分区意味着用对应的sb dentry innode组织了特定磁盘分区。当创建目录和文件的时候,就是意味着分配对应的dentry和inode。然后所谓的open read write通过对这些结构的操作实现了对应磁盘位置的读写。

而mount就是用struct mount封装了sb结构。例如常用的利用同一个文件系统格式化硬盘的不同分区,形成的mount树。就是对不同分区的树形组织。

而查找文件就是根据目录找到对应的mount点,根据sb找到dentry inode结构进行操作的过程,实际上就是找到了对应的分区。

mnt_namespace

这个时候,如果在对mount树进行一次封装,到mnt_namespace中。不同的mnt_ns将会拥有各自的mount 树。当用户进程属于不同的mnt_ns的时候,那么通过相同的目录,访问到的将是不同的mount 树,所以最终操作的也是不同的磁盘位置。

setns

这个调用比较直接,把进程直接放到命名空间内
图二
名字空间形成的文件可以直接解析成nsproxy,然后替换task_struck里面的nsproxy。这里也表明,ns/目录下的文件描述符,只能在一个确定的名字空间中,我们可以通过打开的文件,确定名字空间。甚至切换名字空间。

proc文件系统

图3
上一节讲到了setns,利用的原理就是打开伪文件系统。伪文件系统并不在磁盘上显示内容,是独立的功能模块。如proc文件系统,显示的就是进程的相关信息。setns利用proc文件系统来切换namespace实现容器的切换。也就是说通过文件的形式固化namespace的相关信息。只要我们可以知道创建namespace的进程的pid,那么也就可以任意切换namespace。当然前提是影射了所有的proc文件系统信息。

proc_ns中有个ns,我看了下,每个namespace会单独实现一个install函数,其实就是把void *ns强制转换成六种ns类型,当然还有引用计数什么的。所以说,每个proc下面的节点文件,都会有相应的ns对应,但其实主要还是/proc/pid/ns下面的六个文件,打开后,其中的inode->ns就是代表的namespace信息。

在这里插入图片描述
open的具体过程就是一步一步根据目录名称找对应dentry的过程,期间会对name以"/"符号为分割进行拆分,因为每个目录都是一个dentry。查找的核心位于d_lookup,这里根据dentry的杂凑算法找到dentry下属的list_head列表,进行遍历。简单讲就是没个目录下的dentry会形成list_head列表,但查找算法比较特殊,这里先不研究了。最终一步步找到inode,填入到file结构中。也就是说所有的目录项其实在内存中早已形成了dentry的层级list,我们打开文件就是找到这些list,并最终找到对应的inode节点。之后就是根据文件系统的读写算法进行磁盘操作。这里proc文件系统也是一样。我们根据pid/ns目录找到节点刚好就是proc_inode节点,其中定义了namespace的信息。

文件系统mount过程

在这里插入图片描述

mount树结构遍历算法

在这里插入图片描述
mount结果会形成层级数结构,遍历算法如下:
1.根据第一层的mount1往下找到child1.
2child1->mount->next == &child1->mount说明找到了最底层,否则说明没到最底层,返回child1的entry地址。根据for循环再次进入next_mnt,递归找到最底层。
3.找到最底层即next==&p->mnt_mount, 遍历child.next,属于本层遍历,直到结束。返回到list_for_each_entry
4.这时会找到p1_1,继续递归底层所有mount.
5.如果多余3层,则层层递归向上查询。


struct mount *next_mnt(struct mount *p, struct mount *root)
{
        struct list_head *next = p->mnt_mounts.next;
        if (next == &p->mnt_mounts) {
            while (1) {
                if (p == root)
                    return NULL;
                next = p->mnt_child.next;
                if (next != &p->mnt_parent->mnt_mounts)
                    break;
                p = p->mnt_parent;
            }
        }
        return list_entry(next, struct mount, mnt_child);
}

void test_next_mnt(struct mount *p, struct mount *root)
{

    struct mount * r, *parent;
    parent = p->mnt_parent;
    struct list_head *next = parent->mnt_child.next;
    int i =0 ;

    struct mount * mnt;
    mnt = p;
    struct mount* s;
	list_for_each_entry(r, &mnt->mnt_mounts, mnt_child) {


        for (s = r; s; s = next_mnt(s, r)) {
            printk("vfs %lx mount  %lx mnt_mounts %lx \n", s->mnt, s, s->mnt_mounts);
        }

    }

    printk("mount parent vfsmount %lx \n", p->mnt_parent->mnt);
    return;
	//return list_entry(next, struct mount, mnt_child);
}

总结

/proc/pid/ns目录下通过伪文件形式记录了各个进程的namespace情况,通过proc_inode结构体体现在ns属性上,父命名空间上进程可以通过open setns形式加入到相应的namespace。普通的文件不具有相应的能力,开始的时候猜测有误。当然还是能通过task_struct的形式找到open file的namespace所属的。但是如果是通过另一个ns传递过来的fd,在本ns中如何确定ns却是一个问题。只能通过路径重新打开一边,判断下数据是否相同,或hash是否相同。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值