1 简介
1.1 背景
Linux容器场景下实现对进程和其相关的资源的隔离,这些资源同样也包括了进程的文件系统环境。这就要求容器中有一个独立的文件环境,而容器中的进程共享这个文件系统环境,即容器自己的根文件系统环境。
所以内核引入了mnt命名空间的概念,来实现文件系统环境的隔离。
1.2 概述
内核中通过mnt_namespace实现对根文件系统的环境的隔离,不同的mnt_namespace对应不同的根文件系统,组织不同的文件系统挂载树,形成不同的文件系统目录结构。
创建一个新的进程时,会拷贝父进程的命名空间,如果指定一个clone_flag(CLONE_NEWNS)则会创建一个新的命名空间。
对命名空间的修改,属于同一个mnt命名空间的进程可见,其他命名空间的进程不可见。
2 实现机制
2.1 几个相关的技术点
2.1.1 文件系统挂载树
Linux中使用树来组织文件系统,整个文件系统构成一棵树。整个全局系统中只有这样一棵文件树(在没有容器的情况下),这棵树描述文件系统的拓扑结构。
既然是树,所以根就是其赖以生存的基础,这棵树以“/”为根。由于linux的树形文件系统是完全抽象的,因此它不和任何介质进行绑定,仅存在于内核当中,内核只要起来,这个虚拟的树就存在了,只是此时只有树根,然而linux此时却可以挂载任意类型的文件系统到这个树根,这样就可以实现很方便的定制,linux可以在initrd中挂载任意文件系统到树根,这是因为内核和文件系统是分离的概念,内核启动并不依赖任何文件系统。
这棵挂载树的建立包括建立根节点“/”和挂载rootfs文件系统到根目录的过程。
这一部分的操作是在kernel的初始化中完成的,构造根目录的代码是在init_mount_tree()函数(fs/namespace.c)中。
init_mount_tree函数会调用get_fs_type获取文件系统类型,即“rootfs”,这看起来似乎有点奇怪,因为根据前面的说法,似乎是应该先有挂载目录,然后再在其上挂载相应的文件系统,然而此时 VFS 似乎并没有建立其根目录。没关系,这是因为这里调用的是vfs_kern_mount(),这个函数内部自然会创建我们最关心也是最关键的根目录(在 Linux中,目录对应的数据结构是structdentry)。
vfs_kern_mount()主要做的工作是:
1、 调用alloc_vfsmnt()函数在内存里申请了一块该类型的内存空间(struct mount*mnt),并初始化其部分成员变量;
2、 接着会通过rootfs注册的挂载函数rootfs_mount完成rootfs的挂载;
3、 通过rootfs文件系统中挂载的的fill_super函数指针调用ramfs_fill_super()函数;
4、 ramfs_fill_super()函数调用ramfs_get_inode()在内存中分配了一个inode结构(struct *inode)inode,并初始化其部分成员变量,其中比较重要的有i_op、i_fop 和i_sb;
5、 ramfs_fill_super()函数在分配和初始化了inode结构之后,会调用d_make_root()函数来为VFS的目录树建立起关键的根目录“/”的(struct *dentry)dentry,并将 dentry中的d_sb指针指向sb,d_inode指针指向inode;
6、 完成以上的创建后